mirror of
https://github.com/CitizensDev/Citizens2.git
synced 2024-11-28 05:35:45 +01:00
Refactoring, Behaviour trait (scripted AI)
This commit is contained in:
parent
4b1a0bbeae
commit
d17e49ee74
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
107
src/main/java/net/citizensnpcs/trait/Behaviour.java
Normal file
107
src/main/java/net/citizensnpcs/trait/Behaviour.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user