diff --git a/src/main/java/net/citizensnpcs/Settings.java b/src/main/java/net/citizensnpcs/Settings.java index 5c75b0ae9..d4411c7d4 100644 --- a/src/main/java/net/citizensnpcs/Settings.java +++ b/src/main/java/net/citizensnpcs/Settings.java @@ -52,9 +52,12 @@ public class Settings { DATABASE_USERNAME("storage.database.username", ""), DEBUG_MODE("general.debug-mode", false), DEFAULT_LOOK_CLOSE("npc.default.look-close", false), + DEFAULT_LOOK_CLOSE_RANGE("npc.default.look-close.range", 5), DEFAULT_PATHFINDING_RANGE("npc.pathing.default-pathfinding-range", 25F), DEFAULT_RANDOM_TALKER("npc.default.random-talker", true), + DEFAULT_REALISTIC_LOOKING("npc.default.realistic-looking", false), DEFAULT_TALK_CLOSE("npc.default.talk-close", false), + DEFAULT_TALK_CLOSE_RANGE("npc.default.talk-close.range", 5), DEFAULT_TEXT("npc.default.text.0", "Hi, I'm !") { @Override public void loadFromKey(DataKey root) { diff --git a/src/main/java/net/citizensnpcs/command/CommandConfigurable.java b/src/main/java/net/citizensnpcs/command/CommandConfigurable.java new file mode 100644 index 000000000..7ac126ed3 --- /dev/null +++ b/src/main/java/net/citizensnpcs/command/CommandConfigurable.java @@ -0,0 +1,5 @@ +package net.citizensnpcs.command; + +public interface CommandConfigurable { + void configure(CommandContext args); +} diff --git a/src/main/java/net/citizensnpcs/command/CommandManager.java b/src/main/java/net/citizensnpcs/command/CommandManager.java index aea600359..e6b03df82 100644 --- a/src/main/java/net/citizensnpcs/command/CommandManager.java +++ b/src/main/java/net/citizensnpcs/command/CommandManager.java @@ -135,9 +135,11 @@ public class CommandManager { if (cmd.max() != -1 && context.argsLength() > cmd.max()) throw new CommandUsageException("Too many arguments.", getUsage(args, cmd)); - for (char flag : context.getFlags()) - if (cmd.flags().indexOf(String.valueOf(flag)) == -1) - throw new CommandUsageException("Unknown flag: " + flag, getUsage(args, cmd)); + if (!context.getFlags().contains('*')) { + for (char flag : context.getFlags()) + if (cmd.flags().indexOf(String.valueOf(flag)) == -1) + throw new CommandUsageException("Unknown flag: " + flag, getUsage(args, cmd)); + } methodArgs[0] = context; Object instance = instances.get(method); diff --git a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java index f3b427167..6054faef6 100644 --- a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java +++ b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java @@ -13,6 +13,7 @@ import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.trait.trait.Owner; import net.citizensnpcs.api.trait.trait.Spawned; import net.citizensnpcs.command.Command; +import net.citizensnpcs.command.CommandConfigurable; import net.citizensnpcs.command.CommandContext; import net.citizensnpcs.command.Requirements; import net.citizensnpcs.command.exception.CommandException; @@ -557,4 +558,27 @@ public class NPCCommands { Messaging.sendF(sender, ChatColor.GREEN + "Trait %s added successfully.", StringHelper.wrap(traitName)); } + + @Command( + aliases = { "npc" }, + usage = "traitc|tc [trait name] [flags]", + desc = "Configures a trait", + modifiers = { "traitc", "tc" }, + min = 2, + flags = "*", + permission = "npc.trait-configure") + public void traitConfigure(CommandContext args, CommandSender sender, NPC npc) throws CommandException { + String traitName = args.getString(1); + if (!sender.hasPermission("citizens.npc.trait-configure." + traitName)) + throw new NoPermissionsException(); + Class clazz = CitizensAPI.getTraitFactory().getTraitClass(args.getString(1)); + if (clazz == null) + throw new CommandException("Trait not found."); + if (!clazz.isAssignableFrom(CommandConfigurable.class)) + throw new CommandException("That trait is not configurable"); + if (!npc.hasTrait(clazz)) + throw new CommandException("The NPC doesn't have that trait."); + CommandConfigurable trait = (CommandConfigurable) npc.getTrait(clazz); + trait.configure(args); + } } \ No newline at end of file diff --git a/src/main/java/net/citizensnpcs/trait/LookClose.java b/src/main/java/net/citizensnpcs/trait/LookClose.java index 7f1a0930a..9bc3837e9 100644 --- a/src/main/java/net/citizensnpcs/trait/LookClose.java +++ b/src/main/java/net/citizensnpcs/trait/LookClose.java @@ -5,63 +5,58 @@ import java.util.Comparator; import java.util.List; import net.citizensnpcs.Settings.Setting; +import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.exception.NPCLoadException; import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.util.DataKey; -import net.minecraft.server.EntityLiving; +import net.citizensnpcs.command.CommandConfigurable; +import net.citizensnpcs.command.CommandContext; +import net.citizensnpcs.util.Util; import org.bukkit.Location; -import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; -public class LookClose extends Trait implements Toggleable { +public class LookClose extends Trait implements Toggleable, CommandConfigurable { private boolean enabled = Setting.DEFAULT_LOOK_CLOSE.asBoolean(); private Player lookingAt; + private double range = Setting.DEFAULT_LOOK_CLOSE_RANGE.asDouble(); + private boolean realisticLooking = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean(); public LookClose() { super("lookclose"); } - private void faceEntity(Entity from, Entity at) { - if (from.getWorld() != at.getWorld()) - return; - Location loc = from.getLocation(); + private boolean canSeeTarget() { + return realisticLooking ? Util.rayTrace(npc.getBukkitEntity(), lookingAt) : true; + } - double xDiff = at.getLocation().getX() - loc.getX(); - double yDiff = at.getLocation().getY() - loc.getY(); - double zDiff = at.getLocation().getZ() - loc.getZ(); - - double distanceXZ = Math.sqrt(xDiff * xDiff + zDiff * zDiff); - double distanceY = Math.sqrt(distanceXZ * distanceXZ + yDiff * yDiff); - - double yaw = (Math.acos(xDiff / distanceXZ) * 180 / Math.PI); - double pitch = (Math.acos(yDiff / distanceY) * 180 / Math.PI) - 90; - if (zDiff < 0.0) { - yaw = yaw + (Math.abs(180 - yaw) * 2); - } - - EntityLiving handle = ((CraftLivingEntity) from).getHandle(); - handle.yaw = (float) yaw - 90; - handle.pitch = (float) pitch; - handle.as = handle.yaw; + @Override + public void configure(CommandContext args) { + range = args.getFlagDouble("range", range); + range = args.getFlagDouble("r", range); + realisticLooking = args.hasFlag('r'); } private void findNewTarget() { - List nearby = npc.getBukkitEntity().getNearbyEntities(2.5, 5, 2.5); + List nearby = npc.getBukkitEntity().getNearbyEntities(range / 2, range, range / 2); + final Location npcLocation = npc.getBukkitEntity().getLocation(); Collections.sort(nearby, new Comparator() { @Override public int compare(Entity o1, Entity o2) { - double d1 = o1.getLocation().distanceSquared(npc.getBukkitEntity().getLocation()); - double d2 = o2.getLocation().distanceSquared(npc.getBukkitEntity().getLocation()); + double d1 = o1.getLocation().distanceSquared(npcLocation); + double d2 = o2.getLocation().distanceSquared(npcLocation); return Double.compare(d1, d2); } }); for (Entity entity : nearby) { - if (entity instanceof Player) { - lookingAt = (Player) entity; - return; - } + if (entity.getType() != EntityType.PLAYER) + continue; + if (CitizensAPI.getNPCRegistry().getNPC(entity) != null) + continue; + lookingAt = (Player) entity; + return; } lookingAt = null; } @@ -70,33 +65,34 @@ public class LookClose extends Trait implements Toggleable { if (lookingAt == null) return true; if (!lookingAt.isOnline() || lookingAt.getWorld() != npc.getBukkitEntity().getWorld() - || lookingAt.getLocation().distanceSquared(npc.getBukkitEntity().getLocation()) > 5) { + || lookingAt.getLocation().distanceSquared(npc.getBukkitEntity().getLocation()) > range) { lookingAt = null; - return true; } - return false; + return lookingAt == null; } @Override public void load(DataKey key) throws NPCLoadException { enabled = key.getBoolean(""); + range = key.getDouble("range", range); + realisticLooking = key.getBoolean("realistic-looking", false); } @Override public void run() { if (!enabled || npc.getNavigator().isNavigating()) return; - if (hasInvalidTarget()) { + if (hasInvalidTarget()) findNewTarget(); - } - if (lookingAt != null) { - faceEntity(npc.getBukkitEntity(), lookingAt); - } + if (lookingAt != null && canSeeTarget()) + Util.faceEntity(npc.getBukkitEntity(), lookingAt); } @Override public void save(DataKey key) { key.setBoolean("", enabled); + key.setDouble("range", range); + key.setBoolean("realistic-looking", realisticLooking); } @Override diff --git a/src/main/java/net/citizensnpcs/trait/text/StartPrompt.java b/src/main/java/net/citizensnpcs/trait/text/StartPrompt.java index e337f8521..863e9b70a 100644 --- a/src/main/java/net/citizensnpcs/trait/text/StartPrompt.java +++ b/src/main/java/net/citizensnpcs/trait/text/StartPrompt.java @@ -3,6 +3,7 @@ package net.citizensnpcs.trait.text; import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.StringHelper; +import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; import org.bukkit.conversations.StringPrompt; @@ -24,10 +25,14 @@ public class StartPrompt extends StringPrompt { else if (input.equalsIgnoreCase("remove")) return new TextRemovePrompt(text); else if (input.equalsIgnoreCase("random")) - Messaging.send((Player) context.getForWhom(), "Random talker set to " + text.toggleRandomTalker() - + "."); + Messaging.send((Player) context.getForWhom(), + "Random talker set to " + text.toggleRandomTalker() + "."); + else if (input.equalsIgnoreCase("realistic looking")) + Messaging.send((CommandSender) context.getForWhom(), + "Realistic looking set to " + text.toggleRealisticLooking() + "."); else if (input.equalsIgnoreCase("close")) - Messaging.send((Player) context.getForWhom(), "Close talker set to " + text.toggle() + "."); + Messaging.send((Player) context.getForWhom(), "Close talker set to " + text.toggle() + + "."); else Messaging.sendError((Player) context.getForWhom(), "Invalid edit type."); diff --git a/src/main/java/net/citizensnpcs/trait/text/Text.java b/src/main/java/net/citizensnpcs/trait/text/Text.java index 135d68fd3..6262fda20 100644 --- a/src/main/java/net/citizensnpcs/trait/text/Text.java +++ b/src/main/java/net/citizensnpcs/trait/text/Text.java @@ -37,6 +37,8 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve private int currentIndex; private final Plugin plugin; private boolean randomTalker = Setting.DEFAULT_RANDOM_TALKER.asBoolean(); + private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble(); + private boolean realisticLooker = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean(); private boolean talkClose = Setting.DEFAULT_TALK_CLOSE.asBoolean(); private final List text = new ArrayList(); @@ -59,9 +61,9 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve } public Editor getEditor(final Player player) { - final Conversation conversation = new ConversationFactory(plugin).addConversationAbandonedListener(this) - .withLocalEcho(false).withEscapeSequence("/npc text").withModality(false) - .withFirstPrompt(new StartPrompt(this)).buildConversation(player); + final Conversation conversation = new ConversationFactory(plugin) + .addConversationAbandonedListener(this).withLocalEcho(false).withEscapeSequence("/npc text") + .withModality(false).withFirstPrompt(new StartPrompt(this)).buildConversation(player); return new Editor() { @Override @@ -89,10 +91,10 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve if (text.isEmpty()) populateDefaultText(); - if (key.keyExists("talk-close")) - talkClose = key.getBoolean("talk-close"); - if (key.keyExists("random-talker")) - randomTalker = key.getBoolean("random-talker"); + talkClose = key.getBoolean("talk-close", talkClose); + realisticLooker = key.getBoolean("realistic-looking", realisticLooker); + randomTalker = key.getBoolean("random-talker", randomTalker); + range = key.getDouble("range", range); } @EventHandler @@ -147,6 +149,8 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve public void save(DataKey key) { key.setBoolean("talk-close", talkClose); key.setBoolean("random-talker", randomTalker); + key.setBoolean("realistic-looking", realisticLooker); + key.setDouble("range", range); for (int i = 0; i < text.size(); i++) key.setString(String.valueOf(i), text.get(i)); } @@ -183,13 +187,15 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve @Override public boolean toggle() { - talkClose = !talkClose; - return talkClose; + return (talkClose = !talkClose); } public boolean toggleRandomTalker() { - randomTalker = !randomTalker; - return randomTalker; + return (randomTalker = !randomTalker); + } + + public boolean toggleRealisticLooking() { + return (realisticLooker = !realisticLooker); } @Override diff --git a/src/main/java/net/citizensnpcs/trait/waypoint/WanderingWaypointProvider.java b/src/main/java/net/citizensnpcs/trait/waypoint/WanderingWaypointProvider.java index 7941293d6..cec31207a 100644 --- a/src/main/java/net/citizensnpcs/trait/waypoint/WanderingWaypointProvider.java +++ b/src/main/java/net/citizensnpcs/trait/waypoint/WanderingWaypointProvider.java @@ -37,6 +37,11 @@ public class WanderingWaypointProvider implements WaypointProvider, Iterable iterator() { + return iterator; + } + @Override public void load(DataKey key) { } @@ -59,9 +64,4 @@ public class WanderingWaypointProvider implements WaypointProvider, Iterable iterator() { - return iterator; - } } diff --git a/src/main/java/net/citizensnpcs/util/Util.java b/src/main/java/net/citizensnpcs/util/Util.java index 6f196fa94..24c8b95c0 100644 --- a/src/main/java/net/citizensnpcs/util/Util.java +++ b/src/main/java/net/citizensnpcs/util/Util.java @@ -4,6 +4,7 @@ import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.event.NPCCollisionEvent; import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.npc.NPC; +import net.minecraft.server.EntityLiving; import net.minecraft.server.Packet; import org.apache.commons.lang.Validate; @@ -11,8 +12,11 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -34,6 +38,30 @@ public class Util { return event; } + public static void faceEntity(Entity from, Entity at) { + if (from.getWorld() != at.getWorld()) + return; + Location loc = from.getLocation(); + + double xDiff = at.getLocation().getX() - loc.getX(); + double yDiff = at.getLocation().getY() - loc.getY(); + double zDiff = at.getLocation().getZ() - loc.getZ(); + + double distanceXZ = Math.sqrt(xDiff * xDiff + zDiff * zDiff); + double distanceY = Math.sqrt(distanceXZ * distanceXZ + yDiff * yDiff); + + double yaw = (Math.acos(xDiff / distanceXZ) * 180 / Math.PI); + double pitch = (Math.acos(yDiff / distanceY) * 180 / Math.PI) - 90; + if (zDiff < 0.0) { + yaw = yaw + (Math.abs(180 - yaw) * 2); + } + + EntityLiving handle = ((CraftLivingEntity) from).getHandle(); + handle.yaw = (float) yaw - 90; + handle.pitch = (float) pitch; + handle.as = handle.yaw; + } + public static boolean isSettingFulfilled(Player player, Setting setting) { String parts = setting.asString(); if (parts.contains("*")) @@ -59,6 +87,12 @@ public class Util { return type; } + public static boolean rayTrace(LivingEntity entity, LivingEntity entity2) { + EntityLiving from = ((CraftLivingEntity) entity).getHandle(); + EntityLiving to = ((CraftLivingEntity) entity2).getHandle(); + return from.l(to); + } + public static void sendPacketNearby(Location location, Packet packet, double radius) { radius *= radius; final World world = location.getWorld();