diff --git a/src/main/java/net/citizensnpcs/Citizens.java b/src/main/java/net/citizensnpcs/Citizens.java index 334c32a21..683339904 100644 --- a/src/main/java/net/citizensnpcs/Citizens.java +++ b/src/main/java/net/citizensnpcs/Citizens.java @@ -13,6 +13,7 @@ import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.scripting.EventRegistrar; import net.citizensnpcs.api.scripting.PluginProvider; import net.citizensnpcs.api.scripting.ScriptCompiler; +import net.citizensnpcs.api.trait.TraitManager; import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.DatabaseStorage; import net.citizensnpcs.api.util.NBTStorage; @@ -57,7 +58,7 @@ public class Citizens extends JavaPlugin { private ClassLoader contextClassLoader; private CitizensNPCManager npcManager; private Storage saves; - private CitizensTraitManager traitManager; + private TraitManager traitManager; public CommandManager getCommandManager() { return commands; @@ -153,6 +154,7 @@ public class Citizens extends JavaPlugin { CitizensAPI.setNPCManager(npcManager); CitizensAPI.setCharacterManager(characterManager); CitizensAPI.setTraitManager(traitManager); + CitizensAPI.setDataFolder(getDataFolder()); getServer().getPluginManager().registerEvents(new EventListen(npcManager), this); diff --git a/src/main/java/net/citizensnpcs/command/CommandContext.java b/src/main/java/net/citizensnpcs/command/CommandContext.java index 0141e49d4..7eaf07cd1 100644 --- a/src/main/java/net/citizensnpcs/command/CommandContext.java +++ b/src/main/java/net/citizensnpcs/command/CommandContext.java @@ -170,10 +170,14 @@ public class CommandContext { } public String getJoinedStrings(int initialIndex) { + return getJoinedStrings(initialIndex, ' '); + } + + public String getJoinedStrings(int initialIndex, char delimiter) { initialIndex = initialIndex + 1; StringBuilder buffer = new StringBuilder(args[initialIndex]); for (int i = initialIndex + 1; i < args.length; i++) - buffer.append(" ").append(args[i]); + buffer.append(delimiter).append(args[i]); return buffer.toString(); } diff --git a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java index 74809bb33..863bf617f 100644 --- a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java +++ b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java @@ -19,8 +19,8 @@ import net.citizensnpcs.command.ServerCommand; import net.citizensnpcs.command.exception.CommandException; import net.citizensnpcs.command.exception.NoPermissionsException; import net.citizensnpcs.npc.CitizensNPCManager; -import net.citizensnpcs.npc.CitizensTraitManager; import net.citizensnpcs.trait.Age; +import net.citizensnpcs.trait.Behaviour; import net.citizensnpcs.trait.Controllable; import net.citizensnpcs.trait.CurrentLocation; import net.citizensnpcs.trait.LookClose; @@ -40,11 +40,12 @@ import org.bukkit.entity.Player; import org.bukkit.entity.Villager.Profession; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import com.google.common.base.Splitter; + @Requirements(selected = true, ownership = true) public class NPCCommands { private final CharacterManager characterManager = CitizensAPI.getCharacterManager(); private final CitizensNPCManager npcManager; - private final CitizensTraitManager traitManager = (CitizensTraitManager) CitizensAPI.getTraitManager(); public NPCCommands(Citizens plugin) { npcManager = plugin.getNPCManager(); @@ -88,6 +89,14 @@ public class NPCCommands { Messaging.send(player, "Age " + (trait.toggle() ? "locked" : "unlocked") + "."); } + @Command(aliases = { "npc" }, usage = "behaviour [scripts]", desc = "Sets the behaviour of a NPC", modifiers = { + "behaviour", "ai" }, min = 2, max = -1) + public void behaviour(CommandContext args, Player player, NPC npc) throws CommandException { + Iterable files = Splitter.on(',').split(args.getJoinedStrings(1, ',')); + npc.getTrait(Behaviour.class).addScripts(files); + player.sendMessage(ChatColor.GREEN + "Behaviours added."); + } + @Command( aliases = { "npc" }, usage = "character [character]", @@ -119,7 +128,7 @@ public class NPCCommands { @Command( aliases = { "npc" }, - usage = "create [name] ((-b) --type (type) --char (char))", + usage = "create [name] ((-b) --type (type) --char (char) --behaviour (behaviour))", desc = "Create a new NPC", flags = "b", modifiers = { "create" }, @@ -142,7 +151,6 @@ public class NPCCommands { type = EntityType.PLAYER; } } - Messaging.log(type); npc = npcManager.createNPC(type, name); String msg = ChatColor.GREEN + "You created " + StringHelper.wrap(npc.getName()); if (args.hasValueFlag("char")) { @@ -175,16 +183,21 @@ public class NPCCommands { } } + if (args.hasValueFlag("behaviour")) { + npc.getTrait(Behaviour.class).addScripts(Splitter.on(",").split(args.getFlag("behaviour"))); + msg += " with the specified behaviours"; + } + msg += "."; // Initialize necessary traits - npc.addTrait(traitManager.getTrait(Owner.class)); + npc.addTrait(Owner.class); if (!Setting.SERVER_OWNS_NPCS.asBoolean()) npc.getTrait(Owner.class).setOwner(player.getName()); npc.getTrait(MobType.class).setType(type.toString()); - npc.addTrait(traitManager.getTrait(LookClose.class, npc)); - npc.addTrait(traitManager.getTrait(Text.class, npc)); - npc.addTrait(traitManager.getTrait(Saddle.class, npc)); + npc.addTrait(LookClose.class); + npc.addTrait(Text.class); + npc.addTrait(Saddle.class); npc.spawn(player.getLocation()); diff --git a/src/main/java/net/citizensnpcs/command/command/ScriptCommands.java b/src/main/java/net/citizensnpcs/command/command/ScriptCommands.java index 52d352768..0071f4591 100644 --- a/src/main/java/net/citizensnpcs/command/command/ScriptCommands.java +++ b/src/main/java/net/citizensnpcs/command/command/ScriptCommands.java @@ -44,11 +44,7 @@ public class ScriptCommands { Script s = script.newInstance(); if (args.hasValueFlag("i")) { for (String m : Splitter.on(',').split(args.getFlag("i"))) { - try { - s.invoke(m, new Object[] {}); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } + s.invoke(m, new Object[] {}); } } Messaging.send(sender, "Done."); diff --git a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java index 213f0de6a..4abc4a5c2 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java @@ -1,9 +1,5 @@ package net.citizensnpcs.npc; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; - import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.event.NPCDespawnEvent; @@ -24,9 +20,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.event.Listener; import org.bukkit.inventory.Inventory; -import org.bukkit.plugin.Plugin; public abstract class CitizensNPC extends AbstractNPC { private final CitizensAI ai = new CitizensAI(this); @@ -40,31 +34,6 @@ public abstract class CitizensNPC extends AbstractNPC { traitManager = (CitizensTraitManager) CitizensAPI.getTraitManager(); } - @Override - public void addTrait(Trait trait) { - // TODO: right now every addTrait call has to be wrapped with - // TraitManager.getTrait(Class, NPC) -- this is bad, need to fix this. - if (trait == null) { - Bukkit.getLogger().log(Level.SEVERE, "Cannot register a null trait. Was it registered properly?"); - return; - } - - if (trait instanceof Runnable) { - runnables.add((Runnable) trait); - if (traits.containsKey(trait.getClass())) - runnables.remove(traits.get(trait.getClass())); - } - if (trait instanceof Listener) { - Bukkit.getPluginManager().registerEvents((Listener) trait, trait.getPlugin()); - } - - Map, Trait> map = traits.get(trait.getPlugin()); - if (map == null) - map = new HashMap, Trait>(); - map.put(trait.getClass(), trait); - traits.put(trait.getPlugin(), map); - } - @Override public void chat(Player player, String message) { Messaging.sendWithNPC(player, Setting.CHAT_PREFIX.asString() + message, this); @@ -115,16 +84,8 @@ public abstract class CitizensNPC extends AbstractNPC { } @Override - public T getTrait(Class clazz) { - Trait trait = null; - for (Plugin plugin : traits.keySet()) - if (traits.get(plugin).containsKey(clazz)) - trait = traits.get(plugin).get(clazz); - if (trait == null) - trait = traitManager.getTrait(clazz, this); - - addTrait(trait); - return trait != null ? clazz.cast(trait) : null; + public Trait getTraitFor(Class clazz) { + return traitManager.getTrait(clazz, this); } @Override @@ -187,10 +148,8 @@ public abstract class CitizensNPC extends AbstractNPC { } // Save all existing traits - for (Map, Trait> map : traits.values()) { - for (Trait trait : map.values()) { - trait.save(root.getRelative("traits." + trait.getName())); - } + for (Trait trait : traits.values()) { + trait.save(root.getRelative("traits." + trait.getName())); } } @@ -222,9 +181,8 @@ public abstract class CitizensNPC extends AbstractNPC { getTrait(Spawned.class).setSpawned(true); // Modify NPC using traits after the entity has been created - for (Plugin plugin : traits.keySet()) - for (Trait trait : getTraits(plugin)) - trait.onNPCSpawn(); + for (Trait trait : traits.values()) + trait.onNPCSpawn(); return true; } diff --git a/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java b/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java index bc30599e8..963695515 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java @@ -16,6 +16,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.trait.Age; +import net.citizensnpcs.trait.Behaviour; import net.citizensnpcs.trait.Controllable; import net.citizensnpcs.trait.CurrentLocation; import net.citizensnpcs.trait.LookClose; @@ -52,6 +53,7 @@ public class CitizensTraitManager implements TraitManager { registerTrait(new TraitFactory(Waypoints.class).withName("waypoints").withPlugin(plugin)); registerTrait(new TraitFactory(WoolColor.class).withName("woolcolor").withPlugin(plugin)); registerTrait(new TraitFactory(Controllable.class).withName("controllable").withPlugin(plugin)); + registerTrait(new TraitFactory(Behaviour.class).withName("behaviour").withPlugin(plugin)); } @SuppressWarnings("unchecked") diff --git a/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java b/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java index 4bf2cc98d..a32c9ecc2 100644 --- a/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java +++ b/src/main/java/net/citizensnpcs/npc/ai/CitizensAI.java @@ -20,6 +20,7 @@ public class CitizensAI implements AI { private PathStrategy executing; private final List executingGoals = Lists.newArrayList(); private final List goals = Lists.newArrayList(); + private List toRemove = null; private final CitizensNPC npc; private boolean paused; @@ -82,6 +83,13 @@ public class CitizensAI implements AI { } } + @Override + public void removeGoal(Goal goal) { + if (toRemove == null) + toRemove = Lists.newArrayList(); + toRemove.add(goal); + } + public void resume() { paused = false; } @@ -136,7 +144,7 @@ public class CitizensAI implements AI { } } } - + removeGoals(); for (int i = 0; i < goals.size(); ++i) { GoalEntry entry = goals.get(i); boolean executing = executingGoals.contains(entry); @@ -157,11 +165,32 @@ public class CitizensAI implements AI { } } - private class GoalEntry implements Comparable { + private void removeGoals() { + if (toRemove == null) + return; + for (Goal goal : toRemove) { + for (int i = 0; i < executingGoals.size(); ++i) { + GoalEntry entry = executingGoals.get(i); + if (entry.goal.equals(goal)) { + entry.goal.reset(); + executingGoals.remove(i); + } + } + for (int i = 0; i < goals.size(); ++i) { + GoalEntry entry = goals.get(i); + if (entry.goal.equals(goal)) + goals.remove(i); + } + } + + toRemove = null; + } + + public static class GoalEntry implements Comparable { final Goal goal; final int priority; - GoalEntry(int priority, Goal goal) { + public GoalEntry(int priority, Goal goal) { this.priority = priority; this.goal = goal; } @@ -170,5 +199,43 @@ public class CitizensAI implements AI { public int compareTo(GoalEntry o) { return o.priority > priority ? 1 : o.priority < priority ? -1 : 0; } + + public Goal getGoal() { + return goal; + } + + public int getPriority() { + return priority; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((goal == null) ? 0 : goal.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GoalEntry other = (GoalEntry) obj; + if (goal == null) { + if (other.goal != null) { + return false; + } + } else if (!goal.equals(other.goal)) { + return false; + } + return true; + } } } \ No newline at end of file diff --git a/src/main/java/net/citizensnpcs/trait/Behaviour.java b/src/main/java/net/citizensnpcs/trait/Behaviour.java new file mode 100644 index 000000000..753a43f1d --- /dev/null +++ b/src/main/java/net/citizensnpcs/trait/Behaviour.java @@ -0,0 +1,107 @@ +package net.citizensnpcs.trait; + +import java.io.File; +import java.util.List; + +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.ai.Goal; +import net.citizensnpcs.api.exception.NPCLoadException; +import net.citizensnpcs.api.npc.NPC; +import net.citizensnpcs.api.scripting.CompileCallback; +import net.citizensnpcs.api.scripting.ScriptFactory; +import net.citizensnpcs.api.trait.Trait; +import net.citizensnpcs.api.util.DataKey; +import net.citizensnpcs.npc.ai.CitizensAI.GoalEntry; + +import org.apache.commons.lang.Validate; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; + +public class Behaviour extends Trait { + private final List scripts = Lists.newArrayList(); + private final List addedGoals = Lists.newArrayList(); + private final File rootFolder = new File(CitizensAPI.getScriptFolder(), "behaviours"); + private final NPC npc; + + public Behaviour(NPC npc) { + this.npc = npc; + } + + @Override + public void load(DataKey key) throws NPCLoadException { + reset(); + if (!key.keyExists("scripts")) + return; + String scripts = key.getString("scripts"); + addScripts(Splitter.on(",").split(scripts)); + } + + private void reset() { + removeGoals(); + scripts.clear(); + addedGoals.clear(); + } + + private void removeGoals() { + for (GoalEntry entry : addedGoals) { + npc.getAI().removeGoal(entry.getGoal()); + } + } + + @Override + public void onNPCSpawn() { + for (GoalEntry entry : addedGoals) { + npc.getAI().addGoal(entry.getPriority(), entry.getGoal()); + } + } + + @Override + public void onRemove() { + removeGoals(); + } + + public void addScripts(Iterable scripts) { + BehaviourCallback callback = new BehaviourCallback(new Goals()); + for (String script : scripts) { + File file = new File(rootFolder, script); + if (!file.exists()) + continue; + CitizensAPI.getScriptCompiler().compile(file).withCallback(callback).begin(); + this.scripts.add(file); + } + List added = callback.goals.goals; + for (GoalEntry entry : added) { + npc.getAI().addGoal(entry.getPriority(), entry.getGoal()); + } + addedGoals.addAll(added); + } + + @Override + public void save(DataKey key) { + key.setString("scripts", Joiner.on(",").join(scripts)); + } + + private class BehaviourCallback implements CompileCallback { + private final Goals goals; + + private BehaviourCallback(Goals goals) { + this.goals = goals; + } + + @Override + public void onScriptCompiled(ScriptFactory script) { + script.newInstance().invoke("addGoals", goals, npc); + } + } + + public static class Goals { + private final List goals = Lists.newArrayList(); + + public void addGoal(int priority, Goal goal) { + Validate.notNull(goal); + this.goals.add(new GoalEntry(priority, goal)); + } + } +}