Refactoring, Behaviour trait (scripted AI)

This commit is contained in:
fullwall 2012-04-30 21:38:18 +08:00
parent 4b1a0bbeae
commit d17e49ee74
8 changed files with 215 additions and 66 deletions

View File

@ -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);

View File

@ -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();
}

View File

@ -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, "<a>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<String> 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());

View File

@ -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, "<a>Done.");

View File

@ -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<Class<? extends Trait>, Trait> map = traits.get(trait.getPlugin());
if (map == null)
map = new HashMap<Class<? extends Trait>, 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 extends Trait> T getTrait(Class<T> 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<? extends Trait> clazz) {
return traitManager.getTrait(clazz, this);
}
@Override
@ -187,10 +148,8 @@ public abstract class CitizensNPC extends AbstractNPC {
}
// Save all existing traits
for (Map<Class<? extends Trait>, 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;
}

View File

@ -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")

View File

@ -20,6 +20,7 @@ public class CitizensAI implements AI {
private PathStrategy executing;
private final List<GoalEntry> executingGoals = Lists.newArrayList();
private final List<GoalEntry> goals = Lists.newArrayList();
private List<Goal> 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<GoalEntry> {
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<GoalEntry> {
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;
}
}
}

View File

@ -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<File> scripts = Lists.newArrayList();
private final List<GoalEntry> 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<String> 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<GoalEntry> 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<GoalEntry> goals = Lists.newArrayList();
public void addGoal(int priority, Goal goal) {
Validate.notNull(goal);
this.goals.add(new GoalEntry(priority, goal));
}
}
}