mirror of
https://github.com/CitizensDev/Citizens2.git
synced 2024-10-06 11:27:31 +02:00
Merge branch 'master' of github.com:CitizensDev/Citizens2
This commit is contained in:
commit
56fe2bb453
@ -1,8 +1,6 @@
|
||||
package net.citizensnpcs;
|
||||
|
||||
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;
|
||||
@ -17,6 +15,8 @@ import com.mojang.authlib.properties.Property;
|
||||
|
||||
import net.citizensnpcs.Settings.Setting;
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.api.ai.event.NavigationBeginEvent;
|
||||
import net.citizensnpcs.api.ai.event.NavigationCompleteEvent;
|
||||
import net.citizensnpcs.api.event.CitizensDeserialiseMetaEvent;
|
||||
import net.citizensnpcs.api.event.CitizensReloadEvent;
|
||||
import net.citizensnpcs.api.event.CitizensSerialiseMetaEvent;
|
||||
@ -42,17 +42,17 @@ import net.citizensnpcs.api.trait.trait.Owner;
|
||||
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.npc.skin.SkinUpdateTracker;
|
||||
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.Navigation;
|
||||
|
||||
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;
|
||||
@ -81,19 +81,17 @@ 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.scheduler.BukkitTask;
|
||||
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);
|
||||
private final SkinUpdateTracker skinUpdateTracker;
|
||||
|
||||
EventListen(Map<String, NPCRegistry> registries) {
|
||||
this.registries = registries;
|
||||
this.skinUpdateTracker = new SkinUpdateTracker(npcRegistry, registries);
|
||||
}
|
||||
|
||||
private void checkCreationEvent(CommandSenderCreateNPCEvent event) {
|
||||
@ -326,12 +324,7 @@ public class EventListen implements Listener {
|
||||
|
||||
@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);
|
||||
skinUpdateTracker.onNPCSpawn(event.getNPC());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -341,6 +334,17 @@ public class EventListen implements Listener {
|
||||
toRespawn.remove(toCoord(event.getNPC().getStoredLocation()), event.getNPC());
|
||||
}
|
||||
}
|
||||
skinUpdateTracker.onNPCDespawn(event.getNPC());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onNavigationBegin(NavigationBeginEvent event) {
|
||||
skinUpdateTracker.onNPCNavigationBegin(event.getNPC());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onNavigationComplete(NavigationCompleteEvent event) {
|
||||
skinUpdateTracker.onNPCNavigationComplete(event.getNPC());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@ -354,7 +358,7 @@ public class EventListen implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerChangeWorld(PlayerChangedWorldEvent event) {
|
||||
recalculatePlayer(event.getPlayer(), 20, true);
|
||||
skinUpdateTracker.updatePlayer(event.getPlayer(), 20, true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@ -376,7 +380,7 @@ public class EventListen implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
recalculatePlayer(event.getPlayer(), 20, true);
|
||||
skinUpdateTracker.updatePlayer(event.getPlayer(), 20, true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
@ -388,17 +392,17 @@ public class EventListen implements Listener {
|
||||
event.getPlayer().leaveVehicle();
|
||||
}
|
||||
}
|
||||
skinUpdateTrackers.remove(event.getPlayer().getUniqueId());
|
||||
skinUpdateTracker.removePlayer(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
recalculatePlayer(event.getPlayer(), 15, true);
|
||||
skinUpdateTracker.updatePlayer(event.getPlayer(), 15, true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event) {
|
||||
recalculatePlayer(event.getPlayer(), 15, true);
|
||||
skinUpdateTracker.updatePlayer(event.getPlayer(), 15, true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -453,108 +457,12 @@ 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;
|
||||
|
||||
if (!updateTracker.shouldUpdate(event.getPlayer()))
|
||||
return;
|
||||
|
||||
recalculatePlayer(event.getPlayer(), 10, false);
|
||||
skinUpdateTracker.onPlayerMove(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onCitizensReload(CitizensReloadEvent event) {
|
||||
skinUpdateTrackers.clear();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
|
||||
if (player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
SkinUpdateTracker tracker = skinUpdateTrackers.get(player.getUniqueId());
|
||||
if (tracker == null)
|
||||
continue;
|
||||
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void recalculatePlayer(final Player player, long delay, boolean reset) {
|
||||
if (player.hasMetadata("NPC"))
|
||||
return;
|
||||
|
||||
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() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
List<SkinnableEntity> nearbyNPCs = getNearbySkinnableNPCs(player);
|
||||
for (SkinnableEntity npc : nearbyNPCs) {
|
||||
npc.getSkinTracker().updateViewer(player);
|
||||
}
|
||||
}
|
||||
}.runTaskLater(CitizensAPI.getPlugin(), delay);
|
||||
}
|
||||
|
||||
// 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();
|
||||
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.getSkinnable(npcEntity);
|
||||
|
||||
results.add(skinnable);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
skinUpdateTracker.reset();
|
||||
}
|
||||
|
||||
private void respawnAllFromCoord(ChunkCoord coord) {
|
||||
@ -639,72 +547,4 @@ public class EventListen implements Listener {
|
||||
return prime * (prime * (prime + ((worldName == null) ? 0 : worldName.hashCode())) + x) + z;
|
||||
}
|
||||
}
|
||||
|
||||
private class SkinUpdateTracker {
|
||||
final Location location = new Location(null, 0, 0, 0);
|
||||
int rotationCount;
|
||||
boolean hasMoved;
|
||||
float upperBound;
|
||||
float lowerBound;
|
||||
|
||||
SkinUpdateTracker(Player player) {
|
||||
hardReset(player);
|
||||
}
|
||||
|
||||
boolean shouldUpdate(Player player) {
|
||||
Location currentLoc = player.getLocation(CACHE_LOCATION);
|
||||
|
||||
if (!hasMoved) {
|
||||
hasMoved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rotationCount < 3) {
|
||||
float yaw = NMS.clampYaw(currentLoc.getYaw());
|
||||
boolean hasRotated = upperBound < lowerBound
|
||||
? yaw > upperBound && yaw < lowerBound
|
||||
: yaw > upperBound || yaw < lowerBound;
|
||||
|
||||
// update the first 2 times the player rotates. helps load skins around player
|
||||
// after the player logs/teleports.
|
||||
if (hasRotated) {
|
||||
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(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 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;
|
||||
}
|
||||
|
@ -79,6 +79,8 @@ import net.citizensnpcs.trait.Powered;
|
||||
import net.citizensnpcs.trait.RabbitType;
|
||||
import net.citizensnpcs.trait.RabbitType.RabbitTypes;
|
||||
import net.citizensnpcs.trait.SheepTrait;
|
||||
import net.citizensnpcs.trait.SkinLayers;
|
||||
import net.citizensnpcs.trait.SkinLayers.Layer;
|
||||
import net.citizensnpcs.trait.SlimeSize;
|
||||
import net.citizensnpcs.trait.VillagerProfession;
|
||||
import net.citizensnpcs.trait.WolfModifiers;
|
||||
@ -1322,6 +1324,42 @@ public class NPCCommands {
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
aliases = { "npc" },
|
||||
usage = "skinlayers (--cape [true|false]) (--hat [true|false]) (--jacket [true|false]) (--sleeves [true|false]) (--pants [true|false])",
|
||||
desc = "Sets an NPC's skin layers visibility.",
|
||||
modifiers = { "skinlayers" },
|
||||
min = 1,
|
||||
max = 5,
|
||||
permission = "citizens.npc.skinlayers")
|
||||
@Requirements(types = EntityType.PLAYER, selected = true, ownership = true)
|
||||
public void skinLayers(final CommandContext args, final CommandSender sender, final NPC npc) throws CommandException {
|
||||
SkinLayers trait = npc.getTrait(SkinLayers.class);
|
||||
if (args.hasValueFlag("cape")) {
|
||||
trait.setVisible(Layer.CAPE, Boolean.valueOf(args.getFlag("cape")));
|
||||
}
|
||||
if (args.hasValueFlag("hat")) {
|
||||
trait.setVisible(Layer.HAT, Boolean.valueOf(args.getFlag("hat")));
|
||||
}
|
||||
if (args.hasValueFlag("jacket")) {
|
||||
trait.setVisible(Layer.JACKET, Boolean.valueOf(args.getFlag("jacket")));
|
||||
}
|
||||
if (args.hasValueFlag("sleeves")) {
|
||||
boolean hasSleeves = Boolean.valueOf(args.getFlag("sleeves"));
|
||||
trait.setVisible(Layer.LEFT_SLEEVE, hasSleeves);
|
||||
trait.setVisible(Layer.RIGHT_SLEEVE, hasSleeves);
|
||||
}
|
||||
if (args.hasValueFlag("pants")) {
|
||||
boolean hasPants = Boolean.valueOf(args.getFlag("pants"));
|
||||
trait.setVisible(Layer.LEFT_PANTS, hasPants);
|
||||
trait.setVisible(Layer.RIGHT_PANTS, hasPants);
|
||||
}
|
||||
Messaging.sendTr(sender, Messages.SKIN_LAYERS_SET, npc.getName(),
|
||||
trait.isVisible(Layer.CAPE), trait.isVisible(Layer.HAT), trait.isVisible(Layer.JACKET),
|
||||
trait.isVisible(Layer.LEFT_SLEEVE) || trait.isVisible(Layer.RIGHT_SLEEVE),
|
||||
trait.isVisible(Layer.LEFT_PANTS) || trait.isVisible(Layer.RIGHT_PANTS));
|
||||
}
|
||||
|
||||
@Command(
|
||||
aliases = { "npc" },
|
||||
usage = "size [size]",
|
||||
|
@ -31,6 +31,7 @@ import net.citizensnpcs.trait.Powered;
|
||||
import net.citizensnpcs.trait.RabbitType;
|
||||
import net.citizensnpcs.trait.Saddle;
|
||||
import net.citizensnpcs.trait.SheepTrait;
|
||||
import net.citizensnpcs.trait.SkinLayers;
|
||||
import net.citizensnpcs.trait.SlimeSize;
|
||||
import net.citizensnpcs.trait.VillagerProfession;
|
||||
import net.citizensnpcs.trait.WolfModifiers;
|
||||
@ -65,6 +66,7 @@ public class CitizensTraitFactory implements TraitFactory {
|
||||
registerTrait(TraitInfo.create(RabbitType.class).withName("rabbittype"));
|
||||
registerTrait(TraitInfo.create(Saddle.class).withName("saddle"));
|
||||
registerTrait(TraitInfo.create(SheepTrait.class).withName("sheeptrait"));
|
||||
registerTrait(TraitInfo.create(SkinLayers.class).withName("skinlayers"));
|
||||
registerTrait(TraitInfo.create(NPCSkeletonType.class).withName("skeletontype"));
|
||||
registerTrait(TraitInfo.create(SlimeSize.class).withName("slimesize"));
|
||||
registerTrait(TraitInfo.create(Spawned.class).withName("spawned"));
|
||||
|
@ -235,6 +235,8 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder, Skinnable
|
||||
controllerMove = new PlayerControllerMove(this);
|
||||
navigation = new PlayerNavigation(this, world);
|
||||
NMS.setStepHeight(this, 1); // the default (0) breaks step climbing
|
||||
|
||||
setSkinFlags((byte)0xFF);
|
||||
}
|
||||
|
||||
public boolean isNavigating() {
|
||||
@ -278,6 +280,18 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder, Skinnable
|
||||
controllerJump.a();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkinFlags(byte flags) {
|
||||
// set skin flag byte (DataWatcher API is lacking so
|
||||
// catch the NPE as a sign that this is a MC 1.7 server without the
|
||||
// skin flag)
|
||||
try {
|
||||
getDataWatcher().watch(10, flags);
|
||||
} catch (NullPointerException e) {
|
||||
getDataWatcher().a(10, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkinName(String name) {
|
||||
Preconditions.checkNotNull(name);
|
||||
@ -333,15 +347,6 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder, Skinnable
|
||||
private void updatePackets(boolean navigating) {
|
||||
|
||||
if (world.getWorld().getFullTime() % Setting.PACKET_UPDATE_DELAY.asInt() == 0) {
|
||||
// set skin flag byte to all visible (DataWatcher API is lacking so
|
||||
// catch the NPE as a sign that this is a MC 1.7 server without the
|
||||
// skin flag)
|
||||
try {
|
||||
datawatcher.watch(10, Byte.valueOf((byte) 127));
|
||||
} catch (NullPointerException e) {
|
||||
datawatcher.a(10, Byte.valueOf((byte) 127));
|
||||
}
|
||||
|
||||
Location current = getBukkitEntity().getLocation(packetLocationCache);
|
||||
Packet<?>[] packets = new Packet[navigating ? 5 : 6];
|
||||
if (!navigating) {
|
||||
@ -422,6 +427,11 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder, Skinnable
|
||||
cserver.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkinFlags(byte flags) {
|
||||
((SkinnableEntity) this.entity).setSkinFlags(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkinName(String name) {
|
||||
((SkinnableEntity) this.entity).setSkinName(name);
|
||||
|
@ -129,12 +129,12 @@ public class HumanController extends AbstractEntityController {
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
|
||||
NMS.removeFromWorld(getBukkitEntity());
|
||||
|
||||
SkinnableEntity npc = NMS.getSkinnable(getBukkitEntity());
|
||||
npc.getSkinTracker().onRemoveNPC();
|
||||
|
||||
Player entity = getBukkitEntity();
|
||||
if (entity != null) {
|
||||
NMS.removeFromWorld(entity);
|
||||
SkinnableEntity npc = NMS.getSkinnable(entity);
|
||||
npc.getSkinTracker().onRemoveNPC();
|
||||
}
|
||||
super.remove();
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ 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;
|
||||
@ -135,8 +134,8 @@ public class SkinPacketTracker {
|
||||
continue;
|
||||
|
||||
// send packet now and later to ensure removal from player list
|
||||
NMS.sendPlayerListRemove(player, entity.getBukkitEntity());
|
||||
PLAYER_LIST_REMOVER.sendPacket(player, entity);
|
||||
NMS.sendTabListRemove(player, entity.getBukkitEntity());
|
||||
TAB_LIST_REMOVER.sendPacket(player, entity);
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,27 +146,24 @@ public class SkinPacketTracker {
|
||||
entry.removeTask = Bukkit.getScheduler().runTaskLater(CitizensAPI.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (shouldRemoveFromPlayerList()) {
|
||||
PLAYER_LIST_REMOVER.sendPacket(entry.player, entity);
|
||||
if (shouldRemoveFromTabList()) {
|
||||
TAB_LIST_REMOVER.sendPacket(entry.player, entity);
|
||||
}
|
||||
}
|
||||
}, PACKET_DELAY_REMOVE);
|
||||
}
|
||||
|
||||
private void scheduleRemovePacket(PlayerEntry entry, int count) {
|
||||
if (!shouldRemoveFromPlayerList())
|
||||
if (!shouldRemoveFromTabList())
|
||||
return;
|
||||
|
||||
entry.removeCount = count;
|
||||
scheduleRemovePacket(entry);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
return isNpcRemoved && isTablistDisabled;
|
||||
private boolean shouldRemoveFromTabList() {
|
||||
return entity.getNPC().data().get("removefromtablist",
|
||||
Settings.Setting.DISABLE_TABLIST.asBoolean());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,11 +212,11 @@ public class SkinPacketTracker {
|
||||
entry = new PlayerEntry(player);
|
||||
}
|
||||
|
||||
PLAYER_LIST_REMOVER.cancelPackets(player, entity);
|
||||
TAB_LIST_REMOVER.cancelPackets(player, entity);
|
||||
|
||||
inProgress.put(player.getUniqueId(), entry);
|
||||
skin.apply(entity);
|
||||
NMS.sendPlayerListAdd(player, entity.getBukkitEntity());
|
||||
NMS.sendTabListAdd(player, entity.getBukkitEntity());
|
||||
|
||||
scheduleRemovePacket(entry, 2);
|
||||
}
|
||||
@ -248,12 +244,12 @@ public class SkinPacketTracker {
|
||||
private void onPlayerQuit(PlayerQuitEvent event) {
|
||||
// this also causes any entries in the "inProgress" field to
|
||||
// be removed.
|
||||
PLAYER_LIST_REMOVER.cancelPackets(event.getPlayer());
|
||||
TAB_LIST_REMOVER.cancelPackets(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
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 final TabListRemover TAB_LIST_REMOVER = new TabListRemover();
|
||||
}
|
||||
|
460
src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java
Normal file
460
src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java
Normal file
@ -0,0 +1,460 @@
|
||||
package net.citizensnpcs.npc.skin;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import net.citizensnpcs.Settings;
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.api.npc.NPC;
|
||||
import net.citizensnpcs.api.npc.NPCRegistry;
|
||||
import net.citizensnpcs.util.NMS;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
/**
|
||||
* Tracks skin updates for players.
|
||||
*
|
||||
* @see net.citizensnpcs.EventListen
|
||||
*/
|
||||
public class SkinUpdateTracker {
|
||||
|
||||
private final Map<SkinnableEntity, Void> navigating = new WeakHashMap<SkinnableEntity, Void>(25);
|
||||
private final NPCRegistry npcRegistry;
|
||||
private final Map<String, NPCRegistry> registries;
|
||||
private final Map<UUID, PlayerTracker> playerTrackers =
|
||||
new HashMap<UUID, PlayerTracker>(Bukkit.getMaxPlayers() / 2);
|
||||
private final NPCNavigationUpdater updater = new NPCNavigationUpdater();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param npcRegistry
|
||||
* The primary citizens registry.
|
||||
* @param registries
|
||||
* Map of other registries.
|
||||
*/
|
||||
public SkinUpdateTracker(NPCRegistry npcRegistry, Map<String, NPCRegistry> registries) {
|
||||
Preconditions.checkNotNull(npcRegistry);
|
||||
Preconditions.checkNotNull(registries);
|
||||
|
||||
this.npcRegistry = npcRegistry;
|
||||
this.registries = registries;
|
||||
|
||||
updater.runTaskTimer(CitizensAPI.getPlugin(), 1, 1);
|
||||
new NPCNavigationTracker().runTaskTimer(CitizensAPI.getPlugin(), 3, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a player with skin related packets from nearby skinnable NPC's.
|
||||
*
|
||||
* @param player
|
||||
* The player to update.
|
||||
* @param delay
|
||||
* The delay before sending the packets.
|
||||
* @param reset
|
||||
* True to hard reset the players tracking info, otherwise false.
|
||||
*/
|
||||
public void updatePlayer(final Player player, long delay, final boolean reset) {
|
||||
if (player.hasMetadata("NPC"))
|
||||
return;
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<SkinnableEntity> visible = getNearbyNPCs(player, reset, false);
|
||||
for (SkinnableEntity skinnable : visible) {
|
||||
skinnable.getSkinTracker().updateViewer(player);
|
||||
}
|
||||
}
|
||||
}.runTaskLater(CitizensAPI.getPlugin(), delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a player from the tracker.
|
||||
*
|
||||
* <p>
|
||||
* Used when the player logs out.
|
||||
* </p>
|
||||
*
|
||||
* @param playerId
|
||||
* The ID of the player.
|
||||
*/
|
||||
public void removePlayer(UUID playerId) {
|
||||
Preconditions.checkNotNull(playerId);
|
||||
playerTrackers.remove(playerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all players currently being tracked.
|
||||
*
|
||||
* <p>
|
||||
* Used when Citizens is reloaded.
|
||||
* </p>
|
||||
*/
|
||||
public void reset() {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
PlayerTracker tracker = playerTrackers.get(player.getUniqueId());
|
||||
if (tracker == null)
|
||||
continue;
|
||||
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when an NPC is spawned.
|
||||
*
|
||||
* @param npc
|
||||
* The spawned NPC.
|
||||
*/
|
||||
public void onNPCSpawn(NPC npc) {
|
||||
Preconditions.checkNotNull(npc);
|
||||
SkinnableEntity skinnable = getSkinnable(npc);
|
||||
if (skinnable == null)
|
||||
return;
|
||||
|
||||
// reset nearby players in case they are not looking at the NPC when it spawns.
|
||||
resetNearbyPlayers(skinnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when an NPC is despawned.
|
||||
*
|
||||
* @param npc
|
||||
* The despawned NPC.
|
||||
*/
|
||||
public void onNPCDespawn(NPC npc) {
|
||||
Preconditions.checkNotNull(npc);
|
||||
SkinnableEntity skinnable = getSkinnable(npc);
|
||||
if (skinnable == null)
|
||||
return;
|
||||
|
||||
navigating.remove(skinnable);
|
||||
|
||||
for (PlayerTracker tracker : playerTrackers.values()) {
|
||||
tracker.fovVisibleSkins.remove(skinnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when an NPC begins navigating.
|
||||
*
|
||||
* @param npc
|
||||
* The navigating NPC.
|
||||
*/
|
||||
public void onNPCNavigationBegin(NPC npc) {
|
||||
Preconditions.checkNotNull(npc);
|
||||
SkinnableEntity skinnable = getSkinnable(npc);
|
||||
if (skinnable == null)
|
||||
return;
|
||||
|
||||
navigating.put(skinnable, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when an NPC finishes navigating.
|
||||
*
|
||||
* @param npc
|
||||
* The finished NPC.
|
||||
*/
|
||||
public void onNPCNavigationComplete(NPC npc) {
|
||||
Preconditions.checkNotNull(npc);
|
||||
SkinnableEntity skinnable = getSkinnable(npc);
|
||||
if (skinnable == null)
|
||||
return;
|
||||
|
||||
navigating.remove(skinnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke when a player moves.
|
||||
*
|
||||
* @param player
|
||||
* The player that moved.
|
||||
*/
|
||||
public void onPlayerMove(Player player) {
|
||||
Preconditions.checkNotNull(player);
|
||||
PlayerTracker updateTracker = playerTrackers.get(player.getUniqueId());
|
||||
if (updateTracker == null)
|
||||
return;
|
||||
|
||||
if (!updateTracker.shouldUpdate(player))
|
||||
return;
|
||||
|
||||
updatePlayer(player, 10, false);
|
||||
}
|
||||
|
||||
// hard reset players near a skinnable NPC
|
||||
private void resetNearbyPlayers(SkinnableEntity skinnable) {
|
||||
Entity entity = skinnable.getBukkitEntity();
|
||||
if (entity == null || !entity.isValid())
|
||||
return;
|
||||
|
||||
double viewDistance = Settings.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;
|
||||
|
||||
PlayerTracker tracker = playerTrackers.get(player.getUniqueId());
|
||||
if (tracker != null) {
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SkinnableEntity> getNearbyNPCs(Player player, boolean reset, boolean checkFov) {
|
||||
List<SkinnableEntity> results = new ArrayList<SkinnableEntity>();
|
||||
PlayerTracker tracker = getTracker(player, reset);
|
||||
for (NPC npc : getAllNPCs()) {
|
||||
|
||||
SkinnableEntity skinnable = getSkinnable(npc);
|
||||
if (skinnable == null)
|
||||
continue;
|
||||
|
||||
// if checking field of view, don't add skins that have already been updated for FOV
|
||||
if (checkFov && tracker.fovVisibleSkins.contains(skinnable))
|
||||
continue;
|
||||
|
||||
if (canSee(player, skinnable, checkFov)) {
|
||||
results.add(skinnable);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private Iterable<NPC> getAllNPCs() {
|
||||
return Iterables.filter(Iterables.concat(npcRegistry, Iterables.concat(registries.values())),
|
||||
Predicates.notNull());
|
||||
}
|
||||
|
||||
// get all navigating skinnable NPC's within the players FOV that have not been "seen" yet
|
||||
private void getNewVisibleNavigating(Player player, Collection<SkinnableEntity> output) {
|
||||
PlayerTracker tracker = getTracker(player, false);
|
||||
|
||||
for (SkinnableEntity skinnable : navigating.keySet()) {
|
||||
|
||||
// make sure player hasn't already been updated to prevent excessive tab list flashing
|
||||
// while NPC's are navigating and to reduce the number of times #canSee is invoked.
|
||||
if (tracker.fovVisibleSkins.contains(skinnable))
|
||||
continue;
|
||||
|
||||
if (canSee(player, skinnable, true))
|
||||
output.add(skinnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SkinnableEntity getSkinnable(NPC npc) {
|
||||
Entity entity = npc.getEntity();
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
return NMS.getSkinnable(entity);
|
||||
}
|
||||
|
||||
// get a players tracker, create new one if not exists.
|
||||
private PlayerTracker getTracker(Player player, boolean reset) {
|
||||
PlayerTracker tracker = playerTrackers.get(player.getUniqueId());
|
||||
if (tracker == null) {
|
||||
tracker = new PlayerTracker(player);
|
||||
playerTrackers.put(player.getUniqueId(), tracker);
|
||||
}
|
||||
else if (reset) {
|
||||
tracker.hardReset(player);
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
|
||||
// determines if a player is near a skinnable entity and, if checkFov set, if the
|
||||
// skinnable entity is within the players field of view.
|
||||
private boolean canSee(Player player, SkinnableEntity skinnable, boolean checkFov) {
|
||||
Player entity = skinnable.getBukkitEntity();
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
if (!player.canSee(entity))
|
||||
return false;
|
||||
|
||||
if (!player.getWorld().equals(entity.getWorld()))
|
||||
return false;
|
||||
|
||||
Location playerLoc = player.getLocation(CACHE_LOCATION);
|
||||
Location skinLoc = entity.getLocation(NPC_LOCATION);
|
||||
|
||||
double viewDistance = Settings.Setting.NPC_SKIN_VIEW_DISTANCE.asDouble();
|
||||
viewDistance *= viewDistance;
|
||||
|
||||
if (playerLoc.distanceSquared(skinLoc) > viewDistance)
|
||||
return false;
|
||||
|
||||
// see if the NPC is within the players field of view
|
||||
if (checkFov) {
|
||||
double deltaX = skinLoc.getX() - playerLoc.getX();
|
||||
double deltaZ = skinLoc.getZ() - playerLoc.getZ();
|
||||
double angle = Math.atan2(deltaX, deltaZ);
|
||||
float skinYaw = NMS.clampYaw(-(float) Math.toDegrees(angle));
|
||||
float playerYaw = NMS.clampYaw(playerLoc.getYaw());
|
||||
float upperBound = playerYaw + FIELD_OF_VIEW;
|
||||
float lowerBound = playerYaw - FIELD_OF_VIEW;
|
||||
|
||||
return skinYaw >= lowerBound && skinYaw <= upperBound;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Tracks player location and yaw to determine when the player should be updated
|
||||
// with nearby skins.
|
||||
private class PlayerTracker {
|
||||
final Location location = new Location(null, 0, 0, 0);
|
||||
final Set<SkinnableEntity> fovVisibleSkins = new HashSet<SkinnableEntity>(20);
|
||||
int rotationCount;
|
||||
boolean hasMoved;
|
||||
float upperBound;
|
||||
float lowerBound;
|
||||
|
||||
PlayerTracker(Player player) {
|
||||
hardReset(player);
|
||||
}
|
||||
|
||||
boolean shouldUpdate(Player player) {
|
||||
Location currentLoc = player.getLocation(CACHE_LOCATION);
|
||||
|
||||
if (!hasMoved) {
|
||||
hasMoved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rotationCount < 3) {
|
||||
float yaw = NMS.clampYaw(currentLoc.getYaw());
|
||||
boolean hasRotated = yaw < lowerBound || yaw > upperBound;
|
||||
|
||||
// update the first 3 times the player rotates. helps load skins around player
|
||||
// after the player logs/teleports.
|
||||
if (hasRotated) {
|
||||
rotationCount++;
|
||||
reset(player);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure player is in same world
|
||||
if (!currentLoc.getWorld().equals(this.location.getWorld())) {
|
||||
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(this.location);
|
||||
if (rotationCount < 3) {
|
||||
float rotationDegrees = Settings.Setting.NPC_SKIN_ROTATION_UPDATE_DEGREES.asFloat();
|
||||
float yaw = NMS.clampYaw(this.location.getYaw());
|
||||
this.upperBound = yaw + rotationDegrees;
|
||||
this.lowerBound = yaw - rotationDegrees;
|
||||
}
|
||||
}
|
||||
|
||||
// reset all
|
||||
void hardReset(Player player) {
|
||||
this.hasMoved = false;
|
||||
this.rotationCount = 0;
|
||||
this.fovVisibleSkins.clear();
|
||||
reset(player);
|
||||
}
|
||||
}
|
||||
|
||||
// update players when the NPC navigates into their field of view
|
||||
private class NPCNavigationTracker extends BukkitRunnable {
|
||||
@Override
|
||||
public void run() {
|
||||
if (navigating.isEmpty() || playerTrackers.isEmpty())
|
||||
return;
|
||||
|
||||
List<SkinnableEntity> nearby = new ArrayList<SkinnableEntity>(10);
|
||||
Collection<? extends Player> players = Bukkit.getOnlinePlayers();
|
||||
|
||||
for (Player player : players) {
|
||||
if (player.hasMetadata("NPC"))
|
||||
continue;
|
||||
|
||||
getNewVisibleNavigating(player, nearby);
|
||||
|
||||
for (SkinnableEntity skinnable : nearby) {
|
||||
PlayerTracker tracker = getTracker(player, false);
|
||||
tracker.fovVisibleSkins.add(skinnable);
|
||||
updater.queue.offer(new UpdateInfo(player, skinnable));
|
||||
}
|
||||
|
||||
nearby.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updates players. Repeating task used to schedule updates without
|
||||
// causing excessive scheduling.
|
||||
private class NPCNavigationUpdater extends BukkitRunnable {
|
||||
Queue<UpdateInfo> queue = new ArrayDeque<UpdateInfo>(20);
|
||||
@Override
|
||||
public void run() {
|
||||
while (!queue.isEmpty()) {
|
||||
UpdateInfo info = queue.remove();
|
||||
info.entity.getSkinTracker().updateViewer(info.player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class UpdateInfo {
|
||||
Player player;
|
||||
SkinnableEntity entity;
|
||||
UpdateInfo(Player player, SkinnableEntity entity) {
|
||||
this.player = player;
|
||||
this.entity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
private static final float FIELD_OF_VIEW = 70f;
|
||||
}
|
@ -9,11 +9,6 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public interface SkinnableEntity extends NPCHolder {
|
||||
|
||||
/**
|
||||
* Get the entities skin packet tracker.
|
||||
*/
|
||||
SkinPacketTracker getSkinTracker();
|
||||
|
||||
/**
|
||||
* Get the bukkit entity.
|
||||
*/
|
||||
@ -29,12 +24,33 @@ public interface SkinnableEntity extends NPCHolder {
|
||||
*/
|
||||
String getSkinName();
|
||||
|
||||
/**
|
||||
* Get the entities skin packet tracker.
|
||||
*/
|
||||
SkinPacketTracker getSkinTracker();
|
||||
|
||||
/**
|
||||
* Set the bit flags that represent the skin layer parts visibility.
|
||||
*
|
||||
* <p>
|
||||
* Setting the skin flags automatically updates the NPC skin.
|
||||
* </p>
|
||||
*
|
||||
* @param flags
|
||||
* The bit flags.
|
||||
*/
|
||||
void setSkinFlags(byte flags);
|
||||
|
||||
/**
|
||||
* Set the name of the player whose skin the NPC
|
||||
* uses.
|
||||
*
|
||||
* <p>Setting the skin name automatically updates and
|
||||
* respawn the NPC.</p>
|
||||
* <p>
|
||||
* Setting the skin name automatically updates and respawn the NPC.
|
||||
* </p>
|
||||
*
|
||||
* @param name
|
||||
* The skin name.
|
||||
*/
|
||||
void setSkinName(String name);
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ import net.citizensnpcs.util.NMS;
|
||||
* Collects entities to remove and sends them all to the player in a single packet.
|
||||
* </p>
|
||||
*/
|
||||
public class PlayerListRemover {
|
||||
public class TabListRemover {
|
||||
private final Map<UUID, PlayerEntry> pending = new HashMap<UUID, PlayerEntry>(Bukkit.getMaxPlayers() / 2);
|
||||
|
||||
PlayerListRemover() {
|
||||
TabListRemover() {
|
||||
Bukkit.getScheduler().runTaskTimer(CitizensAPI.getPlugin(), new Sender(), 2, 2);
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ public class PlayerListRemover {
|
||||
}
|
||||
|
||||
if (entry.player.isOnline())
|
||||
NMS.sendPlayerListRemove(entry.player, skinnableList);
|
||||
NMS.sendTabListRemove(entry.player, skinnableList);
|
||||
|
||||
// notify skin trackers that a remove packet has been sent to a player
|
||||
for (SkinnableEntity entity : skinnableList) {
|
262
src/main/java/net/citizensnpcs/trait/SkinLayers.java
Normal file
262
src/main/java/net/citizensnpcs/trait/SkinLayers.java
Normal file
@ -0,0 +1,262 @@
|
||||
package net.citizensnpcs.trait;
|
||||
|
||||
import net.citizensnpcs.api.persistence.Persist;
|
||||
import net.citizensnpcs.api.trait.Trait;
|
||||
import net.citizensnpcs.npc.skin.SkinnableEntity;
|
||||
import net.citizensnpcs.util.NMS;
|
||||
|
||||
public class SkinLayers extends Trait {
|
||||
|
||||
@Persist("cape")
|
||||
private boolean cape = true;
|
||||
@Persist("hat")
|
||||
private boolean hat = true;
|
||||
@Persist("jacket")
|
||||
private boolean jacket = true;
|
||||
@Persist("left-pants")
|
||||
private boolean leftPants = true;
|
||||
@Persist("left-sleeve")
|
||||
private boolean leftSleeve = true;
|
||||
@Persist("right-pants")
|
||||
private boolean rightPants = true;
|
||||
@Persist("right-sleeve")
|
||||
private boolean rightSleeve = true;
|
||||
|
||||
public SkinLayers() {
|
||||
super("skinlayers");
|
||||
}
|
||||
|
||||
public SkinLayers show() {
|
||||
cape = true;
|
||||
hat = true;
|
||||
jacket = true;
|
||||
leftSleeve = true;
|
||||
rightSleeve = true;
|
||||
leftPants = true;
|
||||
rightPants = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showCape() {
|
||||
cape = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showHat() {
|
||||
hat = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showJacket() {
|
||||
jacket = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showLeftPants() {
|
||||
leftPants = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showLeftSleeve() {
|
||||
leftSleeve = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showPants() {
|
||||
leftPants = true;
|
||||
rightPants = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showRightPants() {
|
||||
rightPants = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showRightSleeve() {
|
||||
rightSleeve = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers showSleeves() {
|
||||
leftSleeve = true;
|
||||
rightSleeve = true;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hide() {
|
||||
cape = false;
|
||||
hat = false;
|
||||
jacket = false;
|
||||
leftSleeve = false;
|
||||
rightSleeve = false;
|
||||
leftPants = false;
|
||||
rightPants = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideCape() {
|
||||
cape = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideHat() {
|
||||
hat = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideJacket() {
|
||||
jacket = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideLeftPants() {
|
||||
leftPants = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideLeftSleeve() {
|
||||
leftSleeve = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hidePants() {
|
||||
leftPants = false;
|
||||
rightPants = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideRightPants() {
|
||||
rightPants = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideRightSleeve() {
|
||||
rightSleeve = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SkinLayers hideSleeves() {
|
||||
leftSleeve = false;
|
||||
rightSleeve = false;
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isVisible(Layer layer) {
|
||||
switch (layer) {
|
||||
case CAPE:
|
||||
return cape;
|
||||
case JACKET:
|
||||
return jacket;
|
||||
case LEFT_SLEEVE:
|
||||
return leftSleeve;
|
||||
case RIGHT_SLEEVE:
|
||||
return rightSleeve;
|
||||
case LEFT_PANTS:
|
||||
return leftPants;
|
||||
case RIGHT_PANTS:
|
||||
return rightPants;
|
||||
case HAT:
|
||||
return hat;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach() {
|
||||
setFlags();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn() {
|
||||
setFlags();
|
||||
}
|
||||
|
||||
public SkinLayers setVisible(Layer layer, boolean isVisible) {
|
||||
switch (layer) {
|
||||
case CAPE:
|
||||
cape = isVisible;
|
||||
break;
|
||||
case JACKET:
|
||||
jacket = isVisible;
|
||||
break;
|
||||
case LEFT_SLEEVE:
|
||||
leftSleeve = isVisible;
|
||||
break;
|
||||
case RIGHT_SLEEVE:
|
||||
rightSleeve = isVisible;
|
||||
break;
|
||||
case LEFT_PANTS:
|
||||
leftPants = isVisible;
|
||||
break;
|
||||
case RIGHT_PANTS:
|
||||
rightPants = isVisible;
|
||||
break;
|
||||
case HAT:
|
||||
hat = isVisible;
|
||||
break;
|
||||
}
|
||||
setFlags();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SkinLayers{cape:" + cape + ", hat:" + hat + ", jacket:" + jacket + ", leftSleeve:" + leftSleeve
|
||||
+ ", rightSleeve:" + rightSleeve + ", leftPants:" + leftPants + ", rightPants:" + rightPants + "}";
|
||||
}
|
||||
|
||||
private void setFlags() {
|
||||
if (!npc.isSpawned())
|
||||
return;
|
||||
|
||||
SkinnableEntity skinnable = NMS.getSkinnable(npc.getEntity());
|
||||
if (skinnable == null)
|
||||
return;
|
||||
|
||||
int flags = 0xFF;
|
||||
for (Layer layer : Layer.values()) {
|
||||
if (!isVisible(layer)) {
|
||||
flags &= ~layer.flag;
|
||||
}
|
||||
}
|
||||
skinnable.setSkinFlags((byte)flags);
|
||||
}
|
||||
|
||||
public enum Layer {
|
||||
CAPE (0),
|
||||
JACKET (1),
|
||||
LEFT_SLEEVE (2),
|
||||
RIGHT_SLEEVE (3),
|
||||
LEFT_PANTS (4),
|
||||
RIGHT_PANTS (5),
|
||||
HAT (6);
|
||||
|
||||
final int flag;
|
||||
|
||||
Layer(int offset) {
|
||||
this.flag = 1 << offset;
|
||||
}
|
||||
}
|
||||
}
|
@ -192,6 +192,7 @@ public class Messages {
|
||||
public static final String SIZE_SET = "citizens.commands.npc.size.set";
|
||||
public static final String SKELETON_TYPE_SET = "citizens.commands.npc.skeletontype.set";
|
||||
public static final String SKIN_CLEARED = "citizens.commands.npc.skin.cleared";
|
||||
public static final String SKIN_LAYERS_SET = "citizens.commands.npc.skin.layers-set";
|
||||
public static final String SKIN_SET = "citizens.commands.npc.skin.set";
|
||||
public static final String SKIPPING_BROKEN_TRAIT = "citizens.notifications.skipping-broken-trait";
|
||||
public static final String SKIPPING_INVALID_ANCHOR = "citizens.notifications.skipping-invalid-anchor";
|
||||
|
@ -117,7 +117,7 @@ public class NMS {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void sendPlayerListAdd(Player recipient, Player listPlayer) {
|
||||
public static void sendTabListAdd(Player recipient, Player listPlayer) {
|
||||
Preconditions.checkNotNull(recipient);
|
||||
Preconditions.checkNotNull(listPlayer);
|
||||
|
||||
@ -127,7 +127,7 @@ public class NMS {
|
||||
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entity));
|
||||
}
|
||||
|
||||
public static void sendPlayerListRemove(Player recipient, Player listPlayer) {
|
||||
public static void sendTabListRemove(Player recipient, Player listPlayer) {
|
||||
Preconditions.checkNotNull(recipient);
|
||||
Preconditions.checkNotNull(listPlayer);
|
||||
|
||||
@ -137,8 +137,8 @@ public class NMS {
|
||||
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, entity));
|
||||
}
|
||||
|
||||
public static void sendPlayerListRemove(Player recipient,
|
||||
Collection<? extends SkinnableEntity> skinnableNPCs) {
|
||||
public static void sendTabListRemove(Player recipient,
|
||||
Collection<? extends SkinnableEntity> skinnableNPCs) {
|
||||
Preconditions.checkNotNull(recipient);
|
||||
Preconditions.checkNotNull(skinnableNPCs);
|
||||
|
||||
|
@ -96,6 +96,7 @@ citizens.commands.npc.respawn.describe=Respawn delay is currently [[{0}]].
|
||||
citizens.commands.npc.select.already-selected=You already have that NPC selected.
|
||||
citizens.commands.npc.skin.set=[[{0}]]''s skin name set to [[{1}]].
|
||||
citizens.commands.npc.skin.cleared=[[{0}]]''s skin name was cleared.
|
||||
citizens.commands.npc.skin.layers-set=[[{0}]]''s skin layers: cape - [[{1}]], hat - [[{2}]], jacket - [[{3}]], sleeves - [[{4}]], pants - [[{5}]].
|
||||
citizens.commands.npc.size.description={0}''s size is [[{1}]].
|
||||
citizens.commands.npc.size.set={0}''s size set to [[{1}]].
|
||||
citizens.commands.npc.sound.invalid-sound=Invalid sound.
|
||||
|
@ -646,6 +646,7 @@ permissions:
|
||||
citizens.npc.size: true
|
||||
citizens.npc.skeletontype: true
|
||||
citizens.npc.skin: true
|
||||
citizens.npc.skinlayers: true
|
||||
citizens.npc.sound: true
|
||||
citizens.npc.spawn: true
|
||||
citizens.npc.speak: true
|
||||
@ -994,6 +995,7 @@ permissions:
|
||||
citizens.npc.size: true
|
||||
citizens.npc.skeletontype: true
|
||||
citizens.npc.skin: true
|
||||
citizens.npc.skinlayers: true
|
||||
citizens.npc.sound: true
|
||||
citizens.npc.spawn: true
|
||||
citizens.npc.speak: true
|
||||
|
Loading…
Reference in New Issue
Block a user