Merge Speech branch into master for testing. Please backup your current saves and config before using this build.

This commit is contained in:
Jeremy Schroeder 2012-12-26 15:49:53 -05:00
parent 81d00792d1
commit b8878eda5d
9 changed files with 1457 additions and 1102 deletions

View File

@ -8,6 +8,7 @@ import java.util.Iterator;
import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.CitizensPlugin; import net.citizensnpcs.api.CitizensPlugin;
import net.citizensnpcs.api.ai.speech.SpeechFactory;
import net.citizensnpcs.api.event.CitizensDisableEvent; import net.citizensnpcs.api.event.CitizensDisableEvent;
import net.citizensnpcs.api.event.CitizensEnableEvent; import net.citizensnpcs.api.event.CitizensEnableEvent;
import net.citizensnpcs.api.event.CitizensReloadEvent; import net.citizensnpcs.api.event.CitizensReloadEvent;
@ -36,6 +37,8 @@ import net.citizensnpcs.editor.Editor;
import net.citizensnpcs.npc.CitizensNPCRegistry; import net.citizensnpcs.npc.CitizensNPCRegistry;
import net.citizensnpcs.npc.CitizensTraitFactory; import net.citizensnpcs.npc.CitizensTraitFactory;
import net.citizensnpcs.npc.NPCSelector; import net.citizensnpcs.npc.NPCSelector;
import net.citizensnpcs.npc.ai.speech.Chat;
import net.citizensnpcs.npc.ai.speech.CitizensSpeechFactory;
import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.Messaging;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -63,6 +66,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
private NPCDataStore saves; private NPCDataStore saves;
private NPCSelector selector; private NPCSelector selector;
private CitizensTraitFactory traitFactory; private CitizensTraitFactory traitFactory;
private CitizensSpeechFactory speechFactory;
private void despawnNPCs() { private void despawnNPCs() {
Iterator<NPC> itr = npcRegistry.iterator(); Iterator<NPC> itr = npcRegistry.iterator();
@ -151,6 +155,11 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
return traitFactory; return traitFactory;
} }
@Override
public SpeechFactory getSpeechFactory() {
return speechFactory;
}
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String cmdName, String[] args) { public boolean onCommand(CommandSender sender, Command command, String cmdName, String[] args) {
String modifier = args.length > 0 ? args[0] : ""; String modifier = args.length > 0 ? args[0] : "";
@ -206,6 +215,8 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
npcRegistry = new CitizensNPCRegistry(saves); npcRegistry = new CitizensNPCRegistry(saves);
traitFactory = new CitizensTraitFactory(); traitFactory = new CitizensTraitFactory();
selector = new NPCSelector(this); selector = new NPCSelector(this);
speechFactory = new CitizensSpeechFactory();
speechFactory.register(Chat.class, "chat");
getServer().getPluginManager().registerEvents(new EventListen(), this); getServer().getPluginManager().registerEvents(new EventListen(), this);
@ -348,4 +359,5 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
} }
private static final String COMPATIBLE_MC_VERSION = "1.4.6"; private static final String COMPATIBLE_MC_VERSION = "1.4.6";
} }

View File

@ -43,7 +43,14 @@ public class Settings {
} }
public enum Setting { public enum Setting {
CHAT_PREFIX("npc.chat.prefix", "[<npc>]: "), CHAT_FORMAT("npc.chat.format.no-targets", "[<npc>]: <text>"),
CHAT_FORMAT_TO_TARGET("npc.chat.format.to-target", "[<npc>] -> You: <text>"),
CHAT_FORMAT_TO_BYSTANDERS("npc.chat.format.with-target-to-bystanders", "[<npc>] -> [<target>]: <text>"),
CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS("npc.chat.format.with-targets-to-bystanders", "[<npc>] -> [<targets>]: <text>"),
CHAT_RANGE("npc.chat.options.range", 5),
CHAT_BYSTANDERS_HEAR_TARGETED_CHAT("npc.chat.options.bystanders-hear-targeted-chat", true),
CHAT_MAX_NUMBER_OF_TARGETS("npc.chat.options.max-number-of-targets-to-show", 2),
CHAT_MULTIPLE_TARGETS_FORMAT("npc.chat.options.multiple-targets-format", "<target>|, <target>| & <target>| & others"),
DATABASE_DRIVER("storage.database.driver", ""), DATABASE_DRIVER("storage.database.driver", ""),
DATABASE_PASSWORD("storage.database.password", ""), DATABASE_PASSWORD("storage.database.password", ""),
DATABASE_URL("storage.database.url", ""), DATABASE_URL("storage.database.url", ""),

View File

@ -8,6 +8,7 @@ import java.util.List;
import net.citizensnpcs.Citizens; import net.citizensnpcs.Citizens;
import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.ai.speech.SpeechContext;
import net.citizensnpcs.api.event.PlayerCreateNPCEvent; import net.citizensnpcs.api.event.PlayerCreateNPCEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.npc.NPCRegistry; import net.citizensnpcs.api.npc.NPCRegistry;
@ -15,6 +16,7 @@ import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.trait.trait.MobType;
import net.citizensnpcs.api.trait.trait.Owner; import net.citizensnpcs.api.trait.trait.Owner;
import net.citizensnpcs.api.trait.trait.Spawned; import net.citizensnpcs.api.trait.trait.Spawned;
import net.citizensnpcs.api.trait.trait.Speech;
import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.api.util.MemoryDataKey; import net.citizensnpcs.api.util.MemoryDataKey;
import net.citizensnpcs.command.Command; import net.citizensnpcs.command.Command;
@ -959,6 +961,45 @@ public class NPCCommands {
} }
} }
@Command(
aliases = { "npc" },
usage = "speak message to speak --target npcid|player_name --type vocal_type",
desc = "Uses the NPCs SpeechController to talk",
modifiers = { "speak" },
min = 2,
permission = "npc.speak")
public void speak(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
String type = npc.getTrait(Speech.class).getDefaultVocalChord();
String message = StringHelper.parseColors(args.getJoinedStrings(1));
if (message.length() <= 0) {
Messaging.send(sender, "Default Vocal Chord for " + npc.getName() + ": " + npc.getTrait(Speech.class).getDefaultVocalChord());
return;
}
SpeechContext context = new SpeechContext(message);
if (args.hasValueFlag("target")) {
if (args.getFlag("target").matches("\\d+")) {
NPC target = CitizensAPI.getNPCRegistry().getById(Integer.valueOf(args.getFlag("target")));
if ( target != null)
context.addRecipient(target.getBukkitEntity());
} else {
Player player = Bukkit.getPlayer(args.getFlag("target"));
if (player != null)
context.addRecipient(player);
}
}
if (args.hasValueFlag("type")) {
if (CitizensAPI.getSpeechFactory().isRegistered(args.getFlag("type")))
type = args.getFlag("type");
}
npc.getDefaultSpeechController().speak(context, type);
}
@Command( @Command(
aliases = { "npc" }, aliases = { "npc" },
usage = "speed [speed]", usage = "speed [speed]",

View File

@ -243,4 +243,5 @@ public class CitizensNPC extends AbstractNPC {
} }
private static final String NPC_METADATA_MARKER = "NPC"; private static final String NPC_METADATA_MARKER = "NPC";
} }

View File

@ -15,6 +15,7 @@ import net.citizensnpcs.api.trait.trait.Inventory;
import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.trait.trait.MobType;
import net.citizensnpcs.api.trait.trait.Owner; import net.citizensnpcs.api.trait.trait.Owner;
import net.citizensnpcs.api.trait.trait.Spawned; import net.citizensnpcs.api.trait.trait.Spawned;
import net.citizensnpcs.api.trait.trait.Speech;
import net.citizensnpcs.trait.Age; import net.citizensnpcs.trait.Age;
import net.citizensnpcs.trait.Anchors; import net.citizensnpcs.trait.Anchors;
import net.citizensnpcs.trait.Behaviour; import net.citizensnpcs.trait.Behaviour;
@ -60,6 +61,7 @@ public class CitizensTraitFactory implements TraitFactory {
registerTrait(TraitInfo.create(NPCSkeletonType.class).withName("skeletontype")); registerTrait(TraitInfo.create(NPCSkeletonType.class).withName("skeletontype"));
registerTrait(TraitInfo.create(SlimeSize.class).withName("slimesize")); registerTrait(TraitInfo.create(SlimeSize.class).withName("slimesize"));
registerTrait(TraitInfo.create(Spawned.class).withName("spawned")); registerTrait(TraitInfo.create(Spawned.class).withName("spawned"));
registerTrait(TraitInfo.create(Speech.class).withName("speech"));
registerTrait(TraitInfo.create(Text.class).withName("text")); registerTrait(TraitInfo.create(Text.class).withName("text"));
registerTrait(TraitInfo.create(MobType.class).withName("type")); registerTrait(TraitInfo.create(MobType.class).withName("type"));
registerTrait(TraitInfo.create(Waypoints.class).withName("waypoints")); registerTrait(TraitInfo.create(Waypoints.class).withName("waypoints"));

View File

@ -0,0 +1,117 @@
package net.citizensnpcs.npc.ai.speech;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.ai.speech.Talkable;
import net.citizensnpcs.api.ai.speech.SpeechContext;
import net.citizensnpcs.api.ai.speech.VocalChord;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.util.Messaging;
public class Chat implements VocalChord {
public final String VOCAL_CHORD_NAME = "chat";
@Override
public String getName() {
return VOCAL_CHORD_NAME;
}
@Override
public void talk(SpeechContext context) {
// Check valid talker
if (context.getTalker() == null) return;
NPC npc = CitizensAPI.getNPCRegistry().getNPC(context.getTalker().getEntity());
if (npc == null) return;
// If no recipients, chat to the world with CHAT_FORMAT and CHAT_RANGE settings
if (!context.hasRecipients()) {
String text = Setting.CHAT_FORMAT.asString().replace("<npc>", npc.getName()).replace("<text>", context.getMessage());
talkToBystanders(npc, text, context);
return;
}
// Assumed recipients at this point
else if (context.size() <= 1) { // One recipient
String text = Setting.CHAT_FORMAT_TO_TARGET.asString().replace("<npc>", npc.getName()).replace("<text>", context.getMessage());
String targetName = "";
// For each recipient
for (Talkable entity : context) {
entity.talkTo(context, text, this);
targetName = entity.getName();
}
// Check if bystanders hear targeted chat
if (!Setting.CHAT_BYSTANDERS_HEAR_TARGETED_CHAT.asBoolean()) return;
// Format message with config setting and send to bystanders
String bystanderText = Setting.CHAT_FORMAT_TO_BYSTANDERS.asString().replace("<npc>", npc.getName()).replace("<target>", targetName).replace("<text>", context.getMessage());
talkToBystanders(npc, bystanderText, context);
return;
}
else { // Multiple recipients
String text = Setting.CHAT_FORMAT_TO_TARGET.asString().replace("<npc>", npc.getName()).replace("<text>", context.getMessage());
List<String> targetNames = Collections.emptyList();
// Talk to each recipient
for (Talkable entity : context) {
entity.talkTo(context, text, this);
targetNames.add(entity.getName());
}
if (!Setting.CHAT_BYSTANDERS_HEAR_TARGETED_CHAT.asBoolean()) return;
String targets = "";
int max = Setting.CHAT_MAX_NUMBER_OF_TARGETS.asInt();
String[] format = Setting.CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS.asString().split("\\|");
if (format.length != 4) Messaging.log(Level.WARNING, "npc.chat.format.with-target-to-bystanders invalid!");
if (max == 1) {
targets = format[0].replace("<npc>", targetNames.get(0)) + format[3];
}
else if (max == 2 || targetNames.size() == 2) {
if (targetNames.size() == 2)
targets = format[0].replace("<npc>", targetNames.get(0)) + format[2].replace("<npc>", targetNames.get(1));
else
targets = format[0].replace("<npc>", targetNames.get(0)) + format[1].replace("<npc>", targetNames.get(1)) + format[3];
}
else if (max >= 3) {
targets = format[0].replace("<npc>", targetNames.get(0));
int x = 1;
for (x = 1; x < max - 1; x++) {
if (targetNames.size() - 1 == x) break;
targets = targets + format[1].replace("<npc>", targetNames.get(x));
}
if (targetNames.size() == max)
targets = targets + format[2].replace("<npc>", targetNames.get(x));
else targets = targets + format[3];
}
String bystanderText = Setting.CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS.asString().replace("<npc>", npc.getName()).replace("<targets>", targets).replace("<text>", context.getMessage());
talkToBystanders(npc, bystanderText, context);
}
}
private void talkToBystanders(NPC npc, String text, SpeechContext context) {
// Get list of nearby entities
List<Entity> bystanderEntities = npc.getBukkitEntity().getNearbyEntities(Setting.CHAT_RANGE.asDouble(), Setting.CHAT_RANGE.asDouble(), Setting.CHAT_RANGE.asDouble());
for (Entity bystander : bystanderEntities)
// Continue if a LivingEntity, which is compatible with TalkableEntity
if (bystander instanceof LivingEntity) {
// Exclude targeted recipients
if (context.hasRecipients()) {
for (Talkable target : context)
if (target.getEntity() == bystander) continue;
else new TalkableEntity((LivingEntity) bystander).talkNear(context, text, this);
} else
// Found a nearby LivingEntity, make it Talkable and talkNear it
new TalkableEntity((LivingEntity) bystander).talkNear(context, text, this);
}
}
}

View File

@ -0,0 +1,79 @@
package net.citizensnpcs.npc.ai.speech;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.bukkit.entity.LivingEntity;
import com.google.common.base.Preconditions;
import net.citizensnpcs.api.ai.speech.SpeechFactory;
import net.citizensnpcs.api.ai.speech.Talkable;
import net.citizensnpcs.api.ai.speech.VocalChord;
public class CitizensSpeechFactory implements SpeechFactory {
Map<String, Class<? extends VocalChord>> registered = new HashMap<String, Class <? extends VocalChord>>();
@Override
public VocalChord getVocalChord(Class<? extends VocalChord> clazz) {
// Return a new instance of the VocalChord specified
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@Override
public VocalChord getVocalChord(String name) {
// Check if VocalChord name is a registered type
if (isRegistered(name))
// Return a new instance of the VocalChord specified
try {
return registered.get(name.toLowerCase()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@Override
public String getVocalChordName(Class<? extends VocalChord> clazz) {
// Get the name of a VocalChord class that has been registered
for (Entry<String, Class<? extends VocalChord>> vocalChord : registered.entrySet())
if (vocalChord.getValue() == clazz) return vocalChord.getKey();
return null;
}
@Override
public boolean isRegistered(String name) {
if (registered.containsKey(name.toLowerCase())) return true;
else return false;
}
@Override
public void register(Class<? extends VocalChord> clazz, String name) {
Preconditions.checkNotNull(name, "info cannot be null");
Preconditions.checkNotNull(clazz, "vocalchord cannot be null");
if (registered.containsKey(name.toLowerCase()))
throw new IllegalArgumentException("vocalchord name already registered");
registered.put(name.toLowerCase(), clazz);
}
@Override
public Talkable newTalkableEntity(LivingEntity entity) {
if (entity == null) return null;
return new TalkableEntity(entity);
}
}

View File

@ -0,0 +1,91 @@
package net.citizensnpcs.npc.ai.speech;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.ai.speech.SpeechContext;
import net.citizensnpcs.api.ai.speech.Talkable;
import net.citizensnpcs.api.ai.speech.VocalChord;
import net.citizensnpcs.api.ai.speech.event.SpeechBystanderEvent;
import net.citizensnpcs.api.ai.speech.event.SpeechTargetedEvent;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.util.Messaging;
import org.bukkit.Bukkit;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
public class TalkableEntity implements Talkable {
LivingEntity entity;
public TalkableEntity(LivingEntity entity) {
this.entity = entity;
}
public TalkableEntity(NPC npc) {
entity = npc.getBukkitEntity();
}
public TalkableEntity(Player player) {
entity = (LivingEntity) player;
}
/**
* Used to compare a LivingEntity to this TalkableEntity
*
* @return
* 0 if the Entities are the same, 1 if they are not, -1 if
* the object compared is not a valid LivingEntity
*/
@Override
public int compareTo(Object o) {
// If not living entity, return -1
if (!(o instanceof LivingEntity)) return -1;
// If NPC and matches, return 0
else if (CitizensAPI.getNPCRegistry().isNPC((LivingEntity) o)
&& CitizensAPI.getNPCRegistry().isNPC((LivingEntity) entity)
&& CitizensAPI.getNPCRegistry().getNPC((LivingEntity) o).getId() ==
CitizensAPI.getNPCRegistry().getNPC((LivingEntity) entity).getId())
return 0;
else if ((LivingEntity) o == entity) return 0;
// Not a match, return 1
else return 1;
}
@Override
public LivingEntity getEntity() {
return entity;
}
@Override
public String getName() {
if (CitizensAPI.getNPCRegistry().isNPC(entity))
return CitizensAPI.getNPCRegistry().getNPC(entity).getName();
else if (entity instanceof Player)
return ((Player) entity).getName();
else
return entity.getType().name().replace("_", " ");
}
private void talk(String message) {
if (entity instanceof Player
&& !CitizensAPI.getNPCRegistry().isNPC(entity))
Messaging.send((Player) entity, message);
}
@Override
public void talkNear(SpeechContext context, String text, VocalChord vocalChord) {
SpeechBystanderEvent event = new SpeechBystanderEvent(this, context, text, vocalChord);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) return;
else talk(event.getMessage());
}
@Override
public void talkTo(SpeechContext context, String text, VocalChord vocalChord) {
SpeechTargetedEvent event = new SpeechTargetedEvent(this, context, text, vocalChord);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) return;
else talk(event.getMessage());
}
}

View File

@ -10,11 +10,14 @@ import java.util.concurrent.TimeUnit;
import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.ai.speech.SpeechContext;
import net.citizensnpcs.api.ai.speech.Talkable;
import net.citizensnpcs.api.event.NPCRightClickEvent; import net.citizensnpcs.api.event.NPCRightClickEvent;
import net.citizensnpcs.api.exception.NPCLoadException; import net.citizensnpcs.api.exception.NPCLoadException;
import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.editor.Editor; import net.citizensnpcs.editor.Editor;
import net.citizensnpcs.npc.ai.speech.TalkableEntity;
import net.citizensnpcs.trait.Toggleable; import net.citizensnpcs.trait.Toggleable;
import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.Messaging;
@ -189,7 +192,9 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
currentIndex = 0; currentIndex = 0;
index = currentIndex++; index = currentIndex++;
} }
Messaging.sendWithNPC(player, Setting.CHAT_PREFIX.asString() + text.get(index), npc);
npc.getDefaultSpeechController().speak(new SpeechContext(text.get(index), player));
// Messaging.sendWithNPC(player, Setting.CHAT_PREFIX.asString() + text.get(index), npc);
return true; return true;
} }