All the latest and ... well, the latest anyway.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3950 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2006-03-15 03:28:02 +00:00
parent 4fe0d6e484
commit ed1be4a0f2
18 changed files with 551 additions and 350 deletions
@@ -23,37 +23,8 @@ package com.threerings.crowd.chat.client {
import mx.collections.ArrayCollection; import mx.collections.ArrayCollection;
import com.threerings.util.ArrayUtil;
// TODO: this class is in progress import com.threerings.util.SimpleMap;
//
//
//
//
//
//
//
//
//
//
//
//
//
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;
import com.samskivert.util.StringUtil;
import com.threerings.presents.client.BasicDirector; import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client; import com.threerings.presents.client.Client;
@@ -104,12 +75,12 @@ public class ChatDirector extends BasicDirector
super(ctx); super(ctx);
// keep the context around // keep the context around
_ctx = ctx; _cctx = ctx;
_msgmgr = msgmgr; _msgmgr = msgmgr;
_bundle = bundle; _bundle = bundle;
// register ourselves as a location observer // register ourselves as a location observer
_ctx.getLocationDirector().addLocationObserver(this); _cctx.getLocationDirector().addLocationObserver(this);
if (_bundle == null || _msgmgr == null) { if (_bundle == null || _msgmgr == null) {
Log.warning("Null bundle or message manager given to ChatDirector"); Log.warning("Null bundle or message manager given to ChatDirector");
@@ -252,18 +223,6 @@ 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.
*
* Info messages are sent when something happens that was neither
* directly triggered by the user, nor requires direct action.
*/
public function displayInfo (bundle :String, message :String) :void
{
displaySystem(bundle, message, SystemMessage.INFO, PLACE_CHAT_TYPE);
}
/** /**
* Display a system INFO message as if it had come from the server. * Display a system INFO message as if it had come from the server.
* *
@@ -271,8 +230,11 @@ public class ChatDirector extends BasicDirector
* directly triggered by the user, nor requires direct action. * directly triggered by the user, nor requires direct action.
*/ */
public function displayInfo ( public function displayInfo (
bundle :String, message :String, localtype :String) :void bundle :String, message :String, localtype :String = null) :void
{ {
if (localtype == null) {
localtype = ChatCodes.PLACE_CHAT_TYPE;
}
displaySystem(bundle, message, SystemMessage.INFO, localtype); displaySystem(bundle, message, SystemMessage.INFO, localtype);
} }
@@ -415,7 +377,7 @@ public class ChatDirector extends BasicDirector
} }
// dispatch a speak request using the supplied speak service // dispatch a speak request using the supplied speak service
speakService.speak(_ctx.getClient(), message, mode); speakService.speak(_cctx.getClient(), message, mode);
} }
/** /**
@@ -432,15 +394,14 @@ public class ChatDirector extends BasicDirector
return; return;
} }
_cservice.broadcast(_ctx.getClient(), message, var failure :Function = function (reason :String) :void
new InvocationAdapter(broadcastFailed)); {
} reason = MessageBundle.compose("m.broadcast_failed", reason);
displayFeedback(_bundle, reason);
};
// Called above, when we fail to broadcast _cservice.broadcast(_cctx.getClient(), message,
private function broadcastFailed (reason :String) :void new InvocationAdapter(failure));
{
reason = MessageBundle.compose("m.broadcast_failed", reason);
displayFeedback(_bundle, reason);
} }
/** /**
@@ -465,67 +426,50 @@ public class ChatDirector extends BasicDirector
return; return;
} }
// create a listener that will report success or failure var failure :Function = function (reason :String) :void
ChatService.TellListener listener = new ChatService.TellListener() { {
public void tellSucceeded (long idletime, String awayMessage) { var msg :String = MessageBundle.compose(
"m.tell_failed", MessageBundle.taint(target), reason);
displayFeedback(_bundle, msg);
if (rl != null) {
rl.requestFailed(null);
}
};
var success :Function = function (
idleTime :long, awayMessage :String) :void
{
var feedback :String = xlate(_bundle, MessageBundle.tcompose(
"m.told_format", target, message));
dispatchMessage(new TellFeedbackMessage(feedback));
addChatter(target);
if (rl != null) {
rl.requestCompleted(target);
} }
protected void success (String feedback) { // if they have an away message, report that
if (awayMessage != null) {
awayMessage = filter(awayMessage, target, false);
if (awayMessage != null) {
var msg :String = MessageBundle.tcompose(
"m.recipient_afk", target, awayMessage);
displayFeedback(_bundle, msg);
}
} }
public void requestFailed (String reason) { // if they are idle, report that
if (idletime > 0) {
// adjust by the time it took them to become idle
idletime += _cctx.getConfig().getValue(
IDLE_TIME_KEY, DEFAULT_IDLE_TIME);
var msg :String = MessageBundle.compose(
"m.recipient_idle", MessageBundle.taint(target),
TimeUtil.getTimeOrderString(idletime, TimeUtil.MINUTE));
displayFeedback(_bundle, msg);
} }
}; };
_cservice.tell(_ctx.getClient(), target, message, _cservice.tell(_cctx.getClient(), target, message,
new TellAdapter(tellFailed, tellSucceeded, [target, message, rl])); new TellAdapter(failure, success));
}
private function tellFailed (reason :String, args :Array) :void
{
var msg :String = MessageBundle.compose(
"m.tell_failed", MessageBundle.taint(args[0]), reason);
displayFeedback(_bundle, msg);
var rl:ResultListener = (args[2] as ResultListener);
if (rl != null) {
rl.requestFailed(null);
}
}
private function tellSucceeded (
idleTime :long, awayMessage :String, args :Array) :void
{
var target :Name = (args[0] as Target);
var message :String = (args[1] as String);
var rl:ResultListener = (args[2] as ResultListener);
var feedback :String = xlate(_bundle, MessageBundle.tcompose(
"m.told_format", target, message));
dispatchMessage(new TellFeedbackMessage(feedback));
addChatter(target);
if (rl != null) {
rl.requestCompleted(target);
}
// if they have an away message, report that
if (awayMessage != null) {
awayMessage = filter(awayMessage, target, false);
if (awayMessage != null) {
var msg :String = MessageBundle.tcompose(
"m.recipient_afk", target, awayMessage);
displayFeedback(_bundle, msg);
}
}
// if they are idle, report that
if (idletime > 0) {
// adjust by the time it took them to become idle
idletime += _ctx.getConfig().getValue(
IDLE_TIME_KEY, DEFAULT_IDLE_TIME);
var msg :String = MessageBundle.compose(
"m.recipient_idle", MessageBundle.taint(target),
TimeUtil.getTimeOrderString(idletime, TimeUtil.MINUTE));
displayFeedback(_bundle, msg);
}
} }
/** /**
@@ -533,7 +477,7 @@ public class ChatDirector extends BasicDirector
* that sends a tell message to this client to indicate that we are * that sends a tell message to this client to indicate that we are
* busy or away from the keyboard. * busy or away from the keyboard.
*/ */
public void setAwayMessage (String message) public function setAwayMessage (message :String) :void
{ {
if (message != null) { if (message != null) {
message = filter(message, null, true); message = filter(message, null, true);
@@ -544,7 +488,7 @@ public class ChatDirector extends BasicDirector
} }
} }
// pass the buck right on along // pass the buck right on along
_cservice.away(_ctx.getClient(), message); _cservice.away(_cctx.getClient(), message);
} }
/** /**
@@ -556,7 +500,7 @@ public class ChatDirector extends BasicDirector
* @param localtype a type to be associated with all chat messages * @param localtype a type to be associated with all chat messages
* that arrive on the specified DObject. * that arrive on the specified DObject.
*/ */
public void addAuxiliarySource (DObject source, String localtype) public function addAuxiliarySource (source :DObject, localtype :String) :void
{ {
source.addListener(this); source.addListener(this);
_auxes.put(source.getOid(), localtype); _auxes.put(source.getOid(), localtype);
@@ -565,7 +509,7 @@ public class ChatDirector extends BasicDirector
/** /**
* Removes a previously added auxiliary chat source. * Removes a previously added auxiliary chat source.
*/ */
public void removeAuxiliarySource (DObject source) public function removeAuxiliarySource (source :DObject) :void
{ {
source.removeListener(this); source.removeListener(this);
_auxes.remove(source.getOid()); _auxes.remove(source.getOid());
@@ -587,20 +531,20 @@ public class ChatDirector extends BasicDirector
/** /**
* Runs the supplied message through the various chat mogrifications. * Runs the supplied message through the various chat mogrifications.
*/ */
public String mogrifyChat (String text) public function mogrifyChat (text :String) :String
{ {
return mogrifyChat(text, false, true); return mogrifyChatImpl(text, false, true);
} }
// documentation inherited // documentation inherited from interface LocationObserver
public boolean locationMayChange (int placeId) public function locationMayChange (placeId :int) :Boolean
{ {
// we accept all location change requests // we accept all location change requests
return true; return true;
} }
// documentation inherited // documentation inherited from interface LocationObserver
public void locationDidChange (PlaceObject place) public function locationDidChange (place :PlaceObject) :void
{ {
if (_place != null) { if (_place != null) {
// unlisten to our old object // unlisten to our old object
@@ -614,31 +558,31 @@ public class ChatDirector extends BasicDirector
} }
} }
// documentation inherited // documentation inherited from interface LocationObserver
public void locationChangeFailed (int placeId, String reason) public function locationChangeFailed (placeId :int, reason :String) :void
{ {
// nothing we care about // nothing we care about
} }
// documentation inherited // documentation inherited from interface MessageListener
public void messageReceived (MessageEvent event) public function messageReceived (event :MessageEvent) :void
{ {
if (CHAT_NOTIFICATION.equals(event.getName())) { if (ChatCodes.CHAT_NOTIFICATION === event.getName()) {
ChatMessage msg = (ChatMessage) event.getArgs()[0]; var msg :ChatMessage = (event.getArgs()[0] as ChatMessage);
String localtype = getLocalType(event.getTargetOid()); var localtype :String = getLocalType(event.getTargetOid());
String message = msg.message; var message :String = msg.message;
String autoResponse = null; var autoResponse :String = null;
Name speaker = null; var speaker :Name = null;
byte mode = (byte) -1; var mode :int = -1;
// figure out if the message was triggered by another user // figure out if the message was triggered by another user
if (msg instanceof UserMessage) { if (msg is UserMessage) {
UserMessage umsg = (UserMessage)msg; var umsg :UserMessage = (msg as UserMessage);
speaker = umsg.speaker; speaker = umsg.speaker;
mode = umsg.mode; mode = umsg.mode;
} else if (msg instanceof UserSystemMessage) { } else if (msg is UserSystemMessage) {
speaker = ((UserSystemMessage) msg).speaker; speaker = (msg as UserSystemMessage).speaker;
} }
// if there was an originating speaker, see if we want to hear it // if there was an originating speaker, see if we want to hear it
@@ -647,14 +591,14 @@ public class ChatDirector extends BasicDirector
return; return;
} }
if (USER_CHAT_TYPE.equals(localtype) && if (ChatCodes.USER_CHAT_TYPE == localtype &&
mode == ChatCodes.DEFAULT_MODE) { mode == ChatCodes.DEFAULT_MODE) {
// if it was a tell, add the speaker as a chatter // if it was a tell, add the speaker as a chatter
addChatter(speaker); addChatter(speaker);
// note whether or not we have an auto-response // note whether or not we have an auto-response
BodyObject self = (BodyObject) var self :BodyObject =
_ctx.getClient().getClientObject(); (_cctx.getClient().getClientObject() as BodyObject);
if (!StringUtil.isBlank(self.awayMessage)) { if (!StringUtil.isBlank(self.awayMessage)) {
autoResponse = self.awayMessage; autoResponse = self.awayMessage;
} }
@@ -669,7 +613,7 @@ public class ChatDirector extends BasicDirector
// if we auto-responded, report as much // if we auto-responded, report as much
if (autoResponse != null) { if (autoResponse != null) {
String amsg = MessageBundle.tcompose( var amsg :String = MessageBundle.tcompose(
"m.auto_responded", speaker, autoResponse); "m.auto_responded", speaker, autoResponse);
displayFeedback(_bundle, amsg); displayFeedback(_bundle, amsg);
} }
@@ -677,28 +621,30 @@ public class ChatDirector extends BasicDirector
} }
// documentation inherited // documentation inherited
public void clientDidLogon (Client client) public override function clientDidLogon (client :Client) :void
{ {
super.clientDidLogon(client); super.clientDidLogon(client);
// listen on the client object for tells // listen on the client object for tells
addAuxiliarySource(_clobj = client.getClientObject(), USER_CHAT_TYPE); addAuxiliarySource(_clobj = client.getClientObject(),
ChatCodes.USER_CHAT_TYPE);
} }
// documentation inherited // documentation inherited
public void clientObjectDidChange (Client client) public override function clientObjectDidChange (client :Client) :void
{ {
super.clientObjectDidChange(client); super.clientObjectDidChange(client);
// change what we're listening to for tells // change what we're listening to for tells
removeAuxiliarySource(_clobj); removeAuxiliarySource(_clobj);
addAuxiliarySource(_clobj = client.getClientObject(), USER_CHAT_TYPE); addAuxiliarySource(_clobj = client.getClientObject(),
ChatCodes.USER_CHAT_TYPE);
clearDisplays(); clearDisplays();
} }
// documentation inherited // documentation inherited
public void clientDidLogoff (Client client) public override function clientDidLogoff (client :Client) :void
{ {
super.clientDidLogoff(client); super.clientDidLogoff(client);
@@ -712,9 +658,9 @@ public class ChatDirector extends BasicDirector
clearDisplays(); clearDisplays();
// clear out the list of people we've chatted with // // clear out the list of people we've chatted with
_chatters.clear(); // _chatters.clear();
notifyChatterObservers(); // notifyChatterObservers();
// clear the _place // clear the _place
locationDidChange(null); locationDidChange(null);
@@ -732,8 +678,8 @@ public class ChatDirector extends BasicDirector
* and has already been dealt with, or a translatable string * and has already been dealt with, or a translatable string
* indicating the reason for rejection if not. * indicating the reason for rejection if not.
*/ */
protected String checkCanChat ( protected function checkCanChat (
SpeakService speakSvc, String message, byte mode) speakSvc :SpeakService , message :String, mode :int) :String
{ {
return null; return null;
} }
@@ -746,11 +692,11 @@ public class ChatDirector extends BasicDirector
* @return {@link ChatCodes#SUCCESS} if the message was delivered or a * @return {@link ChatCodes#SUCCESS} if the message was delivered or a
* string indicating why it failed. * string indicating why it failed.
*/ */
internal String deliverChat ( internal function deliverChat (
SpeakService speakSvc, String message, byte mode) speakSvc :SpeakService, message :String, mode :int) :String
{ {
// run the message through our mogrification process // run the message through our mogrification process
message = mogrifyChat(message, true, mode != ChatCodes.EMOTE_MODE); message = mogrifyChatImpl(message, true, mode != ChatCodes.EMOTE_MODE);
// mogrification may result in something being turned into a slash // mogrification may result in something being turned into a slash
// command, in which case we have to run everything through again // command, in which case we have to run everything through again
@@ -761,7 +707,7 @@ public class ChatDirector extends BasicDirector
// make sure this client is not restricted from performing this // make sure this client is not restricted from performing this
// chat message for some reason or other // chat message for some reason or other
String errmsg = checkCanChat(speakSvc, message, mode); var errmsg :String = checkCanChat(speakSvc, message, mode);
if (errmsg != null) { if (errmsg != null) {
return errmsg; return errmsg;
} }
@@ -775,17 +721,17 @@ public class ChatDirector extends BasicDirector
/** /**
* Add the specified command to the history. * Add the specified command to the history.
*/ */
protected void addToHistory (String cmd) protected function addToHistory (cmd :String) :void
{ {
// remove any previous instance of this command // remove any previous instance of this command
_history.remove(cmd); ArrayUtil.removeAll(_history, cmd);
// append it to the end // append it to the end
_history.add(cmd); _history.push(cmd);
// prune the history once it extends beyond max size // prune the history once it extends beyond max size
if (_history.size() > MAX_COMMAND_HISTORY) { if (_history.length > MAX_COMMAND_HISTORY) {
_history.remove(0); _history.shift();
} }
} }
@@ -798,19 +744,24 @@ public class ChatDirector extends BasicDirector
* @param capFirst if true, the first letter of the text is * @param capFirst if true, the first letter of the text is
* capitalized. This is not desired if the chat is already an emote. * capitalized. This is not desired if the chat is already an emote.
*/ */
protected String mogrifyChat ( protected function mogrifyChatImpl (
String text, boolean transformsAllowed, boolean capFirst) text :String, transformsAllowed :Boolean, capFirst :Boolean) :String
{ {
int tlen = text.length(); var tlen :int = text.length;
if (tlen == 0) { if (tlen == 0) {
return text; return text;
// check to make sure there aren't too many caps // check to make sure there aren't too many caps
} else if (tlen > 7) { } else if (tlen > 7) {
// count caps // count caps
int caps = 0; var caps :int = 0;
for (int ii=0; ii < tlen; ii++) { for (var ii :int = 0; ii < tlen; ii++) {
if (Character.isUpperCase(text.charAt(ii))) { var ch :String = text.charAt(ii);
// do a fucked-up test to see if it's uppercase
// make sure the uppercase version is the same as the orig
// and the lowercase version is different
var chU :String = ch.toUpperCase();
if (ch == chU && chU != ch.toLowerCase()) {
caps++; caps++;
if (caps > (tlen / 2)) { if (caps > (tlen / 2)) {
// lowercase the whole string if there are // lowercase the whole string if there are
@@ -821,21 +772,19 @@ public class ChatDirector extends BasicDirector
} }
} }
StringBuffer buf = new StringBuffer(text); return mogrifyChatText(text, transformsAllowed, capFirst);
buf = mogrifyChat(buf, transformsAllowed, capFirst);
return buf.toString();
} }
/** Helper function for {@link #mogrifyChat}. */ /** Helper function for {@link #mogrifyChat}. */
protected StringBuffer mogrifyChat ( protected function mogrifyChatText (
StringBuffer buf, boolean transformsAllowed, boolean capFirst) text :String, transformsAllowed :Boolean, capFirst :Boolean) :String
{ {
// do the generic mogrifications and translations // do the generic mogrifications and translations
buf = translatedReplacements("x.mogrifies", buf); text = translatedReplacements("x.mogrifies", text);
// perform themed expansions and transformations // perform themed expansions and transformations
if (transformsAllowed) { if (transformsAllowed) {
buf = translatedReplacements("x.transforms", buf); text = translatedReplacements("x.transforms", text);
} }
/* /*
@@ -856,37 +805,28 @@ public class ChatDirector extends BasicDirector
} }
*/ */
return buf; return text;
} }
/** /**
* Do all the replacements (mogrifications) specified in the * Do all the replacements (mogrifications) specified in the
* translation string specified by the key. * translation string specified by the key.
*/ */
protected StringBuffer translatedReplacements (String key, StringBuffer buf) protected function translatedReplacements (
key :String, text :String) :String
{ {
MessageBundle bundle = _msgmgr.getBundle(_bundle); var bundle :MessageBundle = _msgmgr.getBundle(_bundle);
if (!bundle.exists(key)) { if (!bundle.exists(key)) {
return buf; return buf;
} }
StringTokenizer st = new StringTokenizer(bundle.get(key), "#"); var repls :Array = bundle.get(key).split("#");
// apply the replacements to each mogrification that matches // apply the replacements to each mogrification that matches
while (st.hasMoreTokens()) { for (var ii :int = 0; ii < repls.length; ii++) {
String pattern = st.nextToken(); var pattern :RegExp = new RegExp((repls[ii] as String), "gim");
String replace = st.nextToken(); var replace :String = (repls[++ii] as String);
Matcher m = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE). text = text.replace(pattern, replace);
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; return text;
} }
/** /**
@@ -894,22 +834,21 @@ public class ChatDirector extends BasicDirector
* specified command (i.e. the specified command is a prefix of their * specified command (i.e. the specified command is a prefix of their
* registered command string). * registered command string).
*/ */
protected HashMap getCommandHandlers (String command) protected function getCommandHandlers (command :String) :SimpleMap
{ {
HashMap matches = new HashMap(); var matches :SimpleMap = new SimpleMap();
BodyObject user = (BodyObject)_ctx.getClient().getClientObject(); var user :BodyObject =
Iterator itr = _handlers.entrySet().iterator(); (_cctx.getClient().getClientObject() as BodyObject);
while (itr.hasNext()) { var keys :Array = _handlers.keys();
Map.Entry entry = (Map.Entry) itr.next(); for (var ii :int = 0; ii < keys.length; ii++) {
String cmd = (String) entry.getKey(); var cmd :String = (keys[ii] as String);
if (!cmd.startsWith(command)) { if (cmd.indexOf(command) != 0) {
continue; continue;
} }
CommandHandler handler = (CommandHandler)entry.getValue(); var handler :CommandHandler = (_handlers.get(cmd) as CommandHandler);
if (!handler.checkAccess(user)) { if (handler.checkAccess(user)) {
continue; matches.put(cmd, handler);
} }
matches.put(cmd, handler);
} }
return matches; return matches;
} }
@@ -917,48 +856,48 @@ public class ChatDirector extends BasicDirector
/** /**
* Adds a chatter to our list of recent chatters. * Adds a chatter to our list of recent chatters.
*/ */
protected void addChatter (Name name) protected function addChatter (name :Name) :void
{ {
// check to see if the chatter validator approves.. // // check to see if the chatter validator approves..
if ((_chatterValidator != null) && // if ((_chatterValidator != null) &&
(!_chatterValidator.isChatterValid(name))) { // (!_chatterValidator.isChatterValid(name))) {
return; // return;
} // }
//
boolean wasthere = _chatters.remove(name); // boolean wasthere = _chatters.remove(name);
_chatters.addFirst(name); // _chatters.addFirst(name);
//
if (!wasthere) { // if (!wasthere) {
if (_chatters.size() > MAX_CHATTERS) { // if (_chatters.size() > MAX_CHATTERS) {
_chatters.removeLast(); // _chatters.removeLast();
} // }
//
notifyChatterObservers(); // notifyChatterObservers();
} // }
} }
/** /**
* Notifies all registered {@link ChatterObserver}s that the list of * Notifies all registered {@link ChatterObserver}s that the list of
* chatters has changed. * chatters has changed.
*/ */
protected void notifyChatterObservers () // protected void notifyChatterObservers ()
{ // {
_chatterObservers.apply(new ObserverList.ObserverOp() { // _chatterObservers.apply(new ObserverList.ObserverOp() {
public boolean apply (Object observer) { // public boolean apply (Object observer) {
((ChatterObserver)observer).chattersUpdated( // ((ChatterObserver)observer).chattersUpdated(
_chatters.listIterator()); // _chatters.listIterator());
return true; // return true;
} // }
}); // });
} // }
/** /**
* Translates the specified message using the specified bundle. * Translates the specified message using the specified bundle.
*/ */
protected String xlate (String bundle, String message) protected function xlate (bundle :String, message :String) :String
{ {
if (bundle != null && _msgmgr != null) { if (bundle != null && _msgmgr != null) {
MessageBundle msgb = _msgmgr.getBundle(bundle); var msgb :MessageBundle = _msgmgr.getBundle(bundle);
if (msgb == null) { if (msgb == null) {
Log.warning( Log.warning(
"No message bundle available to translate message " + "No message bundle available to translate message " +
@@ -973,15 +912,16 @@ public class ChatDirector extends BasicDirector
/** /**
* Display the specified system message as if it had come from the server. * Display the specified system message as if it had come from the server.
*/ */
protected void displaySystem ( protected function displaySystem (
String bundle, String message, byte attLevel, String localtype) bundle :String, message :String, attLevel :int,
localtype :String) :void
{ {
// nothing should be untranslated, so pass the default bundle if need // nothing should be untranslated, so pass the default bundle if need
// be. // be.
if (bundle == null) { if (bundle == null) {
bundle = _bundle; bundle = _bundle;
} }
SystemMessage msg = new SystemMessage(); var msg :SystemMessage = new SystemMessage();
msg.attentionLevel = attLevel; msg.attentionLevel = attLevel;
msg.setClientInfo(xlate(bundle, message), localtype); msg.setClientInfo(xlate(bundle, message), localtype);
dispatchMessage(msg); dispatchMessage(msg);
@@ -991,79 +931,21 @@ public class ChatDirector extends BasicDirector
* Looks up and returns the message type associated with the specified * Looks up and returns the message type associated with the specified
* oid. * oid.
*/ */
protected String getLocalType (int oid) protected function getLocalType (oid :int) :String
{ {
String type = (String)_auxes.get(oid); var typ :String = (_auxes.get(oid) as String);
return (type == null) ? PLACE_CHAT_TYPE : type; return (typ == null) ? ChatCodes.PLACE_CHAT_TYPE : typ;
}
/**
* Used to assign unique ids to all speak requests.
*/
protected synchronized int nextRequestId ()
{
return _requestId++;
} }
// documentation inherited from interface // documentation inherited from interface
protected void fetchServices (Client client) protected override function fetchServices (client :Client) :void
{ {
// get a handle on our chat service // get a handle on our chat service
_cservice = (ChatService)client.requireService(ChatService.class); _cservice = (client.requireService(ChatService) as ChatService);
}
/**
* An operation that checks with all chat filters to properly filter
* a message prior to sending to the server or displaying.
*/
protected static class FilterMessageOp implements ObserverList.ObserverOp
{
public void setMessage (String msg, Name otherUser, boolean outgoing)
{
_msg = msg;
_otherUser = otherUser;
_out = outgoing;
}
public boolean apply (Object observer)
{
if (_msg != null) {
_msg = ((ChatFilter) observer).filter(_msg, _otherUser, _out);
}
return true;
}
public String getMessage ()
{
return _msg;
}
protected Name _otherUser;
protected String _msg;
protected boolean _out;
}
/**
* An observer op used to dispatch ChatMessages on the client.
*/
protected static class DisplayMessageOp implements ObserverList.ObserverOp
{
public void setMessage (ChatMessage message)
{
_message = message;
}
public boolean apply (Object observer)
{
((ChatDisplay)observer).displayMessage(_message);
return true;
}
protected ChatMessage _message;
} }
/** Our active chat context. */ /** Our active chat context. */
protected var _ctx :CrowdContext; protected var _cctx :CrowdContext;
/** Provides access to chat-related server-side services. */ /** Provides access to chat-related server-side services. */
protected var _cservice :ChatService; protected var _cservice :ChatService;
@@ -1107,12 +989,6 @@ public class ChatDirector extends BasicDirector
/** A history of chat commands. */ /** A history of chat commands. */
protected static const _history :Array = new Array(); protected static const _history :Array = new Array();
/** Used by {@link #nextRequestId}. */
protected var _requestId :int;
// /** Operation used to filter chat messages. */
// protected FilterMessageOp _filterMessageOp = new FilterMessageOp();
/** The maximum number of chatter usernames to track. */ /** The maximum number of chatter usernames to track. */
protected static const MAX_CHATTERS :int = 6; protected static const MAX_CHATTERS :int = 6;
@@ -33,13 +33,13 @@ public interface ChatDisplay
/** /**
* Called to clear the chat display. * Called to clear the chat display.
*/ */
public function clear () :void; function clear () :void;
/** /**
* Called to display a chat message. * Called to display a chat message.
* *
* @see ChatMessage * @see ChatMessage
*/ */
public function displayMessage (msg :ChatMessage) :void; function displayMessage (msg :ChatMessage) :void;
} }
} }
@@ -7,17 +7,16 @@ import com.threerings.presents.client.InvocationAdapter;
public class TellAdapter extends InvocationAdapter public class TellAdapter extends InvocationAdapter
implements TellListener implements TellListener
{ {
public function TellAdapter ( public function TellAdapter (failedFunc :Function, successFunc :Function)
failedFunc :Function, successFunc :Function, args :Array = null)
{ {
super(failedFunc, args); super(failedFunc);
_successFunc = successFunc; _successFunc = successFunc;
} }
// documentation inherited from interface TellListener // documentation inherited from interface TellListener
public function tellSucceeded (idleTime :long, awayMsg :String) public function tellSucceeded (idleTime :long, awayMsg :String) :void
{ {
_successFunc(idleTime, awayMsg, args); _successFunc(idleTime, awayMsg);
} }
/** The method to call on success. */ /** The method to call on success. */
@@ -21,6 +21,9 @@
package com.threerings.crowd.chat.data { package com.threerings.crowd.chat.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/** /**
* A ChatMessage that represents a message that came from the server * A ChatMessage that represents a message that came from the server
* and did not result from direct user action. * and did not result from direct user action.
@@ -145,7 +145,7 @@ public class BodyObject extends ClientObject
*/ */
public function setUsername (value :Name) :void public function setUsername (value :Name) :void
{ {
Name ovalue = this.username; var ovalue :Name = this.username;
requestAttributeChange( requestAttributeChange(
USERNAME, value, ovalue); USERNAME, value, ovalue);
this.username = value; this.username = value;
@@ -161,7 +161,7 @@ public class BodyObject extends ClientObject
*/ */
public function setLocation (value :int) :void public function setLocation (value :int) :void
{ {
int ovalue = this.location; var ovalue :int = this.location;
requestAttributeChange( requestAttributeChange(
LOCATION, value, ovalue); LOCATION, value, ovalue);
this.location = value; this.location = value;
@@ -60,7 +60,7 @@ public class OccupantInfo
public static const DISCONNECTED :int = 2; public static const DISCONNECTED :int = 2;
/** Maps status codes to human readable strings. */ /** Maps status codes to human readable strings. */
public static const X_STATUS :Array = { "active", "idle", "discon" }; public static const X_STATUS :Array = [ "active", "idle", "discon" ];
/** The body object id of this occupant (and our entry key). */ /** The body object id of this occupant (and our entry key). */
public var bodyOid :Integer; public var bodyOid :Integer;
@@ -156,7 +156,7 @@ public class PlaceObject extends DObject
public function setOccupantInfo (value :DSet) :void public function setOccupantInfo (value :DSet) :void
{ {
requestAttributeChange(OCCUPANT_INFO, value, this.occupantInfo); requestAttributeChange(OCCUPANT_INFO, value, this.occupantInfo);
this.occupantInfo = (value == null) ? null : (DSet)value.clone(); this.occupantInfo = (value == null) ? null : value;
} }
/** /**
@@ -0,0 +1,39 @@
package com.threerings.msoy.client {
import flash.util.describeType;
import mx.collections.ArrayCollection;
import com.threerings.util.Name;
import com.threerings.presents.Log;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.TimeBaseMarshaller;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.QSet;
import com.threerings.presents.net.UsernamePasswordCreds;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.net.BootstrapData;
public class MsoyClient extends Client
{
public function MsoyClient ()
{
super(new UsernamePasswordCreds(new Name("Ray"), "fork-u-2"));
_ctx = new MsoyContext(this);
setServer("tasman.sea.earth.threerings.net", DEFAULT_SERVER_PORT);
logon();
}
public function fuckingCompiler () :void
{
var i :int = TimeBaseMarshaller.GET_TIME_OID;
}
protected var _ctx :MsoyContext;
}
}
@@ -0,0 +1,74 @@
package com.threerings.msoy.client {
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.crowd.client.ChatDirector;
import com.threerings.crowd.client.LocationDirector;
import com.threerings.crowd.client.OccupantDirector;
import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.util.CrowdContext;
import com.threerings.crowd.chat.client.ChatDirector;
public class MsoyContext
implements CrowdContext
{
public function MsoyContext (client :Client)
{
_client = client;
// TODO: verify params to these constructors
_msgmgr = new MessageManager("rsrc");
_chatdir = new ChatDirector(this, _msgmgr, "general");
}
// documentation inherited from superinterface PresentsContext
public function getClient () :Client
{
return _client;
}
// documentation inherited from superinterface PresentsContext
public function getDObjectManager () :DObjectManager
{
return _client.getDObjectManager();
}
// documentation inherited from interface CrowdContext
public function getLocationDirector () :LocationDirector
{
return null; // TODO
}
// documentation inherited from interface CrowdContext
public function getOccupantDirector () :OccupantDirector
{
return null; // TODO
}
// documentation inherited from interface CrowdContext
public function getChatDirector () :ChatDirector
{
return _chatdir;
}
// documentation inherited from interface CrowdContext
public function setPlaceView (view :PlaceView) :void
{
// TODO
}
// documentation inherited from interface CrowdContext
public function clearPlaceView (view :PlaceView) :void
{
// TODO
}
protected var _client :Client;
protected var _msgmgr :MessageManager;
protected var _chatdir :ChatDirector;
}
}
@@ -38,7 +38,7 @@ public class BasicDirector
* context that it can use to register itself with the necessary * context that it can use to register itself with the necessary
* entities. * entities.
*/ */
protected function BasicDirector (ctx :PresentsContext) public function BasicDirector (ctx :PresentsContext)
{ {
// save context // save context
_ctx = ctx; _ctx = ctx;
@@ -7,22 +7,18 @@ public class InvocationAdapter
* Construct an InvocationAdapter that will call the specified * Construct an InvocationAdapter that will call the specified
* function on error. * function on error.
*/ */
public function InvocationAdapter (failedFunc :Function, args :Array = null) public function InvocationAdapter (failedFunc :Function)
{ {
_failedFunc = failedFunc; _failedFunc = failedFunc;
_args = args;
} }
// documentation inherited from interface InvocationListener // documentation inherited from interface InvocationListener
public function requestFailed (cause :String) :void public function requestFailed (cause :String) :void
{ {
_failedFunc(cause, _args); _failedFunc(cause);
} }
/** The Function to call when we've recevied our failure response. */ /** The Function to call when we've recevied our failure response. */
protected var _failedFunc :Function; protected var _failedFunc :Function;
/** Any other extra information to pass along to the function. */
protected var _args :Array;
} }
} }
@@ -35,6 +35,15 @@ public class InvocationDirector
_omgr = omgr; _omgr = omgr;
_client = client; _client = client;
var subby :Object = new Object();
subby.objectAvailable = function (obj :DObject) :void {
gotClientObject(obj as ClientObject);
}
subby.requestFailed = function (
oid :int, cause :ObjectAccessError) :void {
gotClientObjectFailed(oid, cause);
}
_omgr.subscribeToObject(cloid, new ClientSubscriber(this)); _omgr.subscribeToObject(cloid, new ClientSubscriber(this));
} }
@@ -11,6 +11,11 @@ import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.QSet; import com.threerings.presents.dobj.QSet;
import com.threerings.presents.net.UsernamePasswordCreds; import com.threerings.presents.net.UsernamePasswordCreds;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.net.BootstrapData;
public class TestClient extends Client public class TestClient extends Client
{ {
public function TestClient () public function TestClient ()
@@ -61,7 +66,18 @@ public class TestClient extends Client
list.addItem(bob); list.addItem(bob);
Log.debug("jim's indeX: " + list.getItemIndex(jim)); Log.debug("jim's indeX: " + list.getItemIndex(jim));
Log.debug("bob2's indeX: " + list.getItemIndex(bob2)); Log.debug("bob2's indeX: " + list.getItemIndex(bob2));
Log.debug("function: " + describeType(testFunc).toXMLString());
Log.debug("func length: " + testFunc.length);
Log.debug("funcToString: " + testFunc);
Log.debug("interfaces: " + describeType(Bint));
var funcy :Function = function (i :int) :void {
Log.debug("i is " + i);
Log.debug("first list item is " + list[0]);
}
_savedFunc = funcy;
_listy = list;
/* /*
@@ -109,6 +125,13 @@ public class TestClient extends Client
//var ta3 :TypedArray = new TypedArray(Pork); //var ta3 :TypedArray = new TypedArray(Pork);
} }
public override function gotBootstrap (
data :BootstrapData, omgr :DObjectManager) :void
{
_listy.addItemAt("new item 0", 0);
super.gotBootstrap(data, omgr);
_savedFunc(3);
}
// If a class isn't used anywhere, it won't get added to the .swf. // If a class isn't used anywhere, it won't get added to the .swf.
// Here, I hack. // Here, I hack.
@@ -117,19 +140,30 @@ public class TestClient extends Client
var i :int = TimeBaseMarshaller.GET_TIME_OID; var i :int = TimeBaseMarshaller.GET_TIME_OID;
} }
public function testFunc (one :int, two :int = 0) :void public function testFunc (one :int, two :int = 0, three :int = -1) :void
{ {
Log.debug("this: " + ", args: " + arguments.length + Log.debug("this: " + ", args: " + arguments.length +
": " + arguments); ": " + arguments);
} }
prototype var _foo :String; prototype var _foo :String;
protected var _savedFunc :Function;
protected var _listy :ArrayCollection;
} }
} }
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.client.TestClient; import com.threerings.presents.client.TestClient;
interface Aint {
function foo () :void;
}
interface Bint extends Aint {
function bar () :void;
}
class Person class Person
{ {
public function Person (name :String) public function Person (name :String)
+50
View File
@@ -0,0 +1,50 @@
package com.threerings.util {
/**
* Contains methods that should be in Array, but aren't.
*/
public class ArrayUtil
{
/**
* Remove the first instance of the specified element from the array.
*/
public static function removeFirst (arr :Array, element :Object) :void
{
removeImpl(arr, element, true);
}
/**
* Remove the last instance of the specified element from the array.
*/
public static function removeLast (arr :Array, element :Object) :void
{
arr.reverse();
removeFirst(arr, element);
arr.reverse();
}
/**
* Removes all instances of the specified element from the array.
*/
public static function removeAll (arr :Array, element :Object) :void
{
removeImpl(arr, element, false);
}
/**
* Implementation of remove methods.
*/
private static function removeImpl (
arr :Array, element :Object, firstOnly :Boolean) :void
{
for (var ii :int = 0; ii < arr.length; ii++) {
if (arr[ii] === element) {
arr.splice(ii--, 1);
if (firstOnly) {
return;
}
}
}
}
}
}
+3 -10
View File
@@ -19,7 +19,7 @@
// License along with this library; if not, write to the Free Software // License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util; package com.threerings.util {
/** /**
* A message bundle provides an easy mechanism by which to obtain * A message bundle provides an easy mechanism by which to obtain
@@ -54,14 +54,6 @@ public class MessageBundle
return getResourceString(key, false) != null; return getResourceString(key, false) != null;
} }
/**
* Get a String from the resource bundle, or null if there was an error.
*/
protected function getResourceString (key :String) :String
{
return getResourceString(key, true);
}
/** /**
* Get a String from the resource bundle, or null if there was an * Get a String from the resource bundle, or null if there was an
* error. * error.
@@ -71,7 +63,7 @@ public class MessageBundle
* if the resource didn't exist. * if the resource didn't exist.
*/ */
protected function getResourceString ( protected function getResourceString (
key :String, reportMissing :Boolean) :String key :String, reportMissing :Boolean = true) :String
{ {
// TODO!!! // TODO!!!
// try { // try {
@@ -391,3 +383,4 @@ public class MessageBundle
protected static const QUAL_PREFIX :String = "%"; protected static const QUAL_PREFIX :String = "%";
protected static const QUAL_SEP :String = ":"; protected static const QUAL_SEP :String = ":";
} }
}
+4 -4
View File
@@ -127,7 +127,7 @@ public class MessageManager
// } else { // } else {
rbundle = ResourceBundle.getBundle(fqpath, _locale); rbundle = ResourceBundle.getBundle(fqpath, _locale);
// } // }
} catch (MissingResourceException mre) { } catch (mre :Error) {
Log.warning("Unable to resolve resource bundle " + Log.warning("Unable to resolve resource bundle " +
"[path=" + fqpath + ", locale=" + _locale + "]."); "[path=" + fqpath + ", locale=" + _locale + "].");
} }
@@ -136,7 +136,7 @@ public class MessageManager
// interpret that as a derivation of MessageBundle to instantiate // interpret that as a derivation of MessageBundle to instantiate
// for handling that class // for handling that class
if (rbundle != null) { if (rbundle != null) {
String mbclass = null; var mbclass :String = null;
try { try {
mbclass = rbundle.getString(MBUNDLE_CLASS_KEY); mbclass = rbundle.getString(MBUNDLE_CLASS_KEY);
if (!StringUtil.isBlank(mbclass)) { if (!StringUtil.isBlank(mbclass)) {
@@ -144,10 +144,10 @@ public class MessageManager
Class.forName(mbclass).newInstance(); Class.forName(mbclass).newInstance();
} }
} catch (MissingResourceException mre) { } catch (mre :Error) {
// nothing to worry about // nothing to worry about
} catch (Throwable t) { } catch (t :Error) {
Log.warning("Failure instantiating custom message bundle " + Log.warning("Failure instantiating custom message bundle " +
"[mbclass=" + mbclass + ", error=" + t + "]."); "[mbclass=" + mbclass + ", error=" + t + "].");
} }
+1 -13
View File
@@ -12,19 +12,7 @@ public class StringUtil
*/ */
public static function trim (str :String) :String public static function trim (str :String) :String
{ {
while (str.search(/\s/) == 0) { return mx.utils.StringUtil.trim(str);
str = str.substring(1);
}
do {
var endstr :String = str.substring(str.length - 1);
if (endstr.search(/\s/) != -1) {
str = str.substring(0, str.length - 1);
} else {
break;
}
} while (true);
return str;
} }
} }
} }
+140
View File
@@ -0,0 +1,140 @@
//
// $Id: TimeUtil.java 3372 2005-03-01 01:16:01Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util {
/**
* Utility for times.
*/
public class TimeUtil
{
/** Time unit constant. */
public static const MILLISECOND :int = 0;
/** Time unit constant. */
public static const SECOND :int = 1;
/** Time unit constant. */
public static const MINUTE :int = 2;
/** Time unit constant. */
public static const HOUR :int = 3;
/** Time unit constant. */
public static const DAY :int = 4;
// TODO: Weeks?, months?
protected static const MAX_UNIT :int = DAY;
/**
* Get a translatable string specifying the magnitude of the specified
* duration. Results will be between "1 second" and "X hours", with
* all times rounded to the nearest unit. "0 units" will never be
* displayed, the minimum is 1.
*/
public static function getTimeOrderString (
duration :long, minUnit :int, maxUnit :int = -1) :String
{
// enforce sanity
if (maxUnit == -1) {
maxUnit = MAX_UNIT;
}
minUnit = Math.min(minUnit, maxUnit);
maxUnit = Math.min(maxUnit, MAX_UNIT);
for (var uu :int = MILLISECOND; uu <= MAX_UNIT; uu++) {
var quantity :int = getQuantityPerUnit(uu);
if ((minUnit <= uu) && (duration < quantity || maxUnit == uu)) {
duration = Math.max(1, duration);
return MessageBundle.tcompose(getTransKey(uu),
String(duration));
}
duration = Math.round(duration / quantity);
}
// will not happen, because eventually uu will be MAX_UNIT
return null;
}
/**
* Get a translatable string specifying the duration, down to the
* minimum granularity.
*/
public static function getTimeString (duration :long, minUnit :int) :String
{
// sanity
minUnit = Math.min(minUnit, MAX_UNIT);
duration = Math.abs(duration);
var list :Array = new Array();
var parts :int = 0; // how many parts are in the translation string?
for (var uu :int = MILLISECOND; uu <= MAX_UNIT; uu++) {
var quantity :int = getQuantityPerUnit(uu);
if (minUnit <= uu) {
list.push(MessageBundle.tcompose(getTransKey(uu),
String(duration % quantity)));
parts++;
}
duration /= quantity;
if (duration <= 0 && parts > 0) {
break;
}
}
if (parts == 1) {
return (list[0] as String);
} else {
return MessageBundle.compose("m.times_" + parts, list);
}
}
/**
* Internal method to get the quantity for the specified unit.
* (Not very OO)
*/
protected static function getQuantityPerUnit (unit :int) :int
{
switch (unit) {
case MILLISECOND: return 1000;
case SECOND: case MINUTE: return 60;
case HOUR: return 24;
case DAY: return Integer.MAX_VALUE;
default: return -1;
}
}
/**
* Internal method to get the translation key for the specified unit.
* (Not very OO)
*/
protected static function getTransKey (unit :int) :String
{
switch (unit) {
case MILLISECOND: return "m.millisecond";
case SECOND: return "m.second";
case MINUTE: return "m.minute";
case HOUR: return "m.hour";
case DAY: return "m.day";
default: return null;
}
}
}
}