Add skin handling and /npc skin (note: may not work in 1.8)

This commit is contained in:
fullwall 2014-04-18 16:38:44 +08:00
parent c1b5fd78c2
commit 0af5f8bee1
5 changed files with 164 additions and 14 deletions

View File

@ -11,7 +11,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<craftbukkit.version>1.7.8-R0.1-SNAPSHOT</craftbukkit.version>
<craftbukkit.version>1.7.9-R0.1-SNAPSHOT</craftbukkit.version>
<citizensapi.version>2.0.13-SNAPSHOT</citizensapi.version>
<vault.version>1.2.19-SNAPSHOT</vault.version>
<powermock.version>1.4.12</powermock.version>

View File

@ -267,7 +267,7 @@ public class NPCCommands {
}
CommandSenderCreateNPCEvent event = sender instanceof Player ? new PlayerCreateNPCEvent((Player) sender, copy)
: new CommandSenderCreateNPCEvent(sender, copy);
: new CommandSenderCreateNPCEvent(sender, copy);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
event.getNPC().destroy();
@ -343,7 +343,7 @@ public class NPCCommands {
spawnLoc = args.getSenderLocation();
}
CommandSenderCreateNPCEvent event = sender instanceof Player ? new PlayerCreateNPCEvent((Player) sender, npc)
: new CommandSenderCreateNPCEvent(sender, npc);
: new CommandSenderCreateNPCEvent(sender, npc);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
npc.destroy();
@ -1018,7 +1018,7 @@ public class NPCCommands {
@Requirements(selected = true, ownership = true, types = { EntityType.CREEPER })
public void power(CommandContext args, CommandSender sender, NPC npc) {
Messaging
.sendTr(sender, npc.getTrait(Powered.class).toggle() ? Messages.POWERED_SET : Messages.POWERED_STOPPED);
.sendTr(sender, npc.getTrait(Powered.class).toggle() ? Messages.POWERED_SET : Messages.POWERED_STOPPED);
}
@Command(
@ -1170,6 +1170,32 @@ public class NPCCommands {
Messaging.sendTr(sender, Messages.SKELETON_TYPE_SET, npc.getName(), type);
}
@Command(
aliases = { "npc" },
usage = "skin (-c) [name]",
desc = "Sets an NPC's skin name",
modifiers = { "skin" },
min = 1,
max = 2,
permission = "citizens.npc.skin")
@Requirements(types = EntityType.PLAYER)
public void skin(final CommandContext args, final CommandSender sender, final NPC npc) throws CommandException {
String skinName = npc.getName();
if (args.hasFlag('c')) {
npc.data().remove(NPC.PLAYER_SKIN_UUID_METADATA);
} else {
if (args.argsLength() != 2)
throw new CommandException();
npc.data().setPersistent(NPC.PLAYER_SKIN_UUID_METADATA, args.getString(1));
skinName = args.getString(1);
}
Messaging.sendTr(sender, Messages.SKIN_SET, npc.getName(), skinName);
if (npc.isSpawned()) {
npc.despawn(DespawnReason.PENDING_RESPAWN);
npc.spawn(npc.getStoredLocation());
}
}
@Command(
aliases = { "npc" },
usage = "size [size]",

View File

@ -115,8 +115,9 @@ public class CitizensNPC extends AbstractNPC {
// Spawn the NPC
CurrentLocation spawnLocation = getTrait(CurrentLocation.class);
if (getTrait(Spawned.class).shouldSpawn() && spawnLocation.getLocation() != null)
if (getTrait(Spawned.class).shouldSpawn() && spawnLocation.getLocation() != null) {
spawn(spawnLocation.getLocation());
}
navigator.load(root.getRelative("navigator"));
}

View File

@ -1,23 +1,35 @@
package net.citizensnpcs.npc.entity;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.event.DespawnReason;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.util.Colorizer;
import net.citizensnpcs.npc.AbstractEntityController;
import net.citizensnpcs.util.NMS;
import net.minecraft.server.v1_7_R3.PlayerInteractManager;
import net.minecraft.server.v1_7_R3.WorldServer;
import net.minecraft.util.com.google.common.collect.Iterables;
import net.minecraft.util.com.mojang.authlib.Agent;
import net.minecraft.util.com.mojang.authlib.GameProfile;
import net.minecraft.util.com.mojang.authlib.GameProfileRepository;
import net.minecraft.util.com.mojang.authlib.ProfileLookupCallback;
import net.minecraft.util.com.mojang.authlib.minecraft.MinecraftSessionService;
import net.minecraft.util.com.mojang.authlib.properties.Property;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_7_R3.CraftServer;
import org.bukkit.craftbukkit.v1_7_R3.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import com.google.common.collect.Maps;
public class HumanController extends AbstractEntityController {
public HumanController() {
super();
@ -25,15 +37,22 @@ public class HumanController extends AbstractEntityController {
@Override
protected Entity createEntity(final Location at, final NPC npc) {
WorldServer ws = ((CraftWorld) at.getWorld()).getHandle();
String parseColors = Colorizer.parseColors(npc.getFullName());
if (parseColors.length() > 16) {
parseColors = parseColors.substring(0, 16);
final WorldServer nmsWorld = ((CraftWorld) at.getWorld()).getHandle();
String coloredName = Colorizer.parseColors(npc.getFullName());
if (coloredName.length() > 16) {
coloredName = coloredName.substring(0, 16);
}
UUID uuid = UUID.randomUUID(); // clear version
uuid = new UUID(uuid.getMostSignificantBits() | 0x0000000000005000L, uuid.getLeastSignificantBits());
final EntityHumanNPC handle = new EntityHumanNPC(ws.getServer().getServer(), ws, new GameProfile(uuid,
parseColors), new PlayerInteractManager(ws), npc);
UUID uuid = UUID.randomUUID();
if (uuid.version() == 4) { // clear version
uuid = new UUID(uuid.getMostSignificantBits() | 0x0000000000005000L, uuid.getLeastSignificantBits());
}
GameProfile profile = new GameProfile(uuid, coloredName);
updateSkin(npc, nmsWorld, profile);
final EntityHumanNPC handle = new EntityHumanNPC(nmsWorld.getServer().getServer(), nmsWorld, profile,
new PlayerInteractManager(nmsWorld), npc);
handle.setPositionRotation(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch());
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
@Override
@ -51,4 +70,108 @@ public class HumanController extends AbstractEntityController {
public Player getBukkitEntity() {
return (Player) super.getBukkitEntity();
}
private void updateSkin(final NPC npc, final WorldServer nmsWorld, GameProfile profile) {
String skinUUID = npc.data().get(NPC.PLAYER_SKIN_UUID_METADATA);
if (skinUUID == null) {
skinUUID = npc.getName();
}
if (npc.data().has(CACHED_SKIN_UUID_METADATA) && npc.data().has(CACHED_SKIN_UUID_NAME_METADATA)
&& skinUUID.equalsIgnoreCase(npc.data().<String> get(CACHED_SKIN_UUID_NAME_METADATA))) {
skinUUID = npc.data().get(CACHED_SKIN_UUID_METADATA);
}
if (UUID_CACHE.containsKey(skinUUID)) {
skinUUID = UUID_CACHE.get(skinUUID);
}
Property cached = TEXTURE_CACHE.get(skinUUID);
if (cached != null) {
profile.getProperties().put("textures", cached);
} else {
Bukkit.getScheduler().runTaskAsynchronously(CitizensAPI.getPlugin(),
new SkinFetcher(new UUIDFetcher(skinUUID, npc), nmsWorld.getMinecraftServer().av(), npc));
}
}
private static class SkinFetcher implements Runnable {
private final NPC npc;
private final MinecraftSessionService repo;
private final Callable<String> uuid;
public SkinFetcher(Callable<String> uuid, MinecraftSessionService repo, NPC npc) {
this.uuid = uuid;
this.repo = repo;
this.npc = npc;
}
@Override
public void run() {
String realUUID;
try {
realUUID = uuid.call();
} catch (Exception e) {
e.printStackTrace();
return;
}
GameProfile skinProfile = null;
try {
skinProfile = repo.fillProfileProperties(new GameProfile(UUID.fromString(realUUID), ""), true);
} catch (Exception e) {
e.printStackTrace();
return;
}
if (skinProfile == null || !skinProfile.getProperties().containsKey("textures"))
return;
Property textures = Iterables.getFirst(skinProfile.getProperties().get("textures"), null);
if (textures.getValue() != null && textures.getSignature() != null) {
TEXTURE_CACHE.put(realUUID, new Property("textures", textures.getValue(), textures.getSignature()));
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
@Override
public void run() {
if (npc.isSpawned()) {
npc.despawn(DespawnReason.PENDING_RESPAWN);
npc.spawn(npc.getStoredLocation());
}
}
});
}
}
}
public static class UUIDFetcher implements Callable<String> {
private final NPC npc;
private final String reportedUUID;
public UUIDFetcher(String reportedUUID, NPC npc) {
this.reportedUUID = reportedUUID;
this.npc = npc;
}
@Override
public String call() throws Exception {
if (reportedUUID.contains("-")) {
return reportedUUID;
}
final GameProfileRepository repo = ((CraftServer) Bukkit.getServer()).getServer()
.getGameProfileRepository();
repo.findProfilesByNames(new String[] { reportedUUID }, Agent.MINECRAFT, new ProfileLookupCallback() {
@Override
public void onProfileLookupFailed(GameProfile arg0, Exception arg1) {
throw new RuntimeException(arg1);
}
@Override
public void onProfileLookupSucceeded(final GameProfile profile) {
UUID_CACHE.put(reportedUUID, profile.getId().toString());
npc.data().setPersistent(CACHED_SKIN_UUID_METADATA, profile.getId().toString());
npc.data().setPersistent(CACHED_SKIN_UUID_NAME_METADATA, profile.getName());
}
});
return npc.data().get(CACHED_SKIN_UUID_METADATA);
}
}
private static final String CACHED_SKIN_UUID_METADATA = "cached-skin-uuid";
private static final String CACHED_SKIN_UUID_NAME_METADATA = "cached-skin-uuid-name";
private static final Map<String, Property> TEXTURE_CACHE = Maps.newConcurrentMap();
private static final Map<String, String> UUID_CACHE = Maps.newConcurrentMap();
}

View File

@ -176,7 +176,7 @@ public class NMS {
@SuppressWarnings("deprecation")
private static Constructor<?> getCustomEntityConstructor(Class<?> clazz, EntityType type) throws SecurityException,
NoSuchMethodException {
NoSuchMethodException {
Constructor<?> constructor = ENTITY_CONSTRUCTOR_CACHE.get(clazz);
if (constructor == null) {
constructor = clazz.getConstructor(World.class);