diff --git a/src/java/com/threerings/crowd/chat/client/ChatDirector.java b/src/java/com/threerings/crowd/chat/client/ChatDirector.java
index bde383678..be243f602 100644
--- a/src/java/com/threerings/crowd/chat/client/ChatDirector.java
+++ b/src/java/com/threerings/crowd/chat/client/ChatDirector.java
@@ -21,9 +21,17 @@
package com.threerings.crowd.chat.client;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.samskivert.util.Collections;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.ObserverList;
import com.samskivert.util.ResultListener;
@@ -85,11 +93,52 @@ public class ChatDirector extends BasicDirector
public boolean isChatterValid (Name username);
}
+ /**
+ * Used to implement a slash command (e.g. /who).
+ */
+ public static abstract class CommandHandler
+ {
+ /**
+ * Handles the specified chat command.
+ *
+ * @param speakSvc an optional SpeakService object representing
+ * the object to send the chat message on.
+ * @param command the slash command that was used to invoke this
+ * handler (e.g. /tell).
+ * @param args the arguments provided along with the command (e.g.
+ * Bob hello) or null if no arguments
+ * were supplied.
+ * @param history an in/out parameter that allows the command to
+ * modify the text that will be appended to the chat history. If
+ * this is set to null, nothing will be appended.
+ *
+ * @return an untranslated string that will be reported to the
+ * chat box to convey an error response to the user, or {@link
+ * ChatCodes#SUCCESS}.
+ */
+ public abstract String handleCommand (
+ SpeakService speakSvc, String command, String args,
+ String[] history);
+
+ /**
+ * Returns true if this user should have access to this chat
+ * command.
+ */
+ public boolean checkAccess (BodyObject user)
+ {
+ return true;
+ }
+ }
+
/**
* Creates a chat director and initializes it with the supplied
* context. The chat director will register itself as a location
* observer so that it can automatically process place constrained
* chat.
+ *
+ * @param msgmgr the message manager via which we do our translations.
+ * @param bundle the message bundle from which we obtain our
+ * chat-related translation strings.
*/
public ChatDirector (CrowdContext ctx, MessageManager msgmgr, String bundle)
{
@@ -102,6 +151,14 @@ public class ChatDirector extends BasicDirector
// register ourselves as a location observer
_ctx.getLocationDirector().addLocationObserver(this);
+
+ // register our default chat handlers
+ MessageBundle msg = _msgmgr.getBundle(_bundle);
+ registerCommandHandler(msg, "help", new HelpHandler());
+ registerCommandHandler(msg, "clear", new ClearHandler());
+ registerCommandHandler(msg, "speak", new SpeakHandler());
+ registerCommandHandler(msg, "emote", new EmoteHandler());
+ registerCommandHandler(msg, "think", new ThinkHandler());
}
/**
@@ -169,41 +226,54 @@ public class ChatDirector extends BasicDirector
}
/**
- * Adds a chatter to our list of recent chatters.
+ * Registers a chat command handler.
+ *
+ * @param msg the message bundle via which the slash command will be
+ * translated (as c.command). If no translation
+ * exists the command will be /command.
+ * @param command the name of the command that will be used to invoke
+ * this handler (e.g. tell if the command will be invoked
+ * as /tell).
+ * @param handler the chat command handler itself.
*/
- protected void addChatter (Name name)
+ public void registerCommandHandler (
+ MessageBundle msg, String command, CommandHandler handler)
{
- // check to see if the chatter validator approves..
- if ((_chatterValidator != null) &&
- (!_chatterValidator.isChatterValid(name))) {
- return;
- }
-
- boolean wasthere = _chatters.remove(name);
- _chatters.addFirst(name);
-
- if (!wasthere) {
- if (_chatters.size() > MAX_CHATTERS) {
- _chatters.removeLast();
+ String key = "c." + command;
+ if (msg.exists(key)) {
+ StringTokenizer st = new StringTokenizer(msg.get(key));
+ while (st.hasMoreTokens()) {
+ _handlers.put(st.nextToken(), handler);
}
-
- notifyChatterObservers();
+ } else {
+ // fall back to just using the English command
+ _handlers.put(command, handler);
}
}
/**
- * Notifies all registered {@link ChatterObserver}s that the list of
- * chatters has changed.
+ * Return the current size of the history.
*/
- protected void notifyChatterObservers ()
+ public int getCommandHistorySize ()
{
- _chatterObservers.apply(new ObserverList.ObserverOp() {
- public boolean apply (Object observer) {
- ((ChatterObserver)observer).chattersUpdated(
- _chatters.listIterator());
- return true;
- }
- });
+ return _history.size();
+ }
+
+ /**
+ * Get the chat history entry at the specified index,
+ * with 0 being the oldest.
+ */
+ public String getCommandHistory (int index)
+ {
+ return (String)_history.get(index);
+ }
+
+ /**
+ * Clear the chat command history.
+ */
+ public void clearCommandHistory ()
+ {
+ _history.clear();
}
/**
@@ -218,7 +288,7 @@ public class ChatDirector extends BasicDirector
}
});
}
-
+
/**
* Display a system INFO message as if it had come from the server.
* The localtype of the message will be PLACE_CHAT_TYPE.
@@ -268,23 +338,6 @@ public class ChatDirector extends BasicDirector
bundle, message, SystemMessage.ATTENTION, PLACE_CHAT_TYPE);
}
- /**
- * Display the specified system message as if it had come from the server.
- */
- protected void displaySystem (
- String bundle, String message, byte attLevel, String localtype)
- {
- // nothing should be untranslated, so pass the default bundle if need
- // be.
- if (bundle == null) {
- bundle = _bundle;
- }
- SystemMessage msg = new SystemMessage();
- msg.attentionLevel = attLevel;
- msg.setClientInfo(xlate(bundle, message), localtype);
- dispatchMessage(msg);
- }
-
/**
* Dispatches the provided message to our chat displays.
*/
@@ -293,29 +346,84 @@ public class ChatDirector extends BasicDirector
_displayMessageOp.setMessage(message);
_displays.apply(_displayMessageOp);
}
-
- /**
- * Dispatches a {@link #requestSpeak(SpeakService,String,byte)} on the
- * place object that we currently occupy.
- */
- public void requestSpeak (String message)
- {
- requestSpeak(message, DEFAULT_MODE);
- }
/**
- * Dispatches a {@link #requestSpeak(SpeakService,String,byte)} on the
- * place object that we currently occupy.
+ * Parses and delivers the supplied chat message. Slash command
+ * processing and mogrification are performed and the message is added
+ * to the chat history if appropriate.
+ *
+ * @param speakSvc the SpeakService representing the target dobj of
+ * the speak or null if we should speak in the "default" way.
+ * @param text the text to be parsed and sent.
+ * @param record if text is a command, should it be added to the history?
+ *
+ * @return ChatCodes#SUCCESS if the message was parsed
+ * and sent correctly, a translatable error string if there was some
+ * problem.
*/
- public void requestSpeak (String message, byte mode)
+ public String requestChat (
+ SpeakService speakSvc, String text, boolean record)
{
- // make sure we're currently in a place
- if (_place == null) {
- return;
+ if (text.startsWith("/")) {
+ // split the text up into a command and arguments
+ String command = text.substring(1).toLowerCase();
+ String[] hist = new String[1];
+ String args = "";
+ int sidx = text.indexOf(" ");
+ if (sidx != -1) {
+ command = text.substring(1, sidx).toLowerCase();
+ args = text.substring(sidx+1).trim();
+ }
+
+ HashMap possibleCommands = getCommandHandlers(command);
+ switch (possibleCommands.size()) {
+ case 0:
+ StringTokenizer tok = new StringTokenizer(text);
+ return MessageBundle.tcompose(
+ "m.unknown_command", tok.nextToken());
+
+ case 1:
+ Iterator itr = possibleCommands.entrySet().iterator();
+ Map.Entry entry = (Map.Entry) itr.next();
+ String cmdName = (String) entry.getKey();
+ CommandHandler cmd = (CommandHandler) entry.getValue();
+
+ String result = cmd.handleCommand(speakSvc, cmdName, args, hist);
+ if (!result.equals(ChatCodes.SUCCESS)) {
+ return result;
+ }
+
+ if (record) {
+ // get the final history-ready command string
+ hist[0] = "/" + ((hist[0] == null) ? command : hist[0]);
+
+ // remove from history if it was present and
+ // add it to the end
+ addToHistory(hist[0]);
+ }
+
+ return result;
+
+ default:
+ String alternativeCommands = "";
+ itr = Collections.getSortedIterator(possibleCommands.keySet());
+ while (itr.hasNext()) {
+ cmdName = (String)itr.next();
+ alternativeCommands += " /" + cmdName;
+ }
+ return MessageBundle.tcompose(
+ "m.unspecific_command", alternativeCommands);
+ }
}
- // dispatch a speak request on the active place object
- requestSpeak(_place.speakService, message, mode);
+ // if not a command then just speak
+ String message = text.trim();
+ if (StringUtil.blank(message)) {
+ // report silent failure for now
+ return ChatCodes.SUCCESS;
+ }
+
+ return deliverChat(speakSvc, message, ChatCodes.DEFAULT_MODE);
}
/**
@@ -326,7 +434,7 @@ public class ChatDirector extends BasicDirector
* possibly vetoed) before being dispatched.
*
* @param speakService the speak service to use when generating the
- * speak request.
+ * speak request or null if we should speak in the current "place".
* @param message the contents of the speak message.
* @param mode a speech mode that will be interpreted by the {@link
* ChatDisplay} implementations that eventually display this speak
@@ -335,6 +443,13 @@ public class ChatDirector extends BasicDirector
public void requestSpeak (
SpeakService speakService, String message, byte mode)
{
+ if (speakService == null) {
+ if (_place == null) {
+ return;
+ }
+ speakService = _place.speakService;
+ }
+
// make sure they can say what they want to say
message = filter(message, null, true);
if (message == null) {
@@ -483,11 +598,22 @@ public class ChatDirector extends BasicDirector
_auxes.remove(source.getOid());
}
- // documentation inherited from interface
- protected void fetchServices (Client client)
+ /**
+ * Run a message through all the currently registered filters.
+ */
+ public String filter (String msg, Name otherUser, boolean outgoing)
{
- // get a handle on our chat service
- _cservice = (ChatService)client.requireService(ChatService.class);
+ _filterMessageOp.setMessage(msg, otherUser, outgoing);
+ _filters.apply(_filterMessageOp);
+ return _filterMessageOp.getMessage();
+ }
+
+ /**
+ * Runs the supplied message through the various chat mogrifications.
+ */
+ public String mogrifyChat (String text)
+ {
+ return mogrifyChat(text, false, true);
}
// documentation inherited
@@ -565,42 +691,6 @@ public class ChatDirector extends BasicDirector
}
}
- /**
- * Translates the specified message using the specified bundle.
- */
- protected String xlate (String bundle, String message)
- {
- if (bundle != null && _msgmgr != null) {
- MessageBundle msgb = _msgmgr.getBundle(bundle);
- if (msgb == null) {
- Log.warning(
- "No message bundle available to translate message " +
- "[bundle=" + bundle + ", message=" + message + "].");
- } else {
- message = msgb.xlate(message);
- }
- }
- return message;
- }
-
- /**
- * Looks up and returns the message type associated with the specified
- * oid.
- */
- protected String getLocalType (int oid)
- {
- String type = (String)_auxes.get(oid);
- return (type == null) ? PLACE_CHAT_TYPE : type;
- }
-
- /**
- * Used to assign unique ids to all speak requests.
- */
- protected synchronized int nextRequestId ()
- {
- return _requestId++;
- }
-
// documentation inherited
public void clientDidLogon (Client client)
{
@@ -645,17 +735,295 @@ public class ChatDirector extends BasicDirector
locationDidChange(null);
// clear our service
- _cservice = null;
+ _cservice = null;
}
/**
- * Run a message through all the currently registered filters.
+ * Called to determine whether we are permitted to post the supplied
+ * chat message. Derived classes may wish to throttle chat or restrict
+ * certain types in certain circumstances for whatever reason.
+ *
+ * @return null if the chat is permitted, a translatable string
+ * indicating the reason for rejection if not.
*/
- public String filter (String msg, Name otherUser, boolean outgoing)
+ protected String checkCanChat (
+ SpeakService speakSvc, String message, byte mode)
{
- _filterMessageOp.setMessage(msg, otherUser, outgoing);
- _filters.apply(_filterMessageOp);
- return _filterMessageOp.getMessage();
+ return null;
+ }
+
+ /**
+ * Delivers a plain chat message (not a slash command) on the
+ * specified speak service in the specified mode. The message will be
+ * mogrified and filtered prior to delivery.
+ *
+ * @return {@link ChatCodes#SUCCESS} if the message was delivered or a
+ * string indicating why it failed.
+ */
+ protected String deliverChat (
+ SpeakService speakSvc, String message, byte mode)
+ {
+ // run the message through our mogrification process
+ message = mogrifyChat(message, true, mode != ChatCodes.EMOTE_MODE);
+
+ // mogrification may result in something being turned into a slash
+ // command, in which case we have to run everything through again
+ // from the start
+ if (message.startsWith("/")) {
+ return requestChat(speakSvc, message, false);
+ }
+
+ // make sure this client is not restricted from performing this
+ // chat message for some reason or other
+ String errmsg = checkCanChat(speakSvc, message, mode);
+ if (errmsg != null) {
+ return errmsg;
+ }
+
+ // speak on the specified service
+ requestSpeak(speakSvc, message, mode);
+
+ return ChatCodes.SUCCESS;
+ }
+
+ /**
+ * Add the specified command to the history.
+ */
+ protected void addToHistory (String cmd)
+ {
+ // remove any previous instance of this command
+ _history.remove(cmd);
+
+ // append it to the end
+ _history.add(cmd);
+
+ // prune the history once it extends beyond max size
+ if (_history.size() > MAX_COMMAND_HISTORY) {
+ _history.remove(0);
+ }
+ }
+
+ /**
+ * Mogrify common literary crutches into more appealing chat or
+ * commands.
+ *
+ * @param transformsAllowed if true, the chat may transformed into a
+ * different mode. (lol -> /emote laughs)
+ * @param capFirst if true, the first letter of the text is
+ * capitalized. This is not desired if the chat is already an emote.
+ */
+ protected String mogrifyChat (
+ String text, boolean transformsAllowed, boolean capFirst)
+ {
+ int tlen = text.length();
+ if (tlen == 0) {
+ return text;
+
+ // check to make sure there aren't too many caps
+ } else if (tlen > 7) {
+ // count caps
+ int caps = 0;
+ for (int ii=0; ii < tlen; ii++) {
+ if (Character.isUpperCase(text.charAt(ii))) {
+ caps++;
+ if (caps > (tlen / 2)) {
+ // lowercase the whole string if there are
+ text = text.toLowerCase();
+ break;
+ }
+ }
+ }
+ }
+
+ StringBuffer buf = new StringBuffer(text);
+ buf = mogrifyChat(buf, transformsAllowed, capFirst);
+ return buf.toString();
+ }
+
+ /** Helper function for {@link #mogrifyChat}. */
+ protected StringBuffer mogrifyChat (
+ StringBuffer buf, boolean transformsAllowed, boolean capFirst)
+ {
+ // do the generic mogrifications and translations
+ buf = translatedReplacements("x.mogrifies", buf);
+
+ // perform themed expansions and transformations
+ if (transformsAllowed) {
+ buf = translatedReplacements("x.transforms", buf);
+ }
+
+ /*
+ // capitalize the first letter
+ if (capFirst) {
+ buf.setCharAt(0, Character.toUpperCase(buf.charAt(0)));
+ }
+ // and capitalize any letters after a sentence-ending punctuation
+ Pattern p = Pattern.compile("([^\\.][\\.\\?\\!](\\s)+\\p{Ll})");
+ Matcher m = p.matcher(buf);
+ if (m.find()) {
+ buf = new StringBuffer();
+ m.appendReplacement(buf, m.group().toUpperCase());
+ while (m.find()) {
+ m.appendReplacement(buf, m.group().toUpperCase());
+ }
+ m.appendTail(buf);
+ }
+ */
+
+ return buf;
+ }
+
+ /**
+ * Do all the replacements (mogrifications) specified in the
+ * translation string specified by the key.
+ */
+ protected StringBuffer translatedReplacements (String key, StringBuffer buf)
+ {
+ MessageBundle bundle = _msgmgr.getBundle(_bundle);
+ if (!bundle.exists(key)) {
+ return buf;
+ }
+ StringTokenizer st = new StringTokenizer(bundle.get(key), "#");
+ // apply the replacements to each mogrification that matches
+ while (st.hasMoreTokens()) {
+ String pattern = st.nextToken();
+ String replace = st.nextToken();
+ Matcher m = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).
+ matcher(buf);
+ if (m.find()) {
+ buf = new StringBuffer();
+ m.appendReplacement(buf, replace);
+ // they may match more than once
+ while (m.find()) {
+ m.appendReplacement(buf, replace);
+ }
+ m.appendTail(buf);
+ }
+ }
+ return buf;
+ }
+
+ /**
+ * Returns a hashmap containing all command handlers that match the
+ * specified command (i.e. the specified command is a prefix of their
+ * registered command string).
+ */
+ protected HashMap getCommandHandlers (String command)
+ {
+ HashMap matches = new HashMap();
+ BodyObject user = (BodyObject)_ctx.getClient().getClientObject();
+ Iterator itr = _handlers.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry entry = (Map.Entry) itr.next();
+ String cmd = (String) entry.getKey();
+ if (!cmd.startsWith(command)) {
+ continue;
+ }
+ CommandHandler handler = (CommandHandler)entry.getValue();
+ if (!handler.checkAccess(user)) {
+ continue;
+ }
+ matches.put(cmd, handler);
+ }
+ return matches;
+ }
+
+ /**
+ * Adds a chatter to our list of recent chatters.
+ */
+ protected void addChatter (Name name)
+ {
+ // check to see if the chatter validator approves..
+ if ((_chatterValidator != null) &&
+ (!_chatterValidator.isChatterValid(name))) {
+ return;
+ }
+
+ boolean wasthere = _chatters.remove(name);
+ _chatters.addFirst(name);
+
+ if (!wasthere) {
+ if (_chatters.size() > MAX_CHATTERS) {
+ _chatters.removeLast();
+ }
+
+ notifyChatterObservers();
+ }
+ }
+
+ /**
+ * Notifies all registered {@link ChatterObserver}s that the list of
+ * chatters has changed.
+ */
+ protected void notifyChatterObservers ()
+ {
+ _chatterObservers.apply(new ObserverList.ObserverOp() {
+ public boolean apply (Object observer) {
+ ((ChatterObserver)observer).chattersUpdated(
+ _chatters.listIterator());
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Translates the specified message using the specified bundle.
+ */
+ protected String xlate (String bundle, String message)
+ {
+ if (bundle != null && _msgmgr != null) {
+ MessageBundle msgb = _msgmgr.getBundle(bundle);
+ if (msgb == null) {
+ Log.warning(
+ "No message bundle available to translate message " +
+ "[bundle=" + bundle + ", message=" + message + "].");
+ } else {
+ message = msgb.xlate(message);
+ }
+ }
+ return message;
+ }
+
+ /**
+ * Display the specified system message as if it had come from the server.
+ */
+ protected void displaySystem (
+ String bundle, String message, byte attLevel, String localtype)
+ {
+ // nothing should be untranslated, so pass the default bundle if need
+ // be.
+ if (bundle == null) {
+ bundle = _bundle;
+ }
+ SystemMessage msg = new SystemMessage();
+ msg.attentionLevel = attLevel;
+ msg.setClientInfo(xlate(bundle, message), localtype);
+ dispatchMessage(msg);
+ }
+
+ /**
+ * Looks up and returns the message type associated with the specified
+ * oid.
+ */
+ protected String getLocalType (int oid)
+ {
+ String type = (String)_auxes.get(oid);
+ return (type == null) ? PLACE_CHAT_TYPE : type;
+ }
+
+ /**
+ * Used to assign unique ids to all speak requests.
+ */
+ protected synchronized int nextRequestId ()
+ {
+ return _requestId++;
+ }
+
+ // documentation inherited from interface
+ protected void fetchServices (Client client)
+ {
+ // get a handle on our chat service
+ _cservice = (ChatService)client.requireService(ChatService.class);
}
/**
@@ -708,6 +1076,114 @@ public class ChatDirector extends BasicDirector
protected ChatMessage _message;
}
+ /** Implements /help. */
+ protected class HelpHandler extends CommandHandler
+ {
+ public String handleCommand (
+ SpeakService speakSvc, String command, String args, String[] history)
+ {
+ String hcmd = "";
+
+ // grab the command they want help on
+ if (!StringUtil.blank(args)) {
+ hcmd = args;
+ int sidx = args.indexOf(" ");
+ if (sidx != -1) {
+ hcmd = args.substring(0, sidx);
+ }
+ }
+
+ // let the user give commands with or with the /
+ if (hcmd.startsWith("/")) {
+ hcmd = hcmd.substring(1);
+ }
+
+ // handle "/help help" and "/help someboguscommand"
+ HashMap possibleCommands = getCommandHandlers(hcmd);
+ if (hcmd.equals("help") || possibleCommands.isEmpty()) {
+ possibleCommands = getCommandHandlers("");
+ possibleCommands.remove("help"); // remove help from the list
+ }
+
+ // if there is only one possible command display its usage
+ switch (possibleCommands.size()) {
+ case 1:
+ Iterator itr = possibleCommands.keySet().iterator();
+ // this is a little funny, but we display the feeback
+ // message by hand and return SUCCESS so that the chat
+ // entry field doesn't think that we've failed and
+ // preserve our command text
+ displayFeedback(null, "m.usage_" + (String)itr.next());
+ return ChatCodes.SUCCESS;
+
+ default:
+ Object[] commands = possibleCommands.keySet().toArray();
+ Arrays.sort(commands);
+ String commandList = "";
+ for (int ii = 0; ii < commands.length; ii++) {
+ commandList += " /" + commands[ii];
+ }
+ return MessageBundle.tcompose("m.usage_help", commandList);
+ }
+ }
+ }
+
+ /** Implements /clear. */
+ protected class ClearHandler extends CommandHandler
+ {
+ public String handleCommand (
+ SpeakService speakSvc, String command, String args, String[] history)
+ {
+ clearDisplays();
+ return ChatCodes.SUCCESS;
+ }
+ }
+
+ /** Implements /speak. */
+ protected class SpeakHandler extends CommandHandler
+ {
+ public String handleCommand (
+ SpeakService speakSvc, String command, String args, String[] history)
+ {
+ if (StringUtil.blank(args)) {
+ return "m.usage_speak";
+ }
+ // note the command to be stored in the history
+ history[0] = command + " ";
+ return requestChat(null, args, true);
+ }
+ }
+
+ /** Implements /emote. */
+ protected class EmoteHandler extends CommandHandler
+ {
+ public String handleCommand (
+ SpeakService speakSvc, String command, String args, String[] history)
+ {
+ if (StringUtil.blank(args)) {
+ return "m.usage_emote";
+ }
+ // note the command to be stored in the history
+ history[0] = command + " ";
+ return deliverChat(speakSvc, args, ChatCodes.EMOTE_MODE);
+ }
+ }
+
+ /** Implements /think. */
+ protected class ThinkHandler extends CommandHandler
+ {
+ public String handleCommand (
+ SpeakService speakSvc, String command, String args, String[] history)
+ {
+ if (StringUtil.blank(args)) {
+ return "m.usage_think";
+ }
+ // note the command to be stored in the history
+ history[0] = command + " ";
+ return deliverChat(speakSvc, args, ChatCodes.THINK_MODE);
+ }
+ }
+
/** Our active chat context. */
protected CrowdContext _ctx;
@@ -748,6 +1224,12 @@ public class ChatDirector extends BasicDirector
protected ObserverList _chatterObservers =
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
+ /** Registered chat command handlers. */
+ protected static HashMap _handlers = new HashMap();
+
+ /** A history of chat commands. */
+ protected static ArrayList _history = new ArrayList();
+
/** Used by {@link #nextRequestId}. */
protected int _requestId;
@@ -759,4 +1241,7 @@ public class ChatDirector extends BasicDirector
/** The maximum number of chatter usernames to track. */
protected static final int MAX_CHATTERS = 6;
+
+ /** The maximum number of commands to keep in the chat history. */
+ protected static final int MAX_COMMAND_HISTORY = 10;
}
diff --git a/src/java/com/threerings/jme/chat/ChatView.java b/src/java/com/threerings/jme/chat/ChatView.java
index 7d996958b..71e9042c8 100644
--- a/src/java/com/threerings/jme/chat/ChatView.java
+++ b/src/java/com/threerings/jme/chat/ChatView.java
@@ -160,7 +160,7 @@ public class ChatView extends BContainer
} else {
// request to send this text as a chat message
- _chatdtr.requestSpeak(text);
+ _chatdtr.requestSpeak(null, text, ChatCodes.DEFAULT_MODE);
}
return true;
diff --git a/src/java/com/threerings/micasa/client/ChatPanel.java b/src/java/com/threerings/micasa/client/ChatPanel.java
index bdebb5208..9ed9fe7c1 100644
--- a/src/java/com/threerings/micasa/client/ChatPanel.java
+++ b/src/java/com/threerings/micasa/client/ChatPanel.java
@@ -231,7 +231,7 @@ public class ChatPanel
} else {
// request to send this text as a chat message
- _chatdtr.requestSpeak(text);
+ _chatdtr.requestSpeak(null, text, ChatCodes.DEFAULT_MODE);
}
// clear out the input because we sent a request