From ed1be4a0f23da2d06d09de91d40ff90cf7988e4b Mon Sep 17 00:00:00 2001 From: Ray Greenwell Date: Wed, 15 Mar 2006 03:28:02 +0000 Subject: [PATCH] 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 --- .../crowd/chat/client/ChatDirector.as | 484 +++++++----------- .../crowd/chat/client/ChatDisplay.as | 4 +- .../crowd/chat/client/TellAdapter.as | 9 +- .../crowd/chat/data/SystemMessage.as | 3 + .../com/threerings/crowd/data/BodyObject.as | 4 +- .../com/threerings/crowd/data/OccupantInfo.as | 2 +- .../com/threerings/crowd/data/PlaceObject.as | 2 +- .../com/threerings/msoy/client/MsoyClient.as | 39 ++ .../com/threerings/msoy/client/MsoyContext.as | 74 +++ .../presents/client/BasicDirector.as | 2 +- .../presents/client/InvocationAdapter.as | 8 +- .../presents/client/InvocationDirector.as | 9 + .../threerings/presents/client/TestClient.as | 36 +- src/as/com/threerings/util/ArrayUtil.as | 50 ++ src/as/com/threerings/util/MessageBundle.as | 13 +- src/as/com/threerings/util/MessageManager.as | 8 +- src/as/com/threerings/util/StringUtil.as | 14 +- src/as/com/threerings/util/TimeUtil.as | 140 +++++ 18 files changed, 551 insertions(+), 350 deletions(-) create mode 100644 src/as/com/threerings/msoy/client/MsoyClient.as create mode 100644 src/as/com/threerings/msoy/client/MsoyContext.as create mode 100644 src/as/com/threerings/util/ArrayUtil.as create mode 100644 src/as/com/threerings/util/TimeUtil.as diff --git a/src/as/com/threerings/crowd/chat/client/ChatDirector.as b/src/as/com/threerings/crowd/chat/client/ChatDirector.as index 5930b58bf..676fbcec4 100644 --- a/src/as/com/threerings/crowd/chat/client/ChatDirector.as +++ b/src/as/com/threerings/crowd/chat/client/ChatDirector.as @@ -23,37 +23,8 @@ package com.threerings.crowd.chat.client { import mx.collections.ArrayCollection; - -// TODO: this class is in progress -// -// -// -// -// -// -// -// -// -// -// -// -// - -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.util.ArrayUtil; +import com.threerings.util.SimpleMap; import com.threerings.presents.client.BasicDirector; import com.threerings.presents.client.Client; @@ -104,12 +75,12 @@ public class ChatDirector extends BasicDirector super(ctx); // keep the context around - _ctx = ctx; + _cctx = ctx; _msgmgr = msgmgr; _bundle = bundle; // register ourselves as a location observer - _ctx.getLocationDirector().addLocationObserver(this); + _cctx.getLocationDirector().addLocationObserver(this); if (_bundle == null || _msgmgr == null) { 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. * @@ -271,8 +230,11 @@ public class ChatDirector extends BasicDirector * directly triggered by the user, nor requires direct action. */ 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); } @@ -415,7 +377,7 @@ public class ChatDirector extends BasicDirector } // 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; } - _cservice.broadcast(_ctx.getClient(), message, - new InvocationAdapter(broadcastFailed)); - } + var failure :Function = function (reason :String) :void + { + reason = MessageBundle.compose("m.broadcast_failed", reason); + displayFeedback(_bundle, reason); + }; - // Called above, when we fail to broadcast - private function broadcastFailed (reason :String) :void - { - reason = MessageBundle.compose("m.broadcast_failed", reason); - displayFeedback(_bundle, reason); + _cservice.broadcast(_cctx.getClient(), message, + new InvocationAdapter(failure)); } /** @@ -465,67 +426,50 @@ public class ChatDirector extends BasicDirector return; } - // create a listener that will report success or failure - ChatService.TellListener listener = new ChatService.TellListener() { - public void tellSucceeded (long idletime, String awayMessage) { + var failure :Function = function (reason :String) :void + { + 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, - new TellAdapter(tellFailed, tellSucceeded, [target, message, rl])); - } - - 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); - } + _cservice.tell(_cctx.getClient(), target, message, + new TellAdapter(failure, success)); } /** @@ -533,7 +477,7 @@ public class ChatDirector extends BasicDirector * that sends a tell message to this client to indicate that we are * busy or away from the keyboard. */ - public void setAwayMessage (String message) + public function setAwayMessage (message :String) :void { if (message != null) { message = filter(message, null, true); @@ -544,7 +488,7 @@ public class ChatDirector extends BasicDirector } } // 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 * that arrive on the specified DObject. */ - public void addAuxiliarySource (DObject source, String localtype) + public function addAuxiliarySource (source :DObject, localtype :String) :void { source.addListener(this); _auxes.put(source.getOid(), localtype); @@ -565,7 +509,7 @@ public class ChatDirector extends BasicDirector /** * Removes a previously added auxiliary chat source. */ - public void removeAuxiliarySource (DObject source) + public function removeAuxiliarySource (source :DObject) :void { source.removeListener(this); _auxes.remove(source.getOid()); @@ -587,20 +531,20 @@ public class ChatDirector extends BasicDirector /** * 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 - public boolean locationMayChange (int placeId) + // documentation inherited from interface LocationObserver + public function locationMayChange (placeId :int) :Boolean { // we accept all location change requests return true; } - // documentation inherited - public void locationDidChange (PlaceObject place) + // documentation inherited from interface LocationObserver + public function locationDidChange (place :PlaceObject) :void { if (_place != null) { // unlisten to our old object @@ -614,31 +558,31 @@ public class ChatDirector extends BasicDirector } } - // documentation inherited - public void locationChangeFailed (int placeId, String reason) + // documentation inherited from interface LocationObserver + public function locationChangeFailed (placeId :int, reason :String) :void { // nothing we care about } - // documentation inherited - public void messageReceived (MessageEvent event) + // documentation inherited from interface MessageListener + public function messageReceived (event :MessageEvent) :void { - if (CHAT_NOTIFICATION.equals(event.getName())) { - ChatMessage msg = (ChatMessage) event.getArgs()[0]; - String localtype = getLocalType(event.getTargetOid()); - String message = msg.message; - String autoResponse = null; - Name speaker = null; - byte mode = (byte) -1; + if (ChatCodes.CHAT_NOTIFICATION === event.getName()) { + var msg :ChatMessage = (event.getArgs()[0] as ChatMessage); + var localtype :String = getLocalType(event.getTargetOid()); + var message :String = msg.message; + var autoResponse :String = null; + var speaker :Name = null; + var mode :int = -1; // figure out if the message was triggered by another user - if (msg instanceof UserMessage) { - UserMessage umsg = (UserMessage)msg; + if (msg is UserMessage) { + var umsg :UserMessage = (msg as UserMessage); speaker = umsg.speaker; mode = umsg.mode; - } else if (msg instanceof UserSystemMessage) { - speaker = ((UserSystemMessage) msg).speaker; + } else if (msg is UserSystemMessage) { + speaker = (msg as UserSystemMessage).speaker; } // if there was an originating speaker, see if we want to hear it @@ -647,14 +591,14 @@ public class ChatDirector extends BasicDirector return; } - if (USER_CHAT_TYPE.equals(localtype) && - mode == ChatCodes.DEFAULT_MODE) { + if (ChatCodes.USER_CHAT_TYPE == localtype && + mode == ChatCodes.DEFAULT_MODE) { // if it was a tell, add the speaker as a chatter addChatter(speaker); // note whether or not we have an auto-response - BodyObject self = (BodyObject) - _ctx.getClient().getClientObject(); + var self :BodyObject = + (_cctx.getClient().getClientObject() as BodyObject); if (!StringUtil.isBlank(self.awayMessage)) { autoResponse = self.awayMessage; } @@ -669,7 +613,7 @@ public class ChatDirector extends BasicDirector // if we auto-responded, report as much if (autoResponse != null) { - String amsg = MessageBundle.tcompose( + var amsg :String = MessageBundle.tcompose( "m.auto_responded", speaker, autoResponse); displayFeedback(_bundle, amsg); } @@ -677,28 +621,30 @@ public class ChatDirector extends BasicDirector } // documentation inherited - public void clientDidLogon (Client client) + public override function clientDidLogon (client :Client) :void { super.clientDidLogon(client); // listen on the client object for tells - addAuxiliarySource(_clobj = client.getClientObject(), USER_CHAT_TYPE); + addAuxiliarySource(_clobj = client.getClientObject(), + ChatCodes.USER_CHAT_TYPE); } // documentation inherited - public void clientObjectDidChange (Client client) + public override function clientObjectDidChange (client :Client) :void { super.clientObjectDidChange(client); // change what we're listening to for tells removeAuxiliarySource(_clobj); - addAuxiliarySource(_clobj = client.getClientObject(), USER_CHAT_TYPE); + addAuxiliarySource(_clobj = client.getClientObject(), + ChatCodes.USER_CHAT_TYPE); clearDisplays(); } // documentation inherited - public void clientDidLogoff (Client client) + public override function clientDidLogoff (client :Client) :void { super.clientDidLogoff(client); @@ -712,9 +658,9 @@ public class ChatDirector extends BasicDirector clearDisplays(); - // clear out the list of people we've chatted with - _chatters.clear(); - notifyChatterObservers(); +// // clear out the list of people we've chatted with +// _chatters.clear(); +// notifyChatterObservers(); // clear the _place locationDidChange(null); @@ -732,8 +678,8 @@ public class ChatDirector extends BasicDirector * and has already been dealt with, or a translatable string * indicating the reason for rejection if not. */ - protected String checkCanChat ( - SpeakService speakSvc, String message, byte mode) + protected function checkCanChat ( + speakSvc :SpeakService , message :String, mode :int) :String { return null; } @@ -746,11 +692,11 @@ public class ChatDirector extends BasicDirector * @return {@link ChatCodes#SUCCESS} if the message was delivered or a * string indicating why it failed. */ - internal String deliverChat ( - SpeakService speakSvc, String message, byte mode) + internal function deliverChat ( + speakSvc :SpeakService, message :String, mode :int) :String { // 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 // 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 // chat message for some reason or other - String errmsg = checkCanChat(speakSvc, message, mode); + var errmsg :String = checkCanChat(speakSvc, message, mode); if (errmsg != null) { return errmsg; } @@ -775,17 +721,17 @@ public class ChatDirector extends BasicDirector /** * 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 - _history.remove(cmd); + ArrayUtil.removeAll(_history, cmd); // append it to the end - _history.add(cmd); + _history.push(cmd); // prune the history once it extends beyond max size - if (_history.size() > MAX_COMMAND_HISTORY) { - _history.remove(0); + if (_history.length > MAX_COMMAND_HISTORY) { + _history.shift(); } } @@ -798,19 +744,24 @@ public class ChatDirector extends BasicDirector * @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) + protected function mogrifyChatImpl ( + text :String, transformsAllowed :Boolean, capFirst :Boolean) :String { - int tlen = text.length(); + var tlen :int = 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))) { + var caps :int = 0; + for (var ii :int = 0; ii < tlen; 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++; if (caps > (tlen / 2)) { // lowercase the whole string if there are @@ -821,21 +772,19 @@ public class ChatDirector extends BasicDirector } } - StringBuffer buf = new StringBuffer(text); - buf = mogrifyChat(buf, transformsAllowed, capFirst); - return buf.toString(); + return mogrifyChatText(text, transformsAllowed, capFirst); } /** Helper function for {@link #mogrifyChat}. */ - protected StringBuffer mogrifyChat ( - StringBuffer buf, boolean transformsAllowed, boolean capFirst) + protected function mogrifyChatText ( + text :String, transformsAllowed :Boolean, capFirst :Boolean) :String { // do the generic mogrifications and translations - buf = translatedReplacements("x.mogrifies", buf); + text = translatedReplacements("x.mogrifies", text); // perform themed expansions and transformations 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 * 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)) { return buf; } - StringTokenizer st = new StringTokenizer(bundle.get(key), "#"); + var repls :Array = bundle.get(key).split("#"); // 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); - } + for (var ii :int = 0; ii < repls.length; ii++) { + var pattern :RegExp = new RegExp((repls[ii] as String), "gim"); + var replace :String = (repls[++ii] as String); + text = text.replace(pattern, replace); } - 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 * registered command string). */ - protected HashMap getCommandHandlers (String command) + protected function getCommandHandlers (command :String) :SimpleMap { - 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)) { + var matches :SimpleMap = new SimpleMap(); + var user :BodyObject = + (_cctx.getClient().getClientObject() as BodyObject); + var keys :Array = _handlers.keys(); + for (var ii :int = 0; ii < keys.length; ii++) { + var cmd :String = (keys[ii] as String); + if (cmd.indexOf(command) != 0) { continue; } - CommandHandler handler = (CommandHandler)entry.getValue(); - if (!handler.checkAccess(user)) { - continue; + var handler :CommandHandler = (_handlers.get(cmd) as CommandHandler); + if (handler.checkAccess(user)) { + matches.put(cmd, handler); } - matches.put(cmd, handler); } return matches; } @@ -917,48 +856,48 @@ public class ChatDirector extends BasicDirector /** * 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.. - 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(); - } +// // 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; - } - }); - } +// 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) + protected function xlate (bundle :String, message :String) :String { if (bundle != null && _msgmgr != null) { - MessageBundle msgb = _msgmgr.getBundle(bundle); + var msgb :MessageBundle = _msgmgr.getBundle(bundle); if (msgb == null) { Log.warning( "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. */ - protected void displaySystem ( - String bundle, String message, byte attLevel, String localtype) + protected function displaySystem ( + bundle :String, message :String, attLevel :int, + localtype :String) :void { // nothing should be untranslated, so pass the default bundle if need // be. if (bundle == null) { bundle = _bundle; } - SystemMessage msg = new SystemMessage(); + var msg :SystemMessage = new SystemMessage(); msg.attentionLevel = attLevel; msg.setClientInfo(xlate(bundle, message), localtype); dispatchMessage(msg); @@ -991,79 +931,21 @@ public class ChatDirector extends BasicDirector * Looks up and returns the message type associated with the specified * oid. */ - protected String getLocalType (int oid) + protected function getLocalType (oid :int) :String { - 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++; + var typ :String = (_auxes.get(oid) as String); + return (typ == null) ? ChatCodes.PLACE_CHAT_TYPE : typ; } // documentation inherited from interface - protected void fetchServices (Client client) + protected override function fetchServices (client :Client) :void { // get a handle on our chat service - _cservice = (ChatService)client.requireService(ChatService.class); - } - - /** - * 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; + _cservice = (client.requireService(ChatService) as ChatService); } /** Our active chat context. */ - protected var _ctx :CrowdContext; + protected var _cctx :CrowdContext; /** Provides access to chat-related server-side services. */ protected var _cservice :ChatService; @@ -1107,12 +989,6 @@ public class ChatDirector extends BasicDirector /** A history of chat commands. */ 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. */ protected static const MAX_CHATTERS :int = 6; diff --git a/src/as/com/threerings/crowd/chat/client/ChatDisplay.as b/src/as/com/threerings/crowd/chat/client/ChatDisplay.as index 2b67b2506..08f3d06c8 100644 --- a/src/as/com/threerings/crowd/chat/client/ChatDisplay.as +++ b/src/as/com/threerings/crowd/chat/client/ChatDisplay.as @@ -33,13 +33,13 @@ public interface ChatDisplay /** * Called to clear the chat display. */ - public function clear () :void; + function clear () :void; /** * Called to display a chat message. * * @see ChatMessage */ - public function displayMessage (msg :ChatMessage) :void; + function displayMessage (msg :ChatMessage) :void; } } diff --git a/src/as/com/threerings/crowd/chat/client/TellAdapter.as b/src/as/com/threerings/crowd/chat/client/TellAdapter.as index 97d00a02c..ee1576cfc 100644 --- a/src/as/com/threerings/crowd/chat/client/TellAdapter.as +++ b/src/as/com/threerings/crowd/chat/client/TellAdapter.as @@ -7,17 +7,16 @@ import com.threerings.presents.client.InvocationAdapter; public class TellAdapter extends InvocationAdapter implements TellListener { - public function TellAdapter ( - failedFunc :Function, successFunc :Function, args :Array = null) + public function TellAdapter (failedFunc :Function, successFunc :Function) { - super(failedFunc, args); + super(failedFunc); _successFunc = successFunc; } // 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. */ diff --git a/src/as/com/threerings/crowd/chat/data/SystemMessage.as b/src/as/com/threerings/crowd/chat/data/SystemMessage.as index 928f7f7bc..4514736fa 100644 --- a/src/as/com/threerings/crowd/chat/data/SystemMessage.as +++ b/src/as/com/threerings/crowd/chat/data/SystemMessage.as @@ -21,6 +21,9 @@ 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 * and did not result from direct user action. diff --git a/src/as/com/threerings/crowd/data/BodyObject.as b/src/as/com/threerings/crowd/data/BodyObject.as index 039807ef6..a5afb81da 100644 --- a/src/as/com/threerings/crowd/data/BodyObject.as +++ b/src/as/com/threerings/crowd/data/BodyObject.as @@ -145,7 +145,7 @@ public class BodyObject extends ClientObject */ public function setUsername (value :Name) :void { - Name ovalue = this.username; + var ovalue :Name = this.username; requestAttributeChange( USERNAME, value, ovalue); this.username = value; @@ -161,7 +161,7 @@ public class BodyObject extends ClientObject */ public function setLocation (value :int) :void { - int ovalue = this.location; + var ovalue :int = this.location; requestAttributeChange( LOCATION, value, ovalue); this.location = value; diff --git a/src/as/com/threerings/crowd/data/OccupantInfo.as b/src/as/com/threerings/crowd/data/OccupantInfo.as index 0bf8a9c13..cd9355b83 100644 --- a/src/as/com/threerings/crowd/data/OccupantInfo.as +++ b/src/as/com/threerings/crowd/data/OccupantInfo.as @@ -60,7 +60,7 @@ public class OccupantInfo public static const DISCONNECTED :int = 2; /** 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). */ public var bodyOid :Integer; diff --git a/src/as/com/threerings/crowd/data/PlaceObject.as b/src/as/com/threerings/crowd/data/PlaceObject.as index b3bdd818f..ef192b812 100644 --- a/src/as/com/threerings/crowd/data/PlaceObject.as +++ b/src/as/com/threerings/crowd/data/PlaceObject.as @@ -156,7 +156,7 @@ public class PlaceObject extends DObject public function setOccupantInfo (value :DSet) :void { requestAttributeChange(OCCUPANT_INFO, value, this.occupantInfo); - this.occupantInfo = (value == null) ? null : (DSet)value.clone(); + this.occupantInfo = (value == null) ? null : value; } /** diff --git a/src/as/com/threerings/msoy/client/MsoyClient.as b/src/as/com/threerings/msoy/client/MsoyClient.as new file mode 100644 index 000000000..dbfa8bdc0 --- /dev/null +++ b/src/as/com/threerings/msoy/client/MsoyClient.as @@ -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; +} +} diff --git a/src/as/com/threerings/msoy/client/MsoyContext.as b/src/as/com/threerings/msoy/client/MsoyContext.as new file mode 100644 index 000000000..ed04fb1d0 --- /dev/null +++ b/src/as/com/threerings/msoy/client/MsoyContext.as @@ -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; +} +} diff --git a/src/as/com/threerings/presents/client/BasicDirector.as b/src/as/com/threerings/presents/client/BasicDirector.as index 4d1a19ed6..6dd385f5a 100644 --- a/src/as/com/threerings/presents/client/BasicDirector.as +++ b/src/as/com/threerings/presents/client/BasicDirector.as @@ -38,7 +38,7 @@ public class BasicDirector * context that it can use to register itself with the necessary * entities. */ - protected function BasicDirector (ctx :PresentsContext) + public function BasicDirector (ctx :PresentsContext) { // save context _ctx = ctx; diff --git a/src/as/com/threerings/presents/client/InvocationAdapter.as b/src/as/com/threerings/presents/client/InvocationAdapter.as index 55e9b0453..d0b69ee5e 100644 --- a/src/as/com/threerings/presents/client/InvocationAdapter.as +++ b/src/as/com/threerings/presents/client/InvocationAdapter.as @@ -7,22 +7,18 @@ public class InvocationAdapter * Construct an InvocationAdapter that will call the specified * function on error. */ - public function InvocationAdapter (failedFunc :Function, args :Array = null) + public function InvocationAdapter (failedFunc :Function) { _failedFunc = failedFunc; - _args = args; } // documentation inherited from interface InvocationListener public function requestFailed (cause :String) :void { - _failedFunc(cause, _args); + _failedFunc(cause); } /** The Function to call when we've recevied our failure response. */ protected var _failedFunc :Function; - - /** Any other extra information to pass along to the function. */ - protected var _args :Array; } } diff --git a/src/as/com/threerings/presents/client/InvocationDirector.as b/src/as/com/threerings/presents/client/InvocationDirector.as index e9ae485e7..61c91f210 100644 --- a/src/as/com/threerings/presents/client/InvocationDirector.as +++ b/src/as/com/threerings/presents/client/InvocationDirector.as @@ -35,6 +35,15 @@ public class InvocationDirector _omgr = omgr; _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)); } diff --git a/src/as/com/threerings/presents/client/TestClient.as b/src/as/com/threerings/presents/client/TestClient.as index 70f688f88..c0cda5d08 100644 --- a/src/as/com/threerings/presents/client/TestClient.as +++ b/src/as/com/threerings/presents/client/TestClient.as @@ -11,6 +11,11 @@ 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 TestClient extends Client { public function TestClient () @@ -61,7 +66,18 @@ public class TestClient extends Client list.addItem(bob); Log.debug("jim's indeX: " + list.getItemIndex(jim)); 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); } + 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. // Here, I hack. @@ -117,19 +140,30 @@ public class TestClient extends Client 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 + ": " + arguments); } prototype var _foo :String; + + protected var _savedFunc :Function; + protected var _listy :ArrayCollection; } } import com.threerings.presents.Log; import com.threerings.presents.client.TestClient; +interface Aint { + function foo () :void; +} + +interface Bint extends Aint { + function bar () :void; +} + class Person { public function Person (name :String) diff --git a/src/as/com/threerings/util/ArrayUtil.as b/src/as/com/threerings/util/ArrayUtil.as new file mode 100644 index 000000000..f7e84e2aa --- /dev/null +++ b/src/as/com/threerings/util/ArrayUtil.as @@ -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; + } + } + } + } +} +} diff --git a/src/as/com/threerings/util/MessageBundle.as b/src/as/com/threerings/util/MessageBundle.as index 2dfb9efdc..54acc4ec6 100644 --- a/src/as/com/threerings/util/MessageBundle.as +++ b/src/as/com/threerings/util/MessageBundle.as @@ -19,7 +19,7 @@ // 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; +package com.threerings.util { /** * A message bundle provides an easy mechanism by which to obtain @@ -54,14 +54,6 @@ public class MessageBundle 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 * error. @@ -71,7 +63,7 @@ public class MessageBundle * if the resource didn't exist. */ protected function getResourceString ( - key :String, reportMissing :Boolean) :String + key :String, reportMissing :Boolean = true) :String { // TODO!!! // try { @@ -391,3 +383,4 @@ public class MessageBundle protected static const QUAL_PREFIX :String = "%"; protected static const QUAL_SEP :String = ":"; } +} diff --git a/src/as/com/threerings/util/MessageManager.as b/src/as/com/threerings/util/MessageManager.as index e1946bea8..9dde0dc51 100644 --- a/src/as/com/threerings/util/MessageManager.as +++ b/src/as/com/threerings/util/MessageManager.as @@ -127,7 +127,7 @@ public class MessageManager // } else { rbundle = ResourceBundle.getBundle(fqpath, _locale); // } - } catch (MissingResourceException mre) { + } catch (mre :Error) { Log.warning("Unable to resolve resource bundle " + "[path=" + fqpath + ", locale=" + _locale + "]."); } @@ -136,7 +136,7 @@ public class MessageManager // interpret that as a derivation of MessageBundle to instantiate // for handling that class if (rbundle != null) { - String mbclass = null; + var mbclass :String = null; try { mbclass = rbundle.getString(MBUNDLE_CLASS_KEY); if (!StringUtil.isBlank(mbclass)) { @@ -144,10 +144,10 @@ public class MessageManager Class.forName(mbclass).newInstance(); } - } catch (MissingResourceException mre) { + } catch (mre :Error) { // nothing to worry about - } catch (Throwable t) { + } catch (t :Error) { Log.warning("Failure instantiating custom message bundle " + "[mbclass=" + mbclass + ", error=" + t + "]."); } diff --git a/src/as/com/threerings/util/StringUtil.as b/src/as/com/threerings/util/StringUtil.as index 3a078a43e..e42095ba9 100644 --- a/src/as/com/threerings/util/StringUtil.as +++ b/src/as/com/threerings/util/StringUtil.as @@ -12,19 +12,7 @@ public class StringUtil */ public static function trim (str :String) :String { - while (str.search(/\s/) == 0) { - 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; + return mx.utils.StringUtil.trim(str); } } } diff --git a/src/as/com/threerings/util/TimeUtil.as b/src/as/com/threerings/util/TimeUtil.as new file mode 100644 index 000000000..6fa467f16 --- /dev/null +++ b/src/as/com/threerings/util/TimeUtil.as @@ -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; + } + } +} +}