diff --git a/src/main/java/net/citizensnpcs/Citizens.java b/src/main/java/net/citizensnpcs/Citizens.java index 7393f4410..98b06e3f1 100644 --- a/src/main/java/net/citizensnpcs/Citizens.java +++ b/src/main/java/net/citizensnpcs/Citizens.java @@ -64,6 +64,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { private boolean compatible; private Settings config; private ClassLoader contextClassLoader; + private Metrics metrics; private CitizensNPCRegistry npcRegistry; private NPCDataStore saves; private NPCSelector selector; @@ -183,6 +184,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { Bukkit.getPluginManager().callEvent(new CitizensDisableEvent()); Editor.leaveAll(); CitizensAPI.shutdown(); + metrics.stopTask(); tearDownScripting(); // Don't bother with this part if MC versions are not compatible @@ -340,28 +342,23 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { } private void startMetrics() { - new Thread() { - @Override - public void run() { - try { - Metrics metrics = new Metrics(Citizens.this); - if (metrics.isOptOut()) - return; - metrics.addCustomData(new Metrics.Plotter("Total NPCs") { - @Override - public int getValue() { - return Iterables.size(npcRegistry); - } - }); - - traitFactory.addPlotters(metrics.createGraph("traits")); - metrics.start(); - Messaging.logTr(Messages.METRICS_NOTIFICATION); - } catch (IOException e) { - Messaging.logTr(Messages.METRICS_ERROR_NOTIFICATION, e.getMessage()); + try { + metrics = new Metrics(Citizens.this); + if (metrics.isOptOut()) + return; + metrics.addCustomData(new Metrics.Plotter("Total NPCs") { + @Override + public int getValue() { + return Iterables.size(npcRegistry); } - } - }.start(); + }); + + traitFactory.addPlotters(metrics.createGraph("traits")); + metrics.start(); + Messaging.logTr(Messages.METRICS_NOTIFICATION); + } catch (IOException e) { + Messaging.logTr(Messages.METRICS_ERROR_NOTIFICATION, e.getMessage()); + } } public void storeNPCs() { diff --git a/src/main/java/net/citizensnpcs/Metrics.java b/src/main/java/net/citizensnpcs/Metrics.java index 2c2a9554d..c45e49e99 100644 --- a/src/main/java/net/citizensnpcs/Metrics.java +++ b/src/main/java/net/citizensnpcs/Metrics.java @@ -459,6 +459,15 @@ public class Metrics { } } + public void stopTask() { + synchronized (optOutLock) { + if (taskId > 0) { + this.plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + } + /** * Represents a custom graph on the website */ diff --git a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java index 41b432c6d..87fb01060 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java @@ -89,6 +89,17 @@ public abstract class CitizensNPC extends AbstractNPC { } public void load(final DataKey root) { + // Spawn the NPC + Spawned spawned = getTrait(Spawned.class); + CurrentLocation spawnLocation = getTrait(CurrentLocation.class); + try { + spawned.load(root.getRelative("spawned")); + spawnLocation.load(root.getRelative("location")); + if (spawned.shouldSpawn() && spawnLocation.getLocation() != null) + spawn(spawnLocation.getLocation()); + } catch (NPCLoadException e) { + } + metadata.loadFrom(root.getRelative("metadata")); // Load traits @@ -123,13 +134,6 @@ public abstract class CitizensNPC extends AbstractNPC { } } - // Spawn the NPC - if (getTrait(Spawned.class).shouldSpawn()) { - Location spawnLoc = getTrait(CurrentLocation.class).getLocation(); - if (spawnLoc != null) - spawn(spawnLoc); - } - navigator.load(root.getRelative("navigator")); } diff --git a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java index 91c7ea065..a1fae57b7 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java +++ b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java @@ -42,6 +42,16 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder { initialise(minecraftServer); } + @Override + public void bf() { + super.bf(); + } + + @Override + public float by() { + return super.by() * npc.getNavigator().getDefaultParameters().speed(); + } + @Override public void collide(net.minecraft.server.Entity entity) { // this method is called by both the entities involved - cancelling @@ -141,8 +151,8 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder { motY += 0.04; } else //(handled elsewhere)*/ if (onGround && bW == 0) { - // bf(); // jump commented out as 0.47 works better for stairs - motY = 0.47F; + // bf(); // jump commented to provide block-specific handling + NMS.blockSpecificJump(this); bW = 10; } } else @@ -159,11 +169,6 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder { NMS.setHeadYaw(this, yaw); } - @Override - public float by() { - return super.by() * npc.getNavigator().getDefaultParameters().speed(); - } - public static class PlayerNPC extends CraftPlayer implements NPCHolder { private final CitizensNPC npc; diff --git a/src/main/java/net/citizensnpcs/util/NMS.java b/src/main/java/net/citizensnpcs/util/NMS.java index 3c8f5a256..a1703d9c4 100644 --- a/src/main/java/net/citizensnpcs/util/NMS.java +++ b/src/main/java/net/citizensnpcs/util/NMS.java @@ -4,9 +4,11 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import net.citizensnpcs.npc.CitizensNPC; +import net.citizensnpcs.npc.entity.EntityHumanNPC; import net.minecraft.server.ControllerLook; import net.minecraft.server.DamageSource; import net.minecraft.server.EnchantmentManager; @@ -22,12 +24,18 @@ import net.minecraft.server.PathfinderGoalSelector; import net.minecraft.server.World; import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; +import org.bukkit.material.Stairs; +import org.bukkit.material.Step; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.primitives.Ints; @SuppressWarnings("unchecked") public class NMS { @@ -45,7 +53,11 @@ public class NMS { private static Field NAVIGATION_WORLD_FIELD; private static Field PATHFINDING_RANGE; private static Field PERSISTENT_FIELD; + private static Set SLAB_MATERIALS = Sets.newHashSet(); private static Field SPEED_FIELD; + + private static Set STAIR_MATERIALS = Sets.newHashSet(); + private static Field THREAD_STOPPER; public static void addOrRemoveFromPlayerList(LivingEntity bukkitEntity, boolean remove) { @@ -97,6 +109,28 @@ public class NMS { target.setOnFire(fireAspectLevel * 4); } + public static void blockSpecificJump(EntityHumanNPC entity) { + int x = MathHelper.floor(entity.locX), y = MathHelper.floor(entity.boundingBox.b - 1), z = MathHelper + .floor(entity.locZ); + int below = entity.world.getTypeId(x, y, z); + BlockFace dir = Util.getFacingDirection(entity.yaw); + int[] typeIds = { below }; + if (dir != BlockFace.SELF) { + typeIds = Ints.concat( + typeIds, + new int[] { + entity.world.getTypeId(x + dir.getModX(), y + dir.getModY(), z + dir.getModZ()), + entity.world.getTypeId(x + dir.getModX(), y + dir.getModY() + 1, + z + dir.getModZ()) }); + } + if (containsAny(STAIR_MATERIALS, typeIds)) { + entity.motY = 0.47F; + } else if (containsAny(SLAB_MATERIALS, typeIds)) { + entity.motY = 0.52F; + } else + entity.motY = 0.5F; + } + public static void clearGoals(PathfinderGoalSelector... goalSelectors) { if (GOAL_FIELD == null || goalSelectors == null) return; @@ -110,6 +144,13 @@ public class NMS { } } + private static boolean containsAny(Set set, int[] tests) { + for (int test : tests) + if (set.contains(test)) + return true; + return false; + } + private static Constructor getCustomEntityConstructor(Class clazz, EntityType type) throws SecurityException, NoSuchMethodException { Constructor constructor = ENTITY_CONSTRUCTOR_CACHE.get(clazz); @@ -270,7 +311,6 @@ public class NMS { Messaging.logTr(Messages.ERROR_UPDATING_PATHFINDING_RANGE, e.getMessage()); } } - static { // true field above false and three synchronised lists THREAD_STOPPER = getField(NetworkManager.class, "m"); @@ -304,4 +344,13 @@ public class NMS { Messaging.logTr(Messages.ERROR_GETTING_ID_MAPPING, e.getMessage()); } } + + static { + for (Material material : Material.values()) { + if (Step.class.isAssignableFrom(material.getData())) + SLAB_MATERIALS.add(material.getId()); + else if (Stairs.class.isAssignableFrom(material.getData())) + STAIR_MATERIALS.add(material.getId()); + } + } } diff --git a/src/main/java/net/citizensnpcs/util/Util.java b/src/main/java/net/citizensnpcs/util/Util.java index d0e217e16..bf6e549f8 100644 --- a/src/main/java/net/citizensnpcs/util/Util.java +++ b/src/main/java/net/citizensnpcs/util/Util.java @@ -12,6 +12,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.BlockFace; import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Entity; @@ -65,6 +66,44 @@ public class Util { NMS.look(handle, (float) yaw - 90, (float) pitch); } + public static BlockFace getFacingDirection(float degrees) { + return getFacingDirection(degrees, 10); + } + + public static BlockFace getFacingDirection(float degrees, double leeway) { + while (degrees < 0D) { + degrees += 360D; + } + while (degrees > 360D) { + degrees -= 360D; + } + if (isFacingNorth(degrees, leeway)) + return BlockFace.WEST; + if (isFacingEast(degrees, leeway)) + return BlockFace.NORTH; + if (isFacingSouth(degrees, leeway)) + return BlockFace.EAST; + if (isFacingWest(degrees, leeway)) + return BlockFace.SOUTH; + return BlockFace.SELF; + } + + private static boolean isFacingEast(double degrees, double leeway) { + return (45 - leeway <= degrees) && (degrees < 135 + leeway); + } + + private static boolean isFacingNorth(double degrees, double leeway) { + return ((0 <= degrees) && (degrees < 45 + leeway)) || ((315 - leeway <= degrees) && (degrees <= 360)); + } + + private static boolean isFacingSouth(double degrees, double leeway) { + return (135 - leeway <= degrees) && (degrees < 225 + leeway); + } + + private static boolean isFacingWest(double degrees, double leeway) { + return (225 - leeway <= degrees) && (degrees < 315 + leeway); + } + public static boolean isSettingFulfilled(Player player, Setting setting) { String parts = setting.asString(); if (parts.contains("*"))