diff --git a/src/main/java/net/citizensnpcs/Citizens.java b/src/main/java/net/citizensnpcs/Citizens.java index c17ac1643..11d6718eb 100644 --- a/src/main/java/net/citizensnpcs/Citizens.java +++ b/src/main/java/net/citizensnpcs/Citizens.java @@ -4,7 +4,6 @@ import java.io.File; import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; -import java.util.logging.Level; import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; @@ -152,7 +151,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { getServer().getScheduler().cancelTasks(this); } - Messaging.log("v" + getDescription().getVersion() + " disabled."); + Messaging.logF("v%s disabled.", getDescription().getVersion()); } @Override @@ -161,8 +160,8 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { String mcVersion = ((CraftServer) getServer()).getServer().getVersion(); compatible = mcVersion.startsWith(COMPATIBLE_MC_VERSION); if (!compatible) { - Messaging.log(Level.SEVERE, "v" + getDescription().getVersion() + " is not compatible with Minecraft v" - + mcVersion + ". Disabling."); + Messaging.severeF("v%s is not compatible with Minecraft v%s. Disabling.", getDescription().getVersion(), + mcVersion); getServer().getPluginManager().disablePlugin(this); return; } @@ -181,7 +180,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { registerCommands(); - Messaging.log("v" + getDescription().getVersion() + " enabled."); + Messaging.logF("v%s enabled.", getDescription().getVersion()); // Setup NPCs after all plugins have been enabled (allows for multiworld // support and for NPCs to properly register external settings) @@ -193,7 +192,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { startMetrics(); } }) == -1) { - Messaging.log(Level.SEVERE, "Issue enabling plugin. Disabling."); + Messaging.severe("Issue enabling plugin. Disabling."); getServer().getPluginManager().disablePlugin(this); } } @@ -254,7 +253,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { for (DataKey key : saves.getKey("npc").getIntegerSubKeys()) { int id = Integer.parseInt(key.name()); if (!key.keyExists("name")) { - Messaging.log("Could not find a name for the NPC with ID '" + id + "'."); + Messaging.logF("Could not find a name for the NPC with ID '%s'.", id); continue; } String unparsedEntityType = key.getString("traits.type", "PLAYER"); @@ -263,8 +262,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { try { type = EntityType.valueOf(unparsedEntityType); } catch (IllegalArgumentException ex) { - Messaging.log("NPC type '" + unparsedEntityType - + "' was not recognized. Did you spell it correctly?"); + Messaging.logF("NPC type '%s' was not recognized. Did you spell it correctly?", unparsedEntityType); continue; } } @@ -275,7 +273,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { if (npc.isSpawned()) ++spawned; } - Messaging.log("Loaded " + created + " NPCs (" + spawned + " spawned)."); + Messaging.logF("Loaded %d NPCs (%d spawned).", created, spawned); } private void setupScripting() { @@ -302,7 +300,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { saves = new YamlStorage(getDataFolder() + File.separator + Setting.STORAGE_FILE.asString(), "Citizens NPC Storage"); } - Messaging.log("Save method set to", saves.toString()); + Messaging.logF("Save method set to %s.", saves.toString()); } private void startMetrics() { diff --git a/src/main/java/net/citizensnpcs/Settings.java b/src/main/java/net/citizensnpcs/Settings.java index 91a0f0606..33fdfd78b 100644 --- a/src/main/java/net/citizensnpcs/Settings.java +++ b/src/main/java/net/citizensnpcs/Settings.java @@ -19,7 +19,7 @@ public class Settings { config.load(); for (Setting setting : Setting.values()) { if (!root.keyExists(setting.path)) { - Messaging.log("Writing default setting: '" + setting.path + "'"); + Messaging.logF("Writing default setting: '%s'", setting.path); root.setRaw(setting.path, setting.value); } else setting.set(root.getRaw(setting.path)); diff --git a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java index 6a3feaf58..1848de93f 100644 --- a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java +++ b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java @@ -137,7 +137,7 @@ public class NPCCommands { max = 5, permission = "npc.create") @Requirements - public void create(CommandContext args, Player player, NPC npc) { + public void create(CommandContext args, final Player player, NPC npc) { String name = args.getString(1); if (name.length() > 16) { Messaging.sendError(player, "NPC names cannot be longer than 16 characters. The name has been shortened."); diff --git a/src/main/java/net/citizensnpcs/npc/CitizensMobNPC.java b/src/main/java/net/citizensnpcs/npc/CitizensMobNPC.java index 331e6ac2f..b1ae67db4 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensMobNPC.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensMobNPC.java @@ -5,12 +5,14 @@ import java.lang.reflect.Field; import java.util.Map; import net.citizensnpcs.api.npc.NPC; +import net.minecraft.server.Block; import net.minecraft.server.Entity; import net.minecraft.server.EntityLiving; import net.minecraft.server.EntityTypes; import net.minecraft.server.World; import org.bukkit.Location; +import org.bukkit.block.BlockFace; import org.bukkit.craftbukkit.CraftWorld; @SuppressWarnings("unchecked") @@ -41,6 +43,16 @@ public abstract class CitizensMobNPC extends CitizensNPC { protected EntityLiving createHandle(Location loc) { EntityLiving entity = createEntityFromClass(((CraftWorld) loc.getWorld()).getHandle()); entity.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); + + // entity.onGround isn't updated right away - we approximate here so + // that things like pathfinding still work *immediately* after spawn. + org.bukkit.Material beneath = loc.getBlock().getRelative(BlockFace.DOWN).getType(); + if (beneath.isBlock()) { + Block block = Block.byId[beneath.getId()]; + if (block != null && block.material != null) { + entity.onGround = block.material.isSolid(); + } + } return entity; } diff --git a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java index ceca46461..dc16732c9 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java @@ -113,17 +113,15 @@ public abstract class CitizensNPC extends AbstractNPC { for (DataKey traitKey : root.getRelative("traits").getSubKeys()) { Trait trait = traitManager.getTrait(traitKey.name(), this); if (trait == null) { - Messaging.severe(String.format( - "Skipped missing trait '%s' while loading NPC ID: '%d'. Has the name changed?", - traitKey.name(), getId())); + Messaging.severeF("Skipped missing trait '%s' while loading NPC ID: '%d'. Has the name changed?", + traitKey.name(), getId()); continue; } addTrait(trait); try { getTrait(trait.getClass()).load(traitKey); } catch (NPCLoadException ex) { - Messaging.log( - String.format("The trait '%s' failed to load for NPC ID: '%d'.", traitKey.name(), getId()), + Messaging.logF("The trait '%s' failed to load for NPC ID: '%d'.", traitKey.name(), getId(), ex.getMessage()); } } @@ -195,7 +193,7 @@ public abstract class CitizensNPC extends AbstractNPC { super.update(); ai.update(); } catch (Exception ex) { - Messaging.log("Exception while updating " + getId() + ": " + ex.getMessage() + "."); + Messaging.logF("Exception while updating %d: %s.", getId(), ex.getMessage()); ex.printStackTrace(); } } diff --git a/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java b/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java index 1c9fcb172..30ea5a881 100644 --- a/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java +++ b/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java @@ -1,6 +1,7 @@ package net.citizensnpcs.npc.ai; import java.lang.ref.WeakReference; +import java.util.Iterator; import java.util.List; import net.citizensnpcs.api.ai.AI; @@ -98,13 +99,12 @@ public class CitizensAI implements AI { throw new IllegalArgumentException("destination cannot be null"); if (!npc.isSpawned()) throw new IllegalStateException("npc is not spawned"); + if (destination.getWorld() != npc.getBukkitEntity().getWorld()) + throw new IllegalArgumentException("location is not in the same world"); boolean replaced = executing != null; executing = new MCNavigationStrategy(npc, destination); - if (!replaced) - return; - for (int i = 0; i < callbacks.size(); ++i) { NavigationCallback next = callbacks.get(i).get(); if (next == null || (replaced && next.onCancel(this, CancelReason.REPLACE)) || next.onBegin(this)) { @@ -121,8 +121,6 @@ public class CitizensAI implements AI { boolean replaced = executing != null; executing = new MCTargetStrategy(npc, target, aggressive); - if (!replaced) - return; for (int i = 0; i < callbacks.size(); ++i) { NavigationCallback next = callbacks.get(i).get(); if (next == null || (replaced && next.onCancel(this, CancelReason.REPLACE)) || next.onBegin(this)) { @@ -170,17 +168,19 @@ public class CitizensAI implements AI { if (toRemove == null) return; for (Goal goal : toRemove) { - for (int i = 0; i < executingGoals.size(); ++i) { - GoalEntry entry = executingGoals.get(i); + Iterator itr = executingGoals.iterator(); + while (itr.hasNext()) { + GoalEntry entry = itr.next(); if (entry.getGoal().equals(goal)) { entry.getGoal().reset(); - executingGoals.remove(i); + itr.remove(); } } - for (int i = 0; i < goals.size(); ++i) { - GoalEntry entry = goals.get(i); + itr = goals.iterator(); + while (itr.hasNext()) { + GoalEntry entry = itr.next(); if (entry.getGoal().equals(goal)) - goals.remove(i); + itr.remove(); } } diff --git a/src/main/java/net/citizensnpcs/npc/ai/MCNavigationStrategy.java b/src/main/java/net/citizensnpcs/npc/ai/MCNavigationStrategy.java index d1a54b2a9..214a490a1 100644 --- a/src/main/java/net/citizensnpcs/npc/ai/MCNavigationStrategy.java +++ b/src/main/java/net/citizensnpcs/npc/ai/MCNavigationStrategy.java @@ -4,7 +4,6 @@ import java.lang.reflect.Field; import java.util.Map; import net.citizensnpcs.npc.CitizensNPC; -import net.citizensnpcs.npc.entity.EntityHumanNPC; import net.minecraft.server.EntityLiving; import net.minecraft.server.Navigation; @@ -18,7 +17,7 @@ public class MCNavigationStrategy implements PathStrategy { private final EntityLiving entity; private final Navigation navigation; - MCNavigationStrategy(CitizensNPC npc, Location dest) { + MCNavigationStrategy(final CitizensNPC npc, final Location dest) { entity = npc.getHandle(); if (npc.getBukkitEntity() instanceof Player) { entity.onGround = true; @@ -26,14 +25,14 @@ public class MCNavigationStrategy implements PathStrategy { // navigation won't execute, and calling entity.move doesn't // entirely fix the problem. } - navigation = npc.getHandle().al(); + navigation = entity.al(); navigation.a(dest.getX(), dest.getY(), dest.getZ(), getSpeed(npc.getHandle())); } MCNavigationStrategy(EntityLiving entity, EntityLiving target) { this.entity = entity; - if (entity instanceof EntityHumanNPC) { + if (entity.getBukkitEntity() instanceof Player) { entity.onGround = true; // see above } navigation = entity.al(); @@ -60,10 +59,6 @@ public class MCNavigationStrategy implements PathStrategy { @Override public boolean update() { - if (entity instanceof EntityHumanNPC) { - navigation.d(); - ((EntityHumanNPC) entity).moveOnCurrentHeading(); - } return navigation.e(); } diff --git a/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java b/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java index f0b7969a8..0d59da5b1 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java +++ b/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java @@ -28,13 +28,13 @@ public class CitizensHumanNPC extends CitizensNPC implements Equipable { WorldServer ws = ((CraftWorld) loc.getWorld()).getHandle(); final EntityHumanNPC handle = new EntityHumanNPC(ws.getServer().getServer(), ws, StringHelper.parseColors(getFullName()), new ItemInWorldManager(ws), this); + handle.getBukkitEntity().teleport(loc); Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { @Override public void run() { handle.X = loc.getYaw() % 360; - handle.getBukkitEntity().teleport(loc); - // set the position in another tick - if done immediately, - // minecraft will not update the player's position. + // set the head yaw in another tick - if done immediately, + // minecraft will not update it. } }); return handle; @@ -89,7 +89,7 @@ public class CitizensHumanNPC extends CitizensNPC implements Equipable { trait.set(i, null); } } - Messaging.send(equipper, "" + getName() + " had all of its items removed."); + Messaging.sendF(equipper, "%shad all of its items removed.", getName()); } // Drop any previous equipment on the ground if (trait.get(slot) != null && trait.get(slot).getType() != Material.AIR) diff --git a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java index 99b7d1585..65bc5fea6 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java +++ b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java @@ -11,6 +11,7 @@ import net.citizensnpcs.npc.network.NPCSocket; import net.minecraft.server.EntityPlayer; import net.minecraft.server.ItemInWorldManager; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.Navigation; import net.minecraft.server.NetHandler; import net.minecraft.server.NetworkManager; import net.minecraft.server.World; @@ -44,7 +45,11 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHandle { @Override public void F_() { super.F_(); - if (!npc.getAI().hasDestination() && (motX != 0 || motZ != 0 || motY != 0)) { + Navigation navigation = al(); + if (!navigation.e()) { + navigation.d(); + moveOnCurrentHeading(); + } else if (motX != 0 || motZ != 0 || motY != 0) { a(0, 0); } if (noDamageTicks > 0) @@ -52,7 +57,7 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHandle { npc.update(); } - public void moveOnCurrentHeading() { + private void moveOnCurrentHeading() { getControllerMove().c(); getControllerLook().a(); getControllerJump().b(); diff --git a/src/main/java/net/citizensnpcs/util/Messaging.java b/src/main/java/net/citizensnpcs/util/Messaging.java index c705486da..ec007a9ba 100644 --- a/src/main/java/net/citizensnpcs/util/Messaging.java +++ b/src/main/java/net/citizensnpcs/util/Messaging.java @@ -1,5 +1,6 @@ package net.citizensnpcs.util; +import java.util.Arrays; import java.util.logging.Level; import net.citizensnpcs.Settings.Setting; @@ -29,12 +30,34 @@ public class Messaging { log(Level.INFO, msg); } - public static void send(CommandSender sender, Object msg) { - sender.sendMessage(StringHelper.parseColors(msg.toString())); + private static String getFormatted(Object[] msg) { + String toFormat = msg[0].toString(); + Object[] args = msg.length > 1 ? Arrays.copyOfRange(msg, 1, msg.length) : new Object[] {}; + return String.format(toFormat, args); } - public static void sendError(CommandSender sender, Object msg) { - send(sender, ChatColor.RED.toString() + msg); + public static void logF(Object... msg) { + log(getFormatted(msg)); + } + + public static void send(CommandSender sender, Object... msg) { + String joined = SPACE.join(msg); + joined = StringHelper.parseColors(joined); + sender.sendMessage(joined); + } + + public static void sendF(CommandSender sender, Object... msg) { + String joined = getFormatted(msg); + joined = StringHelper.parseColors(joined); + sender.sendMessage(joined); + } + + public static void sendError(CommandSender sender, Object... msg) { + send(sender, ChatColor.RED.toString() + SPACE.join(msg)); + } + + public static void sendErrorF(CommandSender sender, Object... msg) { + sendF(sender, ChatColor.RED.toString() + SPACE.join(msg)); } public static void sendWithNPC(CommandSender sender, Object msg, NPC npc) { @@ -55,4 +78,8 @@ public class Messaging { public static void severe(Object... messages) { log(Level.SEVERE, messages); } + + public static void severeF(Object... messages) { + log(Level.SEVERE, getFormatted(messages)); + } } \ No newline at end of file