diff --git a/src/as/com/threerings/parlor/client/DefaultFlexTableConfigurator.as b/src/as/com/threerings/parlor/client/DefaultFlexTableConfigurator.as
new file mode 100644
index 00000000..86275562
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/DefaultFlexTableConfigurator.as
@@ -0,0 +1,123 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2005 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.parlor.client {
+
+import mx.controls.CheckBox;
+import mx.controls.HSlider;
+import mx.controls.Label;
+
+import com.threerings.parlor.data.TableConfig;
+import com.threerings.parlor.util.ParlorContext;
+
+import com.threerings.parlor.game.client.FlexGameConfigurator;
+import com.threerings.parlor.game.data.GameConfig;
+
+/**
+ * Provides a default implementation of a TableConfigurator for
+ * a Swing interface.
+ */
+public class DefaultFlexTableConfigurator extends TableConfigurator
+{
+ /**
+ * Create a TableConfigurator that allows for the specified configuration
+ * parameters.
+ */
+ public function DefaultSwingTableConfigurator (
+ desiredPlayers :int, minPlayers :int = -1, maxPlayers :int = -1,
+ allowPrivate :Boolean = false)
+ {
+ if (minPlayers < 0) {
+ minPlayers = desiredPlayers;
+ }
+ if (maxPlayers < 0) {
+ maxPlayers = desiredPlayers;
+ }
+
+ _config.minimumPlayerCount = minPlayers;
+
+ // create a slider for players, if applicable
+ if (minPlayers != maxPlayers) {
+ _playerSlider = new HSlider();
+ _playerSlider.value = desiredPlayers;
+ _playerSlider.minimum = minPlayers;
+ _playerSlider.maximum = maxPlayers;
+
+ } else {
+ _config.desiredPlayerCount = desiredPlayers;
+ }
+
+ // create up the checkbox for private games, if applicable
+ if (allowPrivate) {
+ _privateCheck = new CheckBox();
+ }
+ }
+
+ // documentation inherited
+ protected function createConfigInterface () :void
+ {
+ super.createConfigInterface();
+
+ var gconf :FlexGameConfigurator =
+ (_gameConfigurator as FlexGameConfigurator);
+
+ if (_playerSlider != null) {
+ // TODO: proper translation
+ var playerLabel :Label = new Label();
+ playerLabel.text = "Players:";
+ gconf.addControl(playerLabel, _playerSlider);
+ }
+
+ if (_privateCheck != null) {
+ // TODO: proper translation
+ var privateLabel :Label = new Label();
+ privateLabel.text = "Private:";
+ gconf.addControl(privateLabel, _privateCheck);
+ }
+ }
+
+ // documentation inherited
+ override public function isEmpty () :Boolean
+ {
+ return (_playerSlider == null) && (_privateCheck == null);
+ }
+
+ // documentation inherited
+ override protected function flushTableConfig() :void
+ {
+ super.flushTableConfig();
+
+ if (_playerSlider != null) {
+ _config.desiredPlayerCount = _playerSlider.value;
+ }
+ if (_privateCheck != null) {
+ _config.privateTable = _privateCheck.selected;
+ }
+ }
+
+ /** A slider for configuring the number of players at the table. */
+ protected var _playerSlider :HSlider;
+
+ /** A checkbox to allow the table creator to specify if the table is
+ * private. */
+ protected var _privateCheck :CheckBox;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/GameReadyObserver.as b/src/as/com/threerings/parlor/client/GameReadyObserver.as
new file mode 100644
index 00000000..856a341a
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/GameReadyObserver.as
@@ -0,0 +1,44 @@
+//
+// $Id: GameReadyObserver.java 4191 2006-06-13 22:42:20Z 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.parlor.client {
+
+/**
+ * Used to inform interested parties when the {@link ParlorDirector}
+ * receives a game ready notification. The observers can ratify the
+ * decision to head directly into the game or can take responsibility
+ * themselves for doing so.
+ */
+public interface GameReadyObserver
+{
+ /**
+ * Called when a game ready notification is received.
+ *
+ * @param gameOid the place oid of the ready game.
+ *
+ * @return if the observer returns true from this method, the parlor
+ * director assumes they will take care of entering the game room
+ * after performing processing of their own. If all observers return
+ * false, the director will enter the game room automatically.
+ */
+ function receivedGameReady (gameOid :int) :Boolean;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/Invitation.as b/src/as/com/threerings/parlor/client/Invitation.as
new file mode 100644
index 00000000..7fab2ca3
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/Invitation.as
@@ -0,0 +1,214 @@
+//
+// $Id: Invitation.java 3381 2005-03-03 19:36:34Z mdb $
+//
+// 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.parlor.client {
+
+import com.threerings.util.Name;
+
+import com.threerings.parlor.data.ParlorCodes;
+import com.threerings.parlor.game.data.GameConfig;
+import com.threerings.parlor.util.ParlorContext;
+
+/**
+ * The invitation class is used to track information related to
+ * outstanding invitations generated by or targeted to this client.
+ */
+public class Invitation
+ implements ParlorCodes, ParlorService_InviteListener
+{
+ /** The unique id for this invitation (as assigned by the
+ * server). This is -1 until we receive an acknowledgement from
+ * the server that our invitation was delivered. */
+ public var inviteId :int = -1;
+
+ /** The name of the other user involved in this invitation. */
+ public var opponent :Name;
+
+ /** The configuration of the game to be created. */
+ public var config :GameConfig;
+
+ /** Constructs a new invitation record. */
+ public function Invitation (
+ ctx :ParlorContext, pservice :ParlorService,
+ opponent :Name, config :GameConfig,
+ observer :InvitationResponseObserver)
+ {
+ _ctx = ctx;
+ _pservice = pservice;
+ _observer = observer;
+ this.opponent = opponent;
+ this.config = config;
+ }
+
+ /**
+ * Accepts this invitation.
+ */
+ public function accept () :void
+ {
+ // generate the invocation service request
+ _pservice.respond(_ctx.getClient(), inviteId,
+ INVITATION_ACCEPTED, null, this);
+ }
+
+ /**
+ * Refuses this invitation.
+ *
+ * @param message the message to deliver to the inviting user
+ * explaining the reason for the refusal or null if no message is to
+ * be provided.
+ */
+ public function refuse (message :String) :void
+ {
+ // generate the invocation service request
+ _pservice.respond(_ctx.getClient(), inviteId,
+ INVITATION_REFUSED, message, this);
+ }
+
+ /**
+ * Cancels this invitation.
+ */
+ public function cancel () :void
+ {
+ // if the invitation has not yet been acknowleged by the
+ // server, we make a note that it should be cancelled when we
+ // do receive the acknowlegement
+ if (inviteId == -1) {
+ _cancelled = true;
+
+ } else {
+ // otherwise, generate the invocation service request
+ _pservice.cancel(_ctx.getClient(), inviteId, this);
+ // and remove it from the pending table
+ _ctx.getParlorDirector().clearInvitation(this);
+ }
+ }
+
+ /**
+ * Counters this invitation with an invitation with different game
+ * configuration parameters.
+ *
+ * @param config the updated game configuration.
+ * @param observer the entity that will be notified if this
+ * counter-invitation is accepted, refused or countered.
+ */
+ public function counter (
+ config :GameConfig, observer :InvitationResponseObserver) :void
+ {
+ // update our observer (who will eventually be hearing back from
+ // the other client about their counter-invitation)
+ _observer = observer;
+
+ // generate the invocation service request
+ _pservice.respond(_ctx.getClient(), inviteId,
+ INVITATION_COUNTERED, config, this);
+ }
+
+ // documentation inherited from interface
+ public function inviteReceived (inviteId :int) :void
+ {
+ // fill in our invitation id
+ this.inviteId = inviteId;
+
+ // if the invitation was cancelled before we heard back about
+ // it, we need to send off a cancellation request now
+ if (_cancelled) {
+ _pservice.cancel(_ctx.getClient(), inviteId, this);
+ } else {
+ // otherwise, put it in the pending invites table
+ _ctx.getParlorDirector().registerInvitation(this);
+ }
+ }
+
+ // documentation inherited from interface
+ public function requestFailed (reason :String) :void
+ {
+ // let the observer know what's up
+ _observer.invitationRefused(this, reason);
+ }
+
+ /**
+ * Called by the parlor director when we receive a response to an
+ * invitation initiated by this client.
+ */
+ protected function receivedResponse (code :int, arg :Object) :void
+ {
+ // make sure we have an observer to notify
+ if (_observer == null) {
+ Log.getLog(this).warning("No observer registered for invitation " +
+ this + ".");
+ return;
+ }
+
+ // notify the observer
+ try {
+ switch (code) {
+ case INVITATION_ACCEPTED:
+ _observer.invitationAccepted(this);
+ break;
+
+ case INVITATION_REFUSED:
+ _observer.invitationRefused(this, (arg as String));
+ break;
+
+ case INVITATION_COUNTERED:
+ _observer.invitationCountered(this, (arg as GameConfig));
+ break;
+ }
+
+ } catch (err :Error) {
+ var log :Log = Log.getLog(this);
+ log.warning("Invitation response observer choked on response " +
+ "[code=" + code + ", arg=" + arg +
+ ", invite=" + this + "].");
+ log.logStackTrace(e);
+ }
+
+ // unless the invitation was countered, we can remove it from the
+ // pending table because it's resolved
+ if (code != INVITATION_COUNTERED) {
+ _ctx.getParlorDirector().clearInvitation(this);
+ }
+ }
+
+ /** Returns a string representation of this invitation record. */
+ public function toString () :String
+ {
+ return "[inviteId=" + inviteId + ", opponent=" + opponent +
+ ", config=" + config + ", observer=" + _observer +
+ ", cancelled=" + _cancelled + "]";
+ }
+
+ /** Provides access to client services. */
+ protected var _ctx :ParlorContext;
+
+ /** Provides access to parlor services. */
+ protected var _pservice :ParlorService;
+
+ /** The entity to notify when we receive a response for this
+ * invitation. */
+ protected var _observer :InvitationResponseObserver;
+
+ /** A flag indicating that we were requested to cancel this
+ * invitation before we even heard back with an acknowledgement
+ * that it was received by the server. */
+ protected var _cancelled :Boolean = false;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/InvitationHandler.as b/src/as/com/threerings/parlor/client/InvitationHandler.as
new file mode 100644
index 00000000..69b76fc4
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/InvitationHandler.as
@@ -0,0 +1,46 @@
+//
+// $Id: InvitationHandler.java 4191 2006-06-13 22:42:20Z 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.parlor.client {
+
+/**
+ * A client entity that wishes to handle invitations received by other
+ * clients should implement this interface and register itself with the
+ * parlor director. It will subsequently be notified of any incoming
+ * invitations. It is also responsible for handling cancelled invitations.
+ */
+public interface InvitationHandler
+{
+ /**
+ * Called when an invitation is received from another player.
+ *
+ * @param invite the received invitation.
+ */
+ function invitationReceived (invite :Invitation) :void;
+
+ /**
+ * Called when an invitation is cancelled by the inviting player.
+ *
+ * @param invite the cancelled invitation.
+ */
+ function invitationCancelled (invite :Invitation) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/InvitationResponseObserver.as b/src/as/com/threerings/parlor/client/InvitationResponseObserver.as
new file mode 100644
index 00000000..f049f90b
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/InvitationResponseObserver.as
@@ -0,0 +1,62 @@
+//
+// $Id: InvitationResponseObserver.java 3381 2005-03-03 19:36:34Z mdb $
+//
+// 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.parlor.client {
+
+import com.threerings.parlor.game.data.GameConfig;
+
+/**
+ * A client entity that wishes to generate invitations for games must
+ * implement this interface. An invitation can be accepted, refused or
+ * countered. A countered invitation is one where the game configuration
+ * is adjusted by the invited player and proposed back to the inviting
+ * player.
+ */
+public interface InvitationResponseObserver
+{
+ /**
+ * Called if the invitation was accepted.
+ *
+ * @param invite the invitation for which we received a response.
+ */
+ function invitationAccepted (invite :Invitation) :void;
+
+ /**
+ * Called if the invitation was refused.
+ *
+ * @param invite the invitation for which we received a response.
+ * @param message a message provided by the invited user explaining
+ * the reason for their refusal, or the empty string if no message was
+ * provided.
+ */
+ function invitationRefused (invite :Invitation, message :String) :void;
+
+ /**
+ * Called if the invitation was countered with an alternate game
+ * configuration.
+ *
+ * @param invite the invitation for which we received a response.
+ * @param config the game configuration proposed by the invited
+ * player.
+ */
+ function invitationCountered (invite :Invitation, config :GameConfig) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/ParlorDecoder.as b/src/as/com/threerings/parlor/client/ParlorDecoder.as
new file mode 100644
index 00000000..4dd9d60c
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/ParlorDecoder.as
@@ -0,0 +1,103 @@
+//
+// $Id: ParlorDecoder.java 4145 2006-05-24 01:24:24Z ray $
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2006 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.parlor.client {
+
+import com.threerings.parlor.client.ParlorReceiver;
+import com.threerings.parlor.game.data.GameConfig;
+import com.threerings.presents.client.InvocationDecoder;
+import com.threerings.util.Name;
+
+/**
+ * Dispatches calls to a {@link ParlorReceiver} instance.
+ */
+public class ParlorDecoder extends InvocationDecoder
+{
+ /** The generated hash code used to identify this receiver class. */
+ public static const RECEIVER_CODE :String = "5ef9ee0d359c42a9024498ee9aad119a";
+
+ /** The method id used to dispatch {@link ParlorReceiver#gameIsReady}
+ * notifications. */
+ public static const GAME_IS_READY :int = 1;
+
+ /** The method id used to dispatch {@link ParlorReceiver#receivedInvite}
+ * notifications. */
+ public static const RECEIVED_INVITE :int = 2;
+
+ /** The method id used to dispatch {@link ParlorReceiver#receivedInviteCancellation}
+ * notifications. */
+ public static const RECEIVED_INVITE_CANCELLATION :int = 3;
+
+ /** The method id used to dispatch {@link ParlorReceiver#receivedInviteResponse}
+ * notifications. */
+ public static const RECEIVED_INVITE_RESPONSE :int = 4;
+
+ /**
+ * Creates a decoder that may be registered to dispatch invocation
+ * service notifications to the specified receiver.
+ */
+ public function ParlorDecoder (receiver :ParlorReceiver)
+ {
+ this.receiver = receiver;
+ }
+
+ // documentation inherited
+ override public function getReceiverCode () :String
+ {
+ return RECEIVER_CODE;
+ }
+
+ // documentation inherited
+ override public function dispatchNotification (
+ methodId :int, args :Array) :void
+ {
+ var prec :ParlorReceiver = (receiver as ParlorReceiver);
+ switch (methodId) {
+ case GAME_IS_READY:
+ prec.gameIsReady((args[0] as Integer).value);
+ return;
+
+ case RECEIVED_INVITE:
+ prec.receivedInvite(
+ (args[0] as Integer).value, (args[1] as Name),
+ (args[2] as GameConfig)
+ );
+ return;
+
+ case RECEIVED_INVITE_CANCELLATION:
+ prec.receivedInviteCancellation(
+ (args[0] as Integer).value
+ );
+ return;
+
+ case RECEIVED_INVITE_RESPONSE:
+ prec.receivedInviteResponse(
+ (args[0] as Integer).value, (args[1] as Integer).value, args[2]
+ );
+ return;
+
+ default:
+ super.dispatchNotification(methodId, args);
+ return;
+ }
+ }
+}
+}
diff --git a/src/as/com/threerings/parlor/client/ParlorDirector.as b/src/as/com/threerings/parlor/client/ParlorDirector.as
new file mode 100644
index 00000000..764cff21
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/ParlorDirector.as
@@ -0,0 +1,252 @@
+//
+// $Id: ParlorDirector.java 3381 2005-03-03 19:36:34Z mdb $
+//
+// 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.parlor.client {
+
+import com.threerings.util.ArrayUtil;
+import com.threerings.util.HashMap;
+import com.threerings.util.Name;
+
+import com.threerings.presents.client.BasicDirector;
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationService;
+
+import com.threerings.parlor.Log;
+import com.threerings.parlor.data.ParlorCodes;
+import com.threerings.parlor.game.data.GameConfig;
+import com.threerings.parlor.util.ParlorContext;
+
+/**
+ * The parlor director manages the client side of the game configuration
+ * and matchmaking processes. It is also the entity that is listening for
+ * game start notifications which it then dispatches the client entity
+ * that will actually create and display the user interface for the game
+ * that started.
+ */
+public class ParlorDirector extends BasicDirector
+ implements ParlorCodes, ParlorReceiver
+{
+ /**
+ * Constructs a parlor director and provides it with the parlor
+ * context that it can use to access the client services that it needs
+ * to provide its own services. Only one parlor director should be
+ * active in the client at one time and it should be made available
+ * via the parlor context.
+ *
+ * @param ctx the parlor context in use by the client.
+ */
+ public function ParlorDirector (ctx :ParlorContext)
+ {
+ super(ctx);
+ _pctx = ctx;
+
+ // register ourselves with the invocation director as a parlor
+ // notification receiver
+ _pctx.getClient().getInvocationDirector().registerReceiver(
+ new ParlorDecoder(this));
+ }
+
+ /**
+ * Sets the invitation handler, which is the entity that will be
+ * notified when we receive incoming invitation notifications and when
+ * invitations have been cancelled.
+ *
+ * @param handler our new invitation handler.
+ */
+ public function setInvitationHandler (handler :InvitationHandler) :void
+ {
+ _handler = handler;
+ }
+
+ /**
+ * Adds the specified observer to the list of entities that are
+ * notified when we receive a game ready notification.
+ */
+ public function addGameReadyObserver (observer :GameReadyObserver) :void
+ {
+ _grobs.add(observer);
+ }
+
+ /**
+ * Removes the specified observer from the list of entities that are
+ * notified when we receive a game ready notification.
+ */
+ public function removeGameReadyObserver (observer :GameReadyObserver) :void
+ {
+ ArrayUtil.removeFirst(_grobs, observer);
+ }
+
+ /**
+ * Requests that the named user be invited to a game described by the
+ * supplied game config.
+ *
+ * @param invitee the user to invite.
+ * @param config the configuration of the game to which the user is
+ * being invited.
+ * @param observer the entity that will be notified if this invitation
+ * is accepted, refused or countered.
+ *
+ * @return an invitation object that can be used to manage the
+ * outstanding invitation.
+ */
+ public function invite (
+ invitee :Name, config :GameConfig,
+ observer :InvitationResponseObserver) :Invitation
+ {
+ // create the invitation record
+ var invite :Invitation = new Invitation(
+ _ctx, _pservice, invitee, config, observer);
+ // submit the invitation request to the server
+ _pservice.invite(_ctx.getClient(), invitee, config, invite);
+ // and return the invitation to the caller
+ return invite;
+ }
+
+ /**
+ * Requests that the specified single player game be started.
+ *
+ * @param config the configuration of the single player game to be
+ * started.
+ * @param listener a listener to be informed of failure if the game
+ * cannot be started.
+ */
+ public function startSolitaire (
+ config :GameConfig, listener :InvocationService.ConfirmListener) :void
+ {
+ _pservice.startSolitaire(_ctx.getClient(), config, listener);
+ }
+
+ // documentation inherited
+ public function clientDidLogoff (event :ClientEvent) :void
+ {
+ super.clientDidLogoff(event);
+ _pservice = null;
+ _pendingInvites.clear();
+ }
+
+ // documentation inherited
+ protected function fetchServices (client :Client) :void
+ {
+ // get a handle on our parlor services
+ _pservice = (client.requireService(ParlorService) as ParlorService);
+ }
+
+ // documentation inherited from interface
+ public function gameIsReady (gameOid :int) :void
+ {
+ Log.getLog(this).info("Handling game ready [goid=" + gameOid + "].");
+
+ // see what our observers have to say about it
+ var handled :Boolean = false;
+ for (var ii :int = 0; ii < _grobs.length; ii++) {
+ var grob :GameReadyObserver = (_grobs[ii] as GameReadyObserver);
+ handled = grob.receivedGameReady(gameOid) || handled;
+ }
+
+ // if none of the observers took matters into their own hands,
+ // then we'll head on over to the game room ourselves
+ if (!handled) {
+ _ctx.getLocationDirector().moveTo(gameOid);
+ }
+ }
+
+ // documentation inherited from interface
+ public function receivedInvite (
+ remoteId :int, inviter :Name, config :GameConfig) :void
+ {
+ // create an invitation record for this invitation
+ invite :Invitation = new Invitation(
+ _ctx, _pservice, inviter, config, null);
+ invite.inviteId = remoteId;
+
+ // put it in the pending invitations table
+ _pendingInvites.put(remoteId, invite);
+
+ try {
+ // notify the invitation handler of the incoming invitation
+ _handler.invitationReceived(invite);
+
+ } catch (err :Error) {
+ var log :Log = Log.getLog(this);
+ log.warning("Invitation handler choked on invite " +
+ "notification " + invite + ".");
+ log.logStackTrace(err);
+ }
+ }
+
+ // documentation inherited from interface
+ public function receivedInviteResponse (
+ remoteId :int, code :int, arg :Object) :void
+ {
+ // look up the invitation record for this invitation
+ var invite :Invitation = (_pendingInvites.get(remoteId) as Invitation);
+ if (invite == null) {
+ Log.getLog(this).warning("Have no record of invitation for " +
+ "which we received a response?! [remoteId=" + remoteId +
+ ", code=" + code + ", arg=" + arg + "].");
+
+ } else {
+ invite.receivedResponse(code, arg);
+ }
+ }
+
+ // documentation inherited from interface
+ public function receivedInviteCancellation (remoteId :int) :void
+ {
+ // TBD
+ }
+
+ /**
+ * Register a new invitation in our pending invitations table. The
+ * invitation will call this when it knows its invitation id.
+ */
+ protected function registerInvitation (invite :Invitation) :void
+ {
+ _pendingInvites.put(invite.inviteId, invite);
+ }
+
+ /**
+ * Called by an invitation when it knows it is no longer and can be
+ * cleared from the pending invitations table.
+ */
+ protected function clearInvitation (invite :Invitation) :void
+ {
+ _pendingInvites.remove(invite.inviteId);
+ }
+
+ /** An active parlor context. */
+ protected var _pctx :ParlorContext;
+
+ /** Provides access to parlor server side services. */
+ protected var _pservice :ParlorService;
+
+ /** The entity that has registered itself to handle incoming
+ * invitation notifications. */
+ protected var _handler :InvitationHandler;
+
+ /** A table of acknowledged (but not yet accepted or refused)
+ * invitation requests, keyed on invitation id. */
+ protected var _pendingInvites :HashMap = new HashMap();
+
+ /** We notify the entities on this list when we get a game ready
+ * notification. */
+ protected var _grobs :Array = new Array();
+}
diff --git a/src/as/com/threerings/parlor/client/ParlorReceiver.as b/src/as/com/threerings/parlor/client/ParlorReceiver.as
new file mode 100644
index 00000000..ed4199ae
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/ParlorReceiver.as
@@ -0,0 +1,85 @@
+//
+// $Id: ParlorReceiver.java 3381 2005-03-03 19:36:34Z mdb $
+//
+// 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.parlor.client {
+
+import com.threerings.util.Name;
+
+import com.threerings.presents.client.InvocationReceiver;
+
+import com.threerings.parlor.data.ParlorCodes;
+import com.threerings.parlor.game.data.GameConfig;
+
+/**
+ * Defines, for the parlor services, a set of notifications delivered
+ * asynchronously by the server to the client. These are handled by the
+ * {@link ParlorDirector}.
+ */
+public interface ParlorReceiver extends InvocationReceiver
+{
+ /**
+ * Dispatched to the client when a game in which they are a
+ * participant is ready for play. The client will then enter the game
+ * room which will trigger the loading of the appropriate game UI code
+ * and generally get things started.
+ *
+ * @param gameOid the object id of the game object.
+ */
+ function gameIsReady (gameOid :int) :void;
+
+ /**
+ * Called by the invocation services when another user has invited us
+ * to play a game.
+ *
+ * @param remoteId the unique indentifier for this invitation (used
+ * when countering or responding).
+ * @param inviter the username of the inviting user.
+ * @param config the configuration information for the game to which
+ * we've been invited.
+ */
+ function receivedInvite (remoteId :int, inviter :Name, config :GameConfig);
+
+ /**
+ * Called by the invocation services when another user has responded
+ * to our invitation by either accepting, refusing or countering it.
+ *
+ * @param remoteId the indentifier for the invitation on question.
+ * @param code the response code, either {@link
+ * ParlorCodes#INVITATION_ACCEPTED} or {@link
+ * ParlorCodes#INVITATION_REFUSED} or {@link
+ * ParlorCodes#INVITATION_COUNTERED}.
+ * @param arg in the case of a refused invitation, a string
+ * containing a message provided by the invited user explaining the
+ * reason for refusal (the empty string if no explanation was
+ * provided). In the case of a countered invitation, a new game config
+ * object with the modified game configuration.
+ */
+ function receivedInviteResponse (remoteId :int, code :int, arg :Object);
+
+ /**
+ * Called by the invocation services when an outstanding invitation
+ * has been cancelled by the inviting user.
+ *
+ * @param remoteId the indentifier of the cancelled invitation.
+ */
+ function receivedInviteCancellation (remoteId :int);
+}
+}
diff --git a/src/as/com/threerings/parlor/client/ParlorService.as b/src/as/com/threerings/parlor/client/ParlorService.as
new file mode 100644
index 00000000..c9d1d578
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/ParlorService.as
@@ -0,0 +1,151 @@
+//
+// $Id: ParlorService.java 3525 2005-04-26 02:38:42Z 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.parlor.client {
+
+import com.threerings.util.Name;
+
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationService;
+
+import com.threerings.parlor.data.TableConfig;
+
+import com.threerings.parlor.game.data.GameConfig;
+
+/**
+ * Provides an interface to the various parlor invocation services.
+ * Presently these services are limited to the various matchmaking
+ * mechanisms. It is unlikely that client code will want to make direct
+ * use of this class, instead they would make use of the programmatic
+ * interface provided by the {@link ParlorDirector}.
+ */
+public interface ParlorService extends InvocationService
+{
+ /**
+ * You probably don't want to call this directly, but want to generate
+ * your invitation request via {@link ParlorDirector#invite}. Requests
+ * that an invitation be delivered to the named user, requesting that
+ * they join the inviting user in a game, the details of which are
+ * specified in the supplied game config object.
+ *
+ * @param client a connected, operational client instance.
+ * @param invitee the username of the user to be invited.
+ * @param config a game config object detailing the type and
+ * configuration of the game to be created.
+ * @param listener will receive and process the response.
+ */
+ function invite (
+ client :Client, invitee :Name, config :GameConfig,
+ listener :InviteListener) :void;
+
+ /**
+ * You probably don't want to call this directly, but want to call one
+ * of {@link Invitation#accept}, {@link Invitation#refuse}, or {@link
+ * Invitation#counter}. Requests that an invitation response be
+ * delivered with the specified parameters.
+ *
+ * @param client a connected, operational client instance.
+ * @param inviteId the unique id previously assigned by the server to
+ * this invitation.
+ * @param code the response code to use in responding to the
+ * invitation.
+ * @param arg the argument associated with the response (a string
+ * message from the player explaining why the response was refused in
+ * the case of an invitation refusal or an updated game configuration
+ * object in the case of a counter-invitation, or null in the case of
+ * an accepted invitation).
+ * @param listener will receive and process the response.
+ */
+ function respond (
+ client :Client, inviteId :int, code :int, arg :Object,
+ listener :InvocationListener) :void;
+
+ /**
+ * You probably don't want to call this directly, but want to call
+ * {@link Invitation#cancel}. Requests that an outstanding
+ * invitation be cancelled.
+ *
+ * @param client a connected, operational client instance.
+ * @param inviteId the unique id previously assigned by the server to
+ * this invitation.
+ * @param listener will receive and process the response.
+ */
+ function cancel (
+ client :Client, inviteId :int, listener :InvocationListener) :void;
+
+ /**
+ * You probably don't want to call this directly, but want to call
+ * {@link TableDirector#createTable}. Requests that a new table be
+ * created.
+ *
+ * @param client a connected, operational client instance.
+ * @param lobbyOid the oid of the lobby that will contain the newly
+ * created table.
+ * @param tableConfig the table configuration parameters.
+ * @param config the game config for the game to be matchmade by the
+ * table.
+ * @param listener will receive and process the response.
+ */
+ function createTable (
+ client :Client, lobbyOid :int, tableConfig :TableConfig,
+ config :GameConfig, listener :TableListener) :void;
+
+ /**
+ * You probably don't want to call this directly, but want to call
+ * {@link TableDirector#joinTable}. Requests that the current user
+ * be added to the specified table at the specified position.
+ *
+ * @param client a connected, operational client instance.
+ * @param lobbyOid the oid of the lobby that contains the table.
+ * @param tableId the unique id of the table to which this user wishes
+ * to be added.
+ * @param position the position at the table to which this user desires
+ * to be added.
+ * @param listener will receive and process the response.
+ */
+ function joinTable (
+ client :Client, lobbyOid :int, tableId :int,
+ position :int, listener :InvocationListener) :void;
+
+ /**
+ * You probably don't want to call this directly, but want to call
+ * {@link TableDirector#leaveTable}. Requests that the current user be
+ * removed from the specified table.
+ *
+ * @param client a connected, operational client instance.
+ * @param lobbyOid the oid of the lobby that contains the table.
+ * @param tableId the unique id of the table from which this user
+ * wishes to be removed.
+ * @param listener will receive and process the response.
+ */
+ function leaveTable (
+ client :Client, lobbyOid :int, tableId :int,
+ listener :InvocationListener) :void;
+
+ /**
+ * Requests to start a single player game with the specified game
+ * configuration.
+ */
+ function startSolitaire (
+ client :Client, config :GameConfig,
+ listener :ConfirmListener) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/ParlorService_InviteListener.as b/src/as/com/threerings/parlor/client/ParlorService_InviteListener.as
new file mode 100644
index 00000000..d96e084d
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/ParlorService_InviteListener.as
@@ -0,0 +1,15 @@
+package com.threerings.parlor.client {
+
+import com.threerings.presents.client.InvocationListener;
+
+/**
+ * Used to communicate responses to {@link #invite} requests.
+ */
+public interface ParlorService_InviteListener extends InvocationListener
+{
+ /**
+ * Called in response to a successful {@link #invite} request.
+ */
+ function inviteReceived (inviteId :int) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/ParlorService_TableListener.as b/src/as/com/threerings/parlor/client/ParlorService_TableListener.as
new file mode 100644
index 00000000..d499034e
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/ParlorService_TableListener.as
@@ -0,0 +1,13 @@
+package com.threerings.parlor.client {
+
+import com.threerings.presents.client.InvocationListener;
+
+/**
+ * Used to communicate responses to {@link #createTable}, {@link
+ * #joinTable}, and {@link #leaveTable} requests.
+ */
+public interface ParlorService_TableListener extends InvocationListener
+{
+ function tableCreated (tableId :int) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/SeatednessObserver.as b/src/as/com/threerings/parlor/client/SeatednessObserver.as
new file mode 100644
index 00000000..d5ff079c
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/SeatednessObserver.as
@@ -0,0 +1,39 @@
+//
+// $Id: SeatednessObserver.java 4191 2006-06-13 22:42:20Z 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.parlor.client {
+
+/**
+ * Entites that wish to hear about when we sit down at a table or stand up
+ * from a table can implement this interface and register themselves with
+ * the {@link TableDirector}.
+ */
+public interface SeatednessObserver
+{
+ /**
+ * Called when this client sits down at or stands up from a table.
+ *
+ * @param isSeated true if the client is now seated at a table, false
+ * if they are now no longer seated at a table.
+ */
+ function seatednessDidChange (isSeated :Boolean) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/TableConfigurator.as b/src/as/com/threerings/parlor/client/TableConfigurator.as
new file mode 100644
index 00000000..2e70506c
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/TableConfigurator.as
@@ -0,0 +1,125 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2005 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.parlor.client {
+
+import com.threerings.parlor.util.ParlorContext;
+
+import com.threerings.parlor.data.TableConfig;
+
+import com.threerings.parlor.game.client.GameConfigurator;
+
+/**
+ * This should be implemented some user-interface element that allows
+ * the user to configure whichever TableConfig options are relevant.
+ */
+public /*abstract*/ class TableConfigurator
+{
+ /**
+ * Create a TableConfigurator.
+ */
+ public function TableConfigurator ()
+ {
+ _config = createTableConfig();
+ }
+
+ /**
+ * Optionally, you can set a pre-configured TableConfig
+ * that will be used to initialize the display parameters (if possible).
+ * This should be called prior to init().
+ */
+ public function setTableConfig (config :TableConfig) :void
+ {
+ if (config != null) {
+ _config = config;
+ }
+ }
+
+ /**
+ * Initialize the TableConfigurator.
+ *
+ * At the time this is called, the GameConfigurator should have
+ * already been initialized.
+ */
+ public function init (
+ ctx :ParlorContext, gameConfigurator :GameConfigurator) :void
+ {
+ _ctx = ctx;
+ _gameConfigurator = gameConfigurator;
+ createConfigInterface();
+ }
+
+ /**
+ * Create the table config object that will be used.
+ */
+ protected function createTableConfig () :TableConfig
+ {
+ return new TableConfig();
+ }
+
+ /**
+ * Create the config interface.
+ */
+ protected function createConfigInterface () :void
+ {
+ // nothing by default
+ }
+
+ /**
+ * If true, the TableConfigurator is empty, which doesn't mean that
+ * it will not return a TableConfig object (for it must), but rather
+ * that there are no user-editable options being presented in the
+ * config interface.
+ */
+ public /*abstract*/ function isEmpty () :Boolean
+ {
+ return true;
+ }
+
+ /**
+ * Return the fully configured table config according to the currently
+ * configured user interface elements.
+ */
+ public function getTableConfig () :TableConfig
+ {
+ flushTableConfig();
+ return _config;
+ }
+
+ /**
+ * Derived classes will want to override this method, flushing
+ * values from the user interface to the table config object.
+ */
+ protected function flushTableConfig () :void
+ {
+ // nothing by default
+ }
+
+ /** Provides access to client services. */
+ protected var _ctx :ParlorContext;
+
+ /** The config we're configurating. */
+ protected var _config :TableConfig;
+
+ /** The game configurator, which we may wish to reference. */
+ protected var _gameConfigurator :GameConfigurator;
+}
+}
diff --git a/src/as/com/threerings/parlor/client/TableDirector.as b/src/as/com/threerings/parlor/client/TableDirector.as
new file mode 100644
index 00000000..2a280a43
--- /dev/null
+++ b/src/as/com/threerings/parlor/client/TableDirector.as
@@ -0,0 +1,367 @@
+//
+// $Id: TableDirector.java 3758 2005-11-10 23:18:58Z mdb $
+//
+// 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.parlor.client {
+
+import com.threerings.presents.client.BasicDirector;
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.ClientEvent;
+
+import com.threerings.presents.dobj.EntryAddedEvent;
+import com.threerings.presents.dobj.EntryRemovedEvent;
+import com.threerings.presents.dobj.EntryUpdatedEvent;
+import com.threerings.presents.dobj.SetListener;
+
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.crowd.data.PlaceObject;
+
+import com.threerings.parlor.data.Table;
+import com.threerings.parlor.data.TableConfig;
+import com.threerings.parlor.data.TableLobbyObject;
+import com.threerings.parlor.game.data.GameConfig;
+import com.threerings.parlor.util.ParlorContext;
+
+/**
+ * As tables are created and managed within the scope of a place (a
+ * lobby), we want to fold the table management functionality into the
+ * standard hierarchy of place controllers that deal with place-related
+ * functionality on the client. Thus, instead of forcing places that
+ * expect to have tables to extend a TableLobbyController or
+ * something similar, we instead provide the table director which can be
+ * instantiated by the place controller (or specific table related views)
+ * to handle the table matchmaking services.
+ *
+ *
Entites that do so, will need to implement the {@link + * TableObserver} interface so that the table director can notify them + * when table related things happen. + * + *
The table services expect that the place object being used as a + * lobby in which the table matchmaking takes place implements the {@link + * TableLobbyObject} interface. + */ +public class TableDirector extends BasicDirector + implements SetListener, ParlorService_TableListener +{ + private static const log :Log = Log.getLog(TableDirector); + + /** + * Creates a new table director to manage tables with the specified + * observer which will receive callbacks when interesting table + * related things happen. + * + * @param ctx the parlor context in use by the client. + * @param tableField the field name of the distributed set that + * contains the tables we will be managing. + * @param observer the entity that will receive callbacks when things + * happen to the tables. + */ + public function TableDirector ( + ctx :ParlorContext, tableField :String, observer :TableObserver) + { + super(ctx); + + // keep track of this stuff + _pctx = ctx; + _tableField = tableField; + _observer = observer; + } + + /** + * This must be called by the entity that uses the table director when + * the using entity prepares to enter and display a place. It is + * assumed that the client is already subscribed to the provided place + * object. + */ + public function willEnterPlace (place :PlaceObject) :void + { + // add ourselves as a listener to the place object + place.addListener(this); + + // and remember this for later + _lobby = place; + } + + /** + * This must be called by the entity that uses the table director when + * the using entity has left and is done displaying a place. + */ + public function didLeavePlace (place :PlaceObject) :void + { + // remove our listenership + place.removeListener(this); + + // clear out our lobby reference + _lobby = null; + } + + /** + * Requests that the specified observer be added to the list of + * observers that are notified when this client sits down at or stands + * up from a table. + */ + public function addSeatednessObserver (observer :SeatednessObserver) :void + { + _seatedObservers.push(observer); + } + + /** + * Requests that the specified observer be removed from to the list of + * observers that are notified when this client sits down at or stands + * up from a table. + */ + public function removeSeatednessObserver ( + observer :SeatednessObserver) :void + { + ArrayUtil.removeFirst(_seatedObservers, observer); + } + + /** + * Returns true if this client is currently seated at a table, false + * if they are not. + */ + public function isSeated () :Boolean + { + return (_ourTable != null); + } + + /** + * Sends a request to create a table with the specified game + * configuration. This user will become the owner of this table and + * will be added to the first position in the table. The response will + * be communicated via the {@link TableObserver} interface. + */ + public function createTable ( + tableConfig :TableConfig, config :GameConfig) :void + { + // if we're already in a table, refuse the request + if (_ourTable != null) { + log.warning("Ignoring request to create table as we're " + + "already in a table [table=" + _ourTable + "]."); + return; + } + + // make sure we're currently in a place + if (_lobby == null) { + log.warning("Requested to create a table but we're not " + + "currently in a place [config=" + config + "]."); + return; + } + + // go ahead and issue the create request + _pservice.createTable(_pctx.getClient(), _lobby.getOid(), tableConfig, + config, this); + } + + /** + * Sends a request to join the specified table at the specified + * position. The response will be communicated via the {@link + * TableObserver} interface. + */ + public function joinTable (tableId :int, position :int) :void + { + // if we're already in a table, refuse the request + if (_ourTable != null) { + log.warning("Ignoring request to join table as we're " + + "already in a table [table=" + _ourTable + "]."); + return; + } + + // make sure we're currently in a place + if (_lobby == null) { + log.warning("Requested to join a table but we're not " + + "currently in a place [tableId=" + tableId + "]."); + return; + } + + // issue the join request + _pservice.joinTable(_pctx.getClient(), _lobby.getOid(), tableId, + position, this); + } + + /** + * Sends a request to leave the specified table at which we are + * presumably seated. The response will be communicated via the {@link + * TableObserver} interface. + */ + public function leaveTable (tableId :int) :void + { + // make sure we're currently in a place + if (_lobby == null) { + log.warning("Requested to leave a table but we're not " + + "currently in a place [tableId=" + tableId + "]."); + return; + } + + // issue the leave request + _pservice.leaveTable(_pctx.getClient(), _lobby.getOid(), tableId, this); + } + + // documentation inherited + override public function clientDidLogoff (event :ClientEvent) :void + { + super.clientDidLogoff(event); + _pservice = null; + _lobby = null; + _ourTable = null; + } + + // documentation inherited + protected function fetchServices (Client client) :void + { + // get a handle on our parlor services + _pservice = (client.requireService(ParlorService) as ParlorService); + } + + // documentation inherited + public function entryAdded (event :EntryAddedEvent) :void + { + if (event.getName() == _tableField) { + var table :Table = (event.getEntry() as Table); + + // check to see if we just joined a table + checkSeatedness(table); + + // now let the observer know what's up + _observer.tableAdded(table); + } + } + + // documentation inherited + public function entryUpdated (event :EntryUpdatedEvent) :void + { + if (event.getName() == _tableField) { + var table :Table = (event.getEntry() as Table); + + // check to see if we just joined or left a table + checkSeatedness(table); + + // now let the observer know what's up + _observer.tableUpdated(table); + } + } + + // documentation inherited + public function entryRemoved (event :EntryRemovedEvent) :void + { + if (event.getName() == _tableField) { + var tableId :int = (event.getKey() as int); + + // check to see if our table just disappeared + if (_ourTable != null && tableId == _ourTable.tableId) { + _ourTable = null; + notifySeatedness(false); + } + + // now let the observer know what's up + _observer.tableRemoved(tableId); + } + } + + // documentation inherited from interface + public function tableCreated (tableId :int) :void + { + // nothing much to do here + log.info("Table creation succeeded [tableId=" + tableId + "]."); + } + + // documentation inherited from interface + public function requestFailed (reason :String) :void + { + log.warning("Table creation failed [reason=" + reason + "]."); + } + + /** + * Checks to see if we're a member of this table and notes it as our + * table, if so. + */ + protected function checkSeatedness (table :Table) :void + { + var oldTable :Table = _ourTable; + + // if this is the same table as our table, clear out our table + // reference and allow it to be added back if we are still in the + // table + if (table.equals(_ourTable)) { + _ourTable = null; + } + + // look for our username in the occupants array + var self :BodyObject = + (_pctx.getClient().getClientObject() as BodyObject); + for (var ii :int = 0; ii < table.occupants.length; ii++) { + if (self.getVisibleName().equals(table.occupants[i])) { + _ourTable = table; + break; + } + } + + // if nothing changed, bail now + if (oldTable == _ourTable || + (oldTable != null && oldTable.equals(_ourTable))) { + return; + } + + // otherwise notify the observers + notifySeatedness(_ourTable != null); + } + + /** + * Notifies the seatedness observers of a seatedness change. + */ + protected function notifySeatedness (isSeated :Boolean) :void + { + var slength :int = _seatedObservers.length; + for (var ii :int = 0; ii < slength; ii++) { + var observer :SeatednessObserver = + (_seatedObservers[ii] as SeatednessObserver); + try { + observer.seatednessDidChange(isSeated); + } catch (err :Error) { + log.warning("Observer choked in seatednessDidChange() " + + "[observer=" + observer + "]."); + log.logStackTrace(err); + } + } + } + + /** A context by which we can access necessary client services. */ + protected var _pctx :ParlorContext; + + /** Parlor server-side services. */ + protected var _pservice :ParlorService; + + /** The place object in which we're currently managing tables. */ + protected var _lobby :PlaceObject; + + /** The field name of the distributed set that contains our tables. */ + protected var _tableField :String; + + /** The entity that we talk to when table stuff happens. */ + protected var _observer :TableObserver; + + /** The table of which we are a member if any. */ + protected var _ourTable :Table; + + /** An array of entities that want to hear about when we stand up or + * sit down. */ + protected var _seatedObservers :Array = new Array(); +} +} diff --git a/src/as/com/threerings/parlor/client/TableObserver.as b/src/as/com/threerings/parlor/client/TableObserver.as new file mode 100644 index 00000000..bffc44bc --- /dev/null +++ b/src/as/com/threerings/parlor/client/TableObserver.as @@ -0,0 +1,49 @@ +// +// $Id: TableObserver.java 4191 2006-06-13 22:42:20Z 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.parlor.client { + +import com.threerings.parlor.data.Table; + +/** + * The {@link TableDirector} converts distributed object events into + * higher level callbacks to implementers of this interface, which are + * expected to render these events sensically in a user interface. + */ +public interface TableObserver +{ + /** + * Called when a new table is created. + */ + function tableAdded (table :Table) :void; + + /** + * Called when something has changed about a table (occupant list + * updated, state changed from matchmaking to in-play, etc.). + */ + function tableUpdated (table :Table) :void; + + /** + * Called when a table goes away. + */ + function tableRemoved (tableId :int) :void; +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorCodes.as b/src/as/com/threerings/parlor/data/ParlorCodes.as new file mode 100644 index 00000000..9d3cdc8f --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorCodes.as @@ -0,0 +1,63 @@ +// +// $Id: ParlorCodes.java 3359 2005-02-19 22:38:06Z mdb $ +// +// 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.parlor.data { + +import com.threerings.presents.data.InvocationCodes; + +/** + * Contains codes used by the parlor invocation services. + */ +public class ParlorCodes extends InvocationCodes +{ + /** The response code for an accepted invitation. */ + public static const INVITATION_ACCEPTED :int = 0; + + /** The response code for a refused invitation. */ + public static const INVITATION_REFUSED :int = 1; + + /** The response code for a countered invitation. */ + public static const INVITATION_COUNTERED :int = 2; + + /** An error code explaining that an invitation was rejected because + * the invited user was not online at the time the invitation was + * received. */ + public static const INVITEE_NOT_ONLINE :String = "m.invitee_not_online"; + + /** An error code returned when a user requests to join a table that + * doesn't exist. */ + public static const NO_SUCH_TABLE :String = "m.no_such_table"; + + /** An error code returned when a user requests to join a table at a + * position that is not valid. */ + public static const INVALID_TABLE_POSITION :String = + "m.invalid_table_position"; + + /** An error code returned when a user requests to join a table in a + * position that is already occupied. */ + public static const TABLE_POSITION_OCCUPIED :String = + "m.table_position_occupied"; + + /** An error code returned when a user requests to leave a table that + * they were not sitting at in the first place. */ + public static const NOT_AT_TABLE :String = "m.not_at_table"; +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorMarshaller.as b/src/as/com/threerings/parlor/data/ParlorMarshaller.as new file mode 100644 index 00000000..b259855b --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorMarshaller.as @@ -0,0 +1,143 @@ +// +// $Id: ParlorMarshaller.java 4145 2006-05-24 01:24:24Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 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.parlor.data { + +import com.threerings.parlor.client.ParlorService; +import com.threerings.parlor.data.TableConfig; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; +import com.threerings.presents.client.InvocationService_InvocationListener; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.presents.dobj.InvocationResponseEvent; +import com.threerings.util.Name; + +/** + * Provides the implementation of the {@link ParlorService} interface + * that marshalls the arguments and delivers the request to the provider + * on the server. Also provides an implementation of the response listener + * interfaces that marshall the response arguments and deliver them back + * to the requesting client. + */ +public class ParlorMarshaller extends InvocationMarshaller + implements ParlorService +{ + /** The method id used to dispatch {@link #cancel} requests. */ + public static const CANCEL :int = 1; + + // documentation inherited from interface + public function cancel (arg1 :Client, arg2 :int, arg3 :InvocationService_InvocationListener) :void + { + var listener3 :InvocationMarshaller_ListenerMarshaller = + new InvocationMarshaller_ListenerMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, CANCEL, [ + Integer.valueOf(arg2), listener3 + ]); + } + + /** The method id used to dispatch {@link #createTable} requests. */ + public static const CREATE_TABLE :int = 2; + + // documentation inherited from interface + public function createTable (arg1 :Client, arg2 :int, arg3 :TableConfig, arg4 :GameConfig, arg5 :ParlorService_TableListener) :void + { + var listener5 :ParlorMarshaller_TableMarshaller = + new ParlorMarshaller_TableMarshaller(); + listener5.listener = arg5; + sendRequest(arg1, CREATE_TABLE, [ + Integer.valueOf(arg2), arg3, arg4, listener5 + ]); + } + + /** The method id used to dispatch {@link #invite} requests. */ + public static const INVITE :int = 3; + + // documentation inherited from interface + public function invite (arg1 :Client, arg2 :Name, arg3 :GameConfig, arg4 :ParlorService_InviteListener) :void + { + var listener4 :ParlorMarshaller_InviteMarshaller = + new ParlorMarshaller_InviteMarshaller(); + listener4.listener = arg4; + sendRequest(arg1, INVITE, [ + arg2, arg3, listener4 + ]); + } + + /** The method id used to dispatch {@link #joinTable} requests. */ + public static const JOIN_TABLE :int = 4; + + // documentation inherited from interface + public function joinTable (arg1 :Client, arg2 :int, arg3 :int, arg4 :int, arg5 :InvocationService_InvocationListener) :void + { + var listener5 :InvocationMarshaller_ListenerMarshaller listener5 = + new InvocationMarshaller_ListenerMarshaller(); + listener5.listener = arg5; + sendRequest(arg1, JOIN_TABLE, [ + Integer.valueOf(arg2), Integer.valueOf(arg3), Integer.valueOf(arg4), listener5 + ]); + } + + /** The method id used to dispatch {@link #leaveTable} requests. */ + public static const LEAVE_TABLE :int = 5; + + // documentation inherited from interface + public function leaveTable (arg1 :Client, arg2 :int, arg3 :int, arg4 :InvocationService_InvocationListener) :void + { + var listener4 :InvocationMarshaller_ListenerMarshaller = + new InvocationMarshaller_ListenerMarshaller(); + listener4.listener = arg4; + sendRequest(arg1, LEAVE_TABLE, [ + Integer.valueOf(arg2), Integer.valueOf(arg3), listener4 + ]); + } + + /** The method id used to dispatch {@link #respond} requests. */ + public static const RESPOND :int = 6; + + // documentation inherited from interface + public function respond (arg1 :Client, arg2 :int, arg3 :int, arg4 :Object, arg5 :InvocationService_InvocationListener) :void + { + var listener5 :InvocationMarshaller_ListenerMarshaller = + new InvocationMarshallerListenerMarshaller(); + listener5.listener = arg5; + sendRequest(arg1, RESPOND, [ + Integer.valueOf(arg2), Integer.valueOf(arg3), arg4, listener5 + ]); + } + + /** The method id used to dispatch {@link #startSolitaire} requests. */ + public static const START_SOLITAIRE :int = 7; + + // documentation inherited from interface + public function startSolitaire (arg1 :Client, arg2 :GameConfig, arg3 :InvocationService_ConfirmListener) :void + { + var listener3 :InvocationMarshaller_ConfirmMarshaller = + new InvocationMarshaller_ConfirmMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, START_SOLITAIRE, [ + arg2, listener3 + ]); + } + +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorMarshaller_InviteMarshaller.as b/src/as/com/threerings/parlor/data/ParlorMarshaller_InviteMarshaller.as new file mode 100644 index 00000000..e909ec8e --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorMarshaller_InviteMarshaller.as @@ -0,0 +1,39 @@ +package com.threerings.parlor.data { + +import com.threerings.util.Integer; + +import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller; + +public class ParlorMarshaller_InviteMarshaller + extends InvocationMarshaller_ListenerMarshaller + implements InviteListener +{ + /** The method id used to dispatch {@link #inviteReceived} + * responses. */ + public static const INVITE_RECEIVED :int = 1; + + // documentation inherited from interface + public function inviteReceived (arg1 :int) :void + { + _invId = null; + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, INVITE_RECEIVED, + [ Integer.valueOf(arg1) ])); + } + + // documentation inherited + override public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { + case INVITE_RECEIVED: + (listener as InviteListener).inviteReceived( + (args[0] as Integer).value); + return; + + default: + super.dispatchResponse(methodId, args); + return; + } + } +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorMarshaller_TableMarshaller.as b/src/as/com/threerings/parlor/data/ParlorMarshaller_TableMarshaller.as new file mode 100644 index 00000000..cfc3f129 --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorMarshaller_TableMarshaller.as @@ -0,0 +1,39 @@ +package com.threerings.parlor.data { + +import com.threerings.util.Integer; + +import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller; + +public class ParlorMarshaller_TableMarshaller + extends InvocationMarshaller_ListenerMarshaller + implements TableListener +{ + /** The method id used to dispatch {@link #tableCreated} + * responses. */ + public static const TABLE_CREATED :int = 1; + + // documentation inherited from interface + public function tableCreated (arg1 :int) :void + { + _invId = null; + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, TABLE_CREATED, + [ Integer.valueOf(arg1) ])); + } + + // documentation inherited + override public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { + case TABLE_CREATED: + (listener as TableListener).tableCreated( + (args[0] as Integer).value); + return; + + default: + super.dispatchResponse(methodId, args); + return; + } + } +} +} diff --git a/src/as/com/threerings/parlor/data/Table.as b/src/as/com/threerings/parlor/data/Table.as new file mode 100644 index 00000000..6773a088 --- /dev/null +++ b/src/as/com/threerings/parlor/data/Table.as @@ -0,0 +1,378 @@ +// +// $Id: Table.java 4191 2006-06-13 22:42:20Z 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.parlor.data { + +import com.threerings.util.Hashable; +import com.threerings.util.Name; +import com.threerings.util.StringBuilder; + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +import com.threerings.presents.dobj.DSet; +import com.threerings.presents.dobj.DSet_Entry; + +import com.threerings.crowd.data.BodyObject; + +import com.threerings.parlor.data.ParlorCodes; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.game.data.PartyGameConfig; + +/** + * This class represents a table that is being used to matchmake a game by + * the Parlor services. + */ +public class Table + implements Hashable, DSet_Entry, ParlorCodes +{ + /** The unique identifier for this table. */ + public var tableId :int; + + /** The object id of the lobby object with which this table is + * associated. */ + public var lobbyOid :int; + + /** The oid of the game that was created from this table or -1 if the + * table is still in matchmaking mode. */ + public var gameOid :int = -1; + + /** An array of the usernames of the occupants of this table (some + * slots may not be filled), or null if a party game. */ + public var occupants :TypedArray /* of Name */; + + /** The body oids of the occupants of this table, or null if a party game. + * (This is not propagated to remote instances.) */ + public var bodyOids :TypedArray /* of int */; + + /** The game config for the game that is being matchmade. */ + public var config :GameConfig; + + /** The table configuration object. */ + public var tconfig :TableConfig; + + /** Suitable for unserialization. */ + public function Table () + { + } + + /** + * Returns true if there is no one sitting at this table. + */ + public function isEmpty () :Boolean + { + for (var ii :int = 0; ii < bodyOids.length; ii++) { + if ((bodyOids[ii] as int) !== 0) { + return false; + } + } + return true; + } + + /** + * Count the number of players currently occupying this table. + */ + public function getOccupiedCount () :int + { + var count :int = 0; + for (var ii :int = 0; ii < occupants.length; ii++) { + if (occupants[ii] != null) { + count++; + } + } + return count; + } + + /** + * Once a table is ready to play (see {@link #mayBeStarted} and {@link + * #shouldBeStarted}), the players array can be fetched using this + * method. It will return an array containing the usernames of all of + * the players in the game, sized properly and with each player in the + * appropriate position. + */ + public function getPlayers () :Array + { + if (isPartyGame()) { + return occupants; + } + + // create and populate the players array + var players :Array = new Array(); + for (var ii :int = 0; ii < occupants.length; ii++) { + if (occupants[ii] != null) { + players.push(occupants[ii]); + } + } + + return players; + } + + /** + * For a team game, get the team member indices of the compressed + * players array returned by getPlayers(). + */ + public function getTeamMemberIndices () :Array /* of Array of int */ + { + var teams :Array = tconfig.teamMemberIndices; + if (teams == null) { + return null; + } + +// TODO + // compress the team indexes down + ArrayIntSet set = new ArrayIntSet(); + int[][] newTeams = new int[teams.length][]; + Name[] players = getPlayers(); + for (int ii=0; ii < teams.length; ii++) { + set.clear(); + for (int jj=0; jj < teams[ii].length; jj++) { + Name occ = occupants[teams[ii][jj]]; + if (occ != null) { + set.add(ListUtil.indexOf(players, occ)); + } + } + newTeams[ii] = set.toIntArray(); + } + + return newTeams; + } + + /** + * Return true if the game is a party game. + */ + public function isPartyGame () :Boolean + { + return (PartyGameConfig.NOT_PARTY_GAME != getPartyGameType()); + } + + /** + * Get the type of party game being played at this table, or + * PartyGameConfig.NOT_PARTY_GAME. + */ + public function getPartyGameType () :int + { + if (config is PartyGameConfig) { + return (config as PartyGameConfig).getPartyGameType(); + + } else { + return PartyGameConfig.NOT_PARTY_GAME; + } + } + + /** + * Requests to seat the specified user at the specified position in + * this table. + * + * @param position the position in which to seat the user. + * @param occupant the occupant to set. + * + * @return null if the user was successfully seated, a string error + * code explaining the failure if the user was not able to be seated + * at that position. + */ + public function setOccupant (position :int, occupant :BodyObject) :String + { + // make sure the requested position is a valid one + if (position >= tconfig.desiredPlayerCount || position < 0) { + return INVALID_TABLE_POSITION; + } + + // make sure the requested position is not already occupied + if (occupants[position] != null) { + return TABLE_POSITION_OCCUPIED; + } + + // otherwise all is well, stick 'em in + setOccupantPos(position, occupant); + return null; + } + + /** + * This method is used for party games, it does no bounds checking + * or verification of the player's ability to join, if you are unsure + * you should call 'setOccupant'. + */ + public function setOccupantPos (position :int, occupant :BodyObject) :void + { + occupants[position] = occupant.getVisibleName(); + bodyOids[position] = occupant.getOid(); + } + + /** + * Requests that the specified user be removed from their seat at this + * table. + * + * @return true if the user was seated at the table and has now been + * removed, false if the user was never seated at the table in the + * first place. + */ + public function clearOccupant (username :Name) :void + { + var dex :int = ArrayUtil.indexOf(occupants, username); + if (dex != -1) { + clearOccupantPos(dex); + return true; + } + return false; + } + + /** + * Requests that the user identified by the specified body object id + * be removed from their seat at this table. + * + * @return true if the user was seated at the table and has now been + * removed, false if the user was never seated at the table in the + * first place. + */ + public function clearOccupant (bodyOid :int) :Boolean + { + var dex :int = ArrayUtil.indexOf(bodyOids, bodyOid); + if (dex != -1) { + clearOccupantPos(dex); + return true; + } + return false; + } + + /** + * Called to clear an occupant at the specified position. + * Only call this method if you know what you're doing. + */ + public function clearOccupantPos (position :int) :void + { + occupants[position] = null; + bodyOids[position] = 0; + } + + /** + * Returns true if this table has a sufficient number of occupants + * that the game can be started. + */ + public function mayBeStarted () :Boolean + { + if (tconfig.teamMemberIndices == null) { + // for a normal game, just check to see if we're past the minimum + return tconfig.minimumPlayerCount <= getOccupiedCount(); + + } else { + // for a team game, make sure each team has the minimum players + var teams :Array = tconfig.teamMemberIndices; + for (var ii :int = 0; ii < teams.length; ii++) { + var teamCount :int = 0; + var members :Array = (teams[ii] as Array); + for (var jj :int = 0; jj < members.length; jj++) { + if (occupants[members[jj]] != null) { + teamCount++; + } + } + if (teamCount < tconfig.minimumPlayerCount) { + return false; + } + } + return true; + } + } + + /** + * Returns true if sufficient seats are occupied that the game should + * be automatically started. + */ + public function shouldBeStarted () :Boolean + { + return tconfig.desiredPlayerCount <= getOccupiedCount(); + } + + /** + * Returns true if this table is in play, false if it is still being + * matchmade. + */ + public function inPlay () :Boolean + { + return gameOid != -1; + } + + // documentation inherited + public function getKey () :Object + { + return tableId; + } + + // from Hashable + public function equals (other :Object) :Boolean + { + return (other is Table) && + (tableId == ((Table) other).tableId); + } + + // from Hashable + public function hashCode () :int + { + return tableId; + } + + // from Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeInt(tableId); + out.writeInt(lobbyOid); + out.writeInt(gameOid); + out.writeObject(occupants); + out.writeObject(config); + out.writeObject(tconfig); + } + + // from Streamable + public function readObject (ins :ObjectInputStream) :void + { + tableId = ins.readInt(); + lobbyOid = ins.readInt(); + gameOid = ins.readInt(); + occupants = (ins.readObject() as TypedArray); + config = (ins.readObject() as GameConfig); + tconfig = (ins.readObject() as TableConfig); + } + + /** + * Generates a string representation of this table instance. + */ + public function toString () :String + { + var buf :StringBuilder = new StringBuilder(); + buf.append(ClassUtil.shortClassName(this)); + buf.append(" ["); + toStringBuf(buf); + buf.append("]"); + return buf.toString(); + } + + /** + * Helper method for toString, ripe for overrideability. + */ + protected function toStringBuf (buf :StringBuilder) :void + { + buf.append("tableId=").append(tableId); + buf.append(", lobbyOid=").append(lobbyOid); + buf.append(", gameOid=").append(gameOid); + buf.append(", occupants=").append(occupants.join()); + buf.append(", config=").append(config); + } +} +} diff --git a/src/as/com/threerings/parlor/data/TableConfig.as b/src/as/com/threerings/parlor/data/TableConfig.as new file mode 100644 index 00000000..a85173e7 --- /dev/null +++ b/src/as/com/threerings/parlor/data/TableConfig.as @@ -0,0 +1,72 @@ +// +// $Id: TableConfig.java 3604 2005-06-17 20:25:28Z 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.parlor.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; +import com.threerings.io.TypedArray; + +/** + * Table configuration parameters for a game that is to be matchmade + * using the table services. + */ +public class TableConfig + implements Streamable +{ + /** The total number of players that are desired for the table, + * or -1 for a party game. For team games, this should be set to the + * total number of players overall, as teams may be unequal. */ + public var desiredPlayerCount :int; + + /** The minimum number of players needed overall (or per-team if a + * team-based game) for the game to start at the creator's discretion. */ + public var minimumPlayerCount :int; + + /** If non-null, indicates that this is a team-based game and contains + * the team assignments for each player. For example, a game with + * three players in two teams- players 0 and 2 versus player 1- would + * have { {0, 2}, {1} }; */ + public var teamMemberIndices :TypedArray; + + /** Whether the table is "private". */ + public var privateTable :Boolean; + + // from Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeInt(desiredPlayerCount); + out.writeInt(minimumPlayerCount); + out.writeField(teamMemberIndices); + out.writeBoolean(privateTable); + } + + // from Streamable + public function readObject (ins :ObjectInputStream) :void + { + desiredPlayerCount = ins.readInt(); + minimumPlayerCount = ins.readInt(); + teamMemberIndices = (ins.readField("[[I") as TypedArray); + privateTable = ins.readBoolean(); + } +} +} diff --git a/src/as/com/threerings/parlor/data/TableLobbyObject.as b/src/as/com/threerings/parlor/data/TableLobbyObject.as new file mode 100644 index 00000000..6664b98d --- /dev/null +++ b/src/as/com/threerings/parlor/data/TableLobbyObject.as @@ -0,0 +1,57 @@ +// +// $Id: TableLobbyObject.java 4145 2006-05-24 01:24:24Z 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.parlor.data { + +import com.threerings.presents.dobj.DSet; + +/** + * This interface must be implemented by the place object used by a lobby + * that wishes to make use of the table services. + */ +public interface TableLobbyObject +{ + /** + * Returns a reference to the distributed set instance that will be + * holding the tables. + */ + function getTables () :DSet; + + /** + * Adds the supplied table instance to the tables set (using the + * appropriate distributed object mechanisms). + */ + function addToTables (table :Table) :void; + + /** + * Updates the value of the specified table instance in the tables + * distributed set (using the appropriate distributed object + * mechanisms). + */ + function updateTables (table :Table) :void; + + /** + * Removes the table instance that matches the specified key from the + * tables set (using the appropriate distributed object mechanisms). + */ + function removeFromTables (key :Object) :void; +} +} diff --git a/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as b/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as new file mode 100644 index 00000000..311da5ba --- /dev/null +++ b/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as @@ -0,0 +1,77 @@ +// +// $Id: GameConfigurator.java 3590 2005-06-08 23:20:18Z 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.parlor.game.client; + +import mx.core.Container; +import mx.core.UIComponent; + +import mx.containers.Grid; +import mx.containers.GridItem; +import mx.containers.GridRow; + +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * Provides the base from which interfaces can be built to configure games + * prior to starting them. Derived classes would extend the base + * configurator adding interface elements and wiring them up properly to + * allow the user to configure an instance of their game. + * + *
Clients that use the game configurator will want to instantiate one + * based on the class returned from the {@link GameConfig} and then + * initialize it with a call to {@link #init}. + */ +public /*abstract*/ class FlexGameConfigurator extends GameConfigurator +{ + /** + * Get the Container that contains the UI elements for this configurator. + */ + public function getContainer () :Container + { + return _grid; + } + + /** + * Add a control to the interface. This should be the standard way + * that configurator controls are added, but note also that external + * entities may add their own controls that are related to the game, + * but do not directly alter the game config, so that all the controls + * are added in a uniform manner and are well aligned. + */ + public function addControl ( + label :UIComponent, control :UIComponent) :void + { + var row :GridRow = new GridRow(); + var labelItem :GridItem = new GridItem(); + var controlItem :GridItem = new GridItem(); + + labelItem.addChild(label); + controlItem.addChild(control); + row.addChild(labelItem); + row.addChild(controlItem); + _grid.addChild(row); + } + + /** The grid on which the config options are placed. */ + protected var _grid :Grid = new Grid(); +} diff --git a/src/as/com/threerings/parlor/game/client/GameConfigurator.as b/src/as/com/threerings/parlor/game/client/GameConfigurator.as new file mode 100644 index 00000000..ef778850 --- /dev/null +++ b/src/as/com/threerings/parlor/game/client/GameConfigurator.as @@ -0,0 +1,104 @@ +// +// $Id: GameConfigurator.java 3590 2005-06-08 23:20:18Z 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.parlor.game.client { + +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * Provides the base from which interfaces can be built to configure games + * prior to starting them. Derived classes would extend the base + * configurator adding interface elements and wiring them up properly to + * allow the user to configure an instance of their game. + * + *
Clients that use the game configurator will want to instantiate one
+ * based on the class returned from the {@link GameConfig} and then
+ * initialize it with a call to {@link #init}.
+ */
+public /*abstract*/ class GameConfigurator
+{
+ /**
+ * Initializes this game configurator, creates its user interface
+ * elements and prepares it for display.
+ */
+ public function init (ctx :ParlorContext) :void
+ {
+ // save this for later
+ _ctx = ctx;
+
+ // create our interface elements
+ createConfigInterface();
+ }
+
+ /**
+ * The default implementation creates nothing.
+ */
+ protected function createConfigInterface () :void
+ {
+ }
+
+ /**
+ * Provides this configurator with its configuration. It should set up
+ * all of its user interface elements to reflect the configuration.
+ */
+ public function setGameConfig (config :GameConfig) :void
+ {
+ _config = config;
+ // set up the user interface
+ gotGameConfig();
+ }
+
+ /**
+ * Derived classes will likely want to override this method and
+ * configure their user interface elements accordingly.
+ */
+ protected function gotGameConfig () :void
+ {
+ }
+
+ /**
+ * Obtains a configured game configuration.
+ */
+ public function getGameConfig () :GameConfig
+ {
+ // flush our changes to the config object
+ flushGameConfig();
+ return _config;
+ }
+
+ /**
+ * Derived classes will want to override this method, flushing values
+ * from the user interface to the game config object so that it is
+ * properly configured prior to being returned to the {@link
+ * #getGameConfig} caller.
+ */
+ protected function flushGameConfig () :void
+ {
+ }
+
+ /** Provides access to client services. */
+ protected var _ctx :ParlorContext;
+
+ /** Our game configuration. */
+ protected var _config :GameConfig;
+}
+}
diff --git a/src/as/com/threerings/parlor/game/client/GameController.as b/src/as/com/threerings/parlor/game/client/GameController.as
new file mode 100644
index 00000000..46f0711d
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/client/GameController.as
@@ -0,0 +1,337 @@
+//
+// $Id: GameController.java 3804 2006-01-13 01:52:36Z 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.parlor.game.client {
+
+import com.threerings.presents.dobj.AttributeChangeListener;
+import com.threerings.presents.dobj.AttributeChangedEvent;
+
+import com.threerings.crowd.client.PlaceController;
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.crowd.data.PlaceConfig;
+import com.threerings.crowd.data.PlaceObject;
+import com.threerings.crowd.util.CrowdContext;
+
+import com.threerings.parlor.Log;
+import com.threerings.parlor.game.data.GameCodes;
+import com.threerings.parlor.game.data.GameConfig;
+import com.threerings.parlor.game.data.GameObject;
+import com.threerings.parlor.game.data.PartyGameConfig;
+import com.threerings.parlor.util.ParlorContext;
+
+/**
+ * The game controller manages the flow and control of a game on the
+ * client side. This class serves as the root of a hierarchy of controller
+ * classes that aim to provide functionality shared between various
+ * similar games. The base controller provides functionality for starting
+ * and ending the game and for calculating ratings adjustements when a
+ * game ends normally. It also handles the basic house keeping like
+ * subscription to the game object and dispatch of commands and
+ * distributed object events.
+ */
+public /*abstract*/ class GameController extends PlaceController
+ implements AttributeChangeListener
+{
+ protected static const log :Log = Log.getLog(GameController);
+
+ /**
+ * Initializes this game controller with the game configuration that
+ * was established during the match making process. Derived classes
+ * may want to override this method to initialize themselves with
+ * game-specific configuration parameters but they should be sure to
+ * call super.init in such cases.
+ *
+ * @param ctx the client context.
+ * @param config the configuration of the game we are intended to
+ * control.
+ */
+ override public function init (ctx :CrowdContext, config :PlaceConfig) :void
+ {
+ // cast our references before we call super.init() so that when
+ // super.init() calls createPlaceView(), we have our casted
+ // references already in place
+ _ctx = (ctx as ParlorContext);
+ _config = (config as GameConfig);
+
+ super.init(ctx, config);
+ }
+
+ /**
+ * Adds this controller as a listener to the game object (thus derived
+ * classes need not do so) and lets the game manager know that we are
+ * now ready to go.
+ */
+ override public function willEnterPlace (plobj :PlaceObject) :void
+ {
+ super.willEnterPlace(plobj);
+
+ // obtain a casted reference
+ _gobj = (plobj as GameObject);
+
+ // if this place object is not our current location we'll need to
+ // add it as an auxiliary chat source
+ var bobj :BodyObject =
+ (_ctx.getClient().getClientObject() as BodyObject);
+ if (bobj.location != plobj.getOid()) {
+ _ctx.getChatDirector().addAuxiliarySource(
+ _gobj, GameCodes.GAME_CHAT_TYPE);
+ }
+
+ // and add ourselves as a listener
+ _gobj.addListener(this);
+
+ // we don't want to claim to be finished until any derived classes
+ // that overrode this method have executed, so we'll queue up a
+ // runnable here that will let the game manager know that we're
+ // ready on the next pass through the distributed event loop
+ log.info("Entering game " + _gobj.which() + ".");
+ if (_gobj.getPlayerIndex(bobj.getVisibleName()) != -1) {
+ // finally let the game manager know that we're ready
+ // to roll
+ _ctx.getClient().callLater(playerReady);
+ }
+ }
+
+ /**
+ * Removes our listener registration from the game object and cleans
+ * house.
+ */
+ override public function didLeavePlace (plobj :PlaceObject) :void
+ {
+ super.didLeavePlace(plobj);
+
+ _ctx.getChatDirector().removeAuxiliarySource(_gobj);
+
+ // unlisten to the game object
+ _gobj.removeListener(this);
+ _gobj = null;
+ }
+
+ /**
+ * Returns whether the game is over.
+ */
+ public function isGameOver () :Boolean
+ {
+ var gameOver :Boolean = (_gobj == null) ||
+ (_gobj.state != GameObject.IN_PLAY);
+ return (_gameOver || gameOver);
+ }
+
+ /**
+ * Sets the client game over override. This is used in situations
+ * where we determine that the game is over before the server has
+ * informed us of such.
+ */
+ public function setGameOver (gameOver :Boolean) :void
+ {
+ _gameOver = gameOver;
+ }
+
+ /**
+ * Calls {@link #gameWillReset}, ends the current game (locally, it
+ * does not tell the server to end the game), and waits to receive a
+ * reset notification (which is simply an event setting the game state
+ * to IN_PLAY even though it's already set to
+ * IN_PLAY) from the server which will start up a new
+ * game. Derived classes should override {@link #gameWillReset} to
+ * perform any game-specific animations.
+ */
+ public function resetGame () :void
+ {
+ // let derived classes do their thing
+ gameWillReset();
+
+ // end the game until we receive a new board
+ setGameOver(true);
+ }
+
+ /**
+ * Returns the unique round identifier for the current round.
+ */
+ public function getRoundId () :int
+ {
+ return (_gobj == null) ? -1 : _gobj.roundId;
+ }
+
+ /**
+ * Handles basic game controller action events. Derived classes should
+ * be sure to call super.handleAction for events they
+ * don't specifically handle.
+ */
+ override public function handleAction (cmd :String, arg :Object) :Boolean
+ {
+ return super.handleAction(cmd, arg);
+ }
+
+ /**
+ * A way for controllers to display a game-related system message.
+ */
+ public function systemMessage (bundle :String, msg :String) :void
+ {
+ _ctx.getChatDirector().displayInfo(
+ bundle, msg, GameCodes.GAME_CHAT_TYPE);
+ }
+
+ // from interface AttributeChangeListener
+ public function attributeChanged (event :AttributeChangedEvent) :void
+ {
+ // deal with game state changes
+ var name :String = event.getName();
+ if (GameObject.STATE.equals(name)) {
+ var newState :int = int(event.getValue());
+ if (!stateDidChange(newState)) {
+ log.warning("Game transitioned to unknown state " +
+ "[gobj=" + _gobj + ", state=" + newState + "].");
+ }
+ }
+ }
+
+ /**
+ * Derived classes can override this method if they add additional game
+ * states and should handle transitions to those states, returning true to
+ * indicate they were handled and calling super for the normal game states.
+ */
+ protected function stateDidChange (state :int) :Boolean
+ {
+ switch (state) {
+ case GameObject.IN_PLAY:
+ gameDidStart();
+ return true;
+ case GameObject.GAME_OVER:
+ gameDidEnd();
+ return true;
+ case GameObject.CANCELLED:
+ gameWasCancelled();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Called after we've entered the game and everything has initialized
+ * to notify the server that we, as a player, are ready to play.
+ */
+ protected function playerReady () :void
+ {
+ log.info("Reporting ready " + _gobj.which() + ".");
+ _gobj.gameService.playerReady(_ctx.getClient());
+ }
+
+ /**
+ * Called when the game transitions to the IN_PLAY
+ * state. This happens when all of the players have arrived and the
+ * server starts the game.
+ */
+ protected function gameDidStart () :void
+ {
+ if (_gobj == null) {
+ log.info("Received gameDidStart() after leaving game room.");
+ return;
+ }
+
+ // clear out our game over flag
+ setGameOver(false);
+
+/* TODO
+ // let our delegates do their business
+ applyToDelegates(new DelegateOp() {
+ public void apply (PlaceControllerDelegate delegate) {
+ ((GameControllerDelegate)delegate).gameDidStart();
+ }
+ });
+*/
+ }
+
+ /**
+ * Called when the game transitions to the GAME_OVER
+ * state. This happens when the game reaches some end condition by
+ * normal means (is not cancelled or aborted).
+ */
+ protected function gameDidEnd () :void
+ {
+/* TODO
+ // let our delegates do their business
+ applyToDelegates(new DelegateOp() {
+ public void apply (PlaceControllerDelegate delegate) {
+ ((GameControllerDelegate)delegate).gameDidEnd();
+ }
+ });
+*/
+ }
+
+ /**
+ * Called when the game was cancelled for some reason.
+ */
+ protected function gameWasCancelled () :void
+ {
+/*
+ // let our delegates do their business
+ applyToDelegates(new DelegateOp() {
+ public void apply (PlaceControllerDelegate delegate) {
+ ((GameControllerDelegate)delegate).gameWasCancelled();
+ }
+ });
+*/
+ }
+
+ /**
+ * Called to give derived classes a chance to display animations, send
+ * a final packet, or do any other business they care to do when the
+ * game is about to reset.
+ */
+ protected function gameWillReset () :void
+ {
+/* TODO
+ // let our delegates do their business
+ applyToDelegates(new DelegateOp() {
+ public void apply (PlaceControllerDelegate delegate) {
+ ((GameControllerDelegate)delegate).gameWillReset();
+ }
+ });
+*/
+ }
+
+ /**
+ * Used to determine if this game is a party game.
+ */
+ protected function isPartyGame () :Boolean
+ {
+ return (_config is PartyGameConfig) &&
+ (PartyGameConfig(_config).getPartyGameType() !=
+ PartyGameConfig.NOT_PARTY_GAME);
+ }
+
+ /** A reference to the active parlor context. */
+ protected var _pctx :ParlorContext;
+
+ /** Our game configuration information. */
+ protected var _config :GameConfig;
+
+ /** A reference to the game object for the game that we're
+ * controlling. */
+ protected var _gobj :GameObject;
+
+ /** A local flag overriding the game over state for situations where
+ * the client knows the game is over before the server has
+ * transitioned the game object accordingly. */
+ protected var _gameOver :Boolean;
+}
+}
diff --git a/src/as/com/threerings/parlor/game/client/GameService.as b/src/as/com/threerings/parlor/game/client/GameService.as
new file mode 100644
index 00000000..8458f5f7
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/client/GameService.as
@@ -0,0 +1,39 @@
+//
+// $Id: GameService.java 3600 2005-06-15 23:46:31Z 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.parlor.game.client {
+
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationService;
+
+/**
+ * Provides services used by game clients to request that actions be taken
+ * by the game manager.
+ */
+public interface GameService extends InvocationService
+{
+ /**
+ * Lets the game manager know that the calling player is in the game
+ * room and ready to play.
+ */
+ function playerReady (client :Client) :void;
+}
+}
diff --git a/src/as/com/threerings/parlor/game/data/GameAI.as b/src/as/com/threerings/parlor/game/data/GameAI.as
new file mode 100644
index 00000000..68b8e13c
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/data/GameAI.as
@@ -0,0 +1,47 @@
+//
+// $Id: GameAI.java 3399 2005-03-12 07:37:34Z mdb $
+
+package com.threerings.parlor.game.data {
+
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+import com.threerings.io.Streamable;
+
+/**
+ * Represents attributes of an AI player.
+ */
+public class GameAI
+ implements Streamable
+{
+ /** The "personality" of the AI, which can be interpreted by
+ * each puzzle. */
+ public var personality :int;
+
+ /** The skill level of the AI. */
+ public var skill :int;
+
+ /**
+ * Constructs an AI with the specified (game-interpreted) skill and
+ * personality.
+ */
+ public function GameAI (personality :int = 0, skill :int = 0)
+ {
+ this.personality = personality;
+ this.skill = skill;
+ }
+
+ // from Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeInt(personality);
+ out.writeInt(skill);
+ }
+
+ // from Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ personality = ins.readInt();
+ skill = ins.readInt();
+ }
+}
+}
diff --git a/src/as/com/threerings/parlor/game/data/GameCodes.as b/src/as/com/threerings/parlor/game/data/GameCodes.as
new file mode 100644
index 00000000..938a8dfd
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/data/GameCodes.as
@@ -0,0 +1,45 @@
+//
+// $Id: PuzzleCodes.java 3184 2004-10-28 19:20:27Z mdb $
+//
+// 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.parlor.game.data {
+
+import com.threerings.presents.data.InvocationCodes;
+
+/**
+ * Constants relating to the game services.
+ */
+public class GameCodes extends InvocationCodes
+{
+ /** The message bundle identifier for general game messages. */
+ public static const GAME_MESSAGE_BUNDLE :String = "game.general";
+
+ /** The name of the message event to a placeObject that a player
+ * was knocked out of a puzzle. */
+ public static const PLAYER_KNOCKED_OUT :String = "playerKnocked";
+
+ /** The name of the message event to a placeObject that reports
+ * the winners and losers of a game. */
+ public static const WINNERS_AND_LOSERS :String = "winnersAndLosers";
+
+ /** A chat type for chatting on the game object. */
+ public static const GAME_CHAT_TYPE :String = "gameChat";
+}
+}
diff --git a/src/as/com/threerings/parlor/game/data/GameConfig.as b/src/as/com/threerings/parlor/game/data/GameConfig.as
new file mode 100644
index 00000000..87336a27
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/data/GameConfig.as
@@ -0,0 +1,162 @@
+//
+// $Id: GameConfig.java 4026 2006-04-18 01:32:41Z mdb $
+//
+// 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.parlor.game.data {
+
+import com.threerings.util.ClassUtil;
+import com.threerings.util.Cloneable;
+import com.threerings.util.Hashable;
+import com.threerings.util.Name;
+
+import com.threerings.io.TypedArray;
+
+import com.threerings.crowd.data.PlaceConfig;
+
+import com.threerings.parlor.client.TableConfigurator;
+import com.threerings.parlor.game.client.GameConfigurator;
+
+/**
+ * The game config class encapsulates the configuration information for a
+ * particular type of game. The hierarchy of game config objects mimics the
+ * hierarchy of game managers and controllers. Both the game manager and game
+ * controller are provided with the game config object when the game is
+ * created.
+ *
+ *
The game config object is also the mechanism used to instantiate the
+ * appropriate game manager and controller. Every game must have an associated
+ * game config derived class that overrides {@link #createController} and
+ * {@link #getManagerClassName}, returning the appropriate game controller and
+ * manager class for that game. Thus the entire chain of events that causes a
+ * particular game to be created is the construction of the appropriate game
+ * config instance which is provided to the server as part of an invitation or
+ * via some other matchmaking mechanism.
+ */
+public /*abstract*/ class GameConfig extends PlaceConfig
+ implements Cloneable, Hashable
+{
+ /** The usernames of the players involved in this game, or an empty
+ * array if such information is not needed by this particular game. */
+ public var players :TypedArray = TypedArray.create(Name);
+
+ /** Indicates whether or not this game is rated. */
+ public var rated :Boolean = true;
+
+ /** Configurations for AIs to be used in this game. Slots with real
+ * players should be null and slots with AIs should contain
+ * configuration for those AIs. A null array indicates no use of AIs
+ * at all. */
+ public var ais :TypedArray = TypedArray.create(GameAI);
+
+ /**
+ * Returns the message bundle identifier for the bundle that should be
+ * used to translate the translatable strings used to describe the
+ * game config parameters.
+ */
+ public function getBundleName () :String
+ {
+ Log.getLog(this).warning("this method is actually abstract.");
+ }
+
+ /**
+ * Creates a configurator that can be used to create a user interface
+ * for configuring this instance prior to starting the game. If no
+ * configuration is necessary, this method should return null.
+ */
+ public function createConfigurator () :GameConfigurator
+
+ /**
+ * Creates a table configurator for initializing 'table' properties
+ * of the game. The default implementation returns null.
+ */
+ public function createTableConfigurator () :TableConfigurator
+ {
+ return null;
+ }
+
+ /**
+ * Returns a translatable label describing this game.
+ */
+ public function getGameName () :String
+ {
+ // the whole getRatingTypeId(), getGameName(), getBundleName()
+ // business should be cleaned up. we should have getGameIdent()
+ // and everything should have a default implementation using that
+ return "m." + getBundleName();
+ }
+
+ /**
+ * Returns the game rating type, if the system uses such things.
+ */
+ public function getRatingTypeId () :int
+ {
+ return -1;
+ }
+
+ /**
+ * Returns an Array of strings that describe the configuration of this
+ * game. Default implementation returns an empty array.
+ */
+ public function getDescription () :Array
+ {
+ return new Array(); // nothing by default
+ }
+
+ /**
+ * Returns true if this game config object is equal to the supplied
+ * object (meaning it is also a game config object and its
+ * configuration settings are the same as ours).
+ */
+ public function equals (other :Object) :Boolean
+ {
+ // make sure they're of the same class
+ if (ClassUtil.getClassName(other) == ClassUtil.getClassName(this)) {
+ var that :GameConfig = GameConfig(other);
+ return this.rated == that.rated;
+
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Computes a hashcode for this game config object that supports our
+ * {@link #equals} implementation. Objects that are equal should have
+ * the same hashcode.
+ */
+ public function hashCode () :int
+ {
+ // look ma, it's so sophisticated!
+ return StringUtil.hashCode(ClassUtil.getClassName(this)) +
+ (rated ? 1 : 0);
+ }
+
+ // from Cloneable
+ public function clone () :Object
+ {
+ var copy :GameConfig = ClassUtil.newInstance(this);
+ copy.players = this.players;
+ copy.rated = this.rated;
+ copy.ais = this.ais;
+
+ return copy;
+ }
+}
+}
diff --git a/src/as/com/threerings/parlor/game/data/GameMarshaller.as b/src/as/com/threerings/parlor/game/data/GameMarshaller.as
new file mode 100644
index 00000000..cb125bcf
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/data/GameMarshaller.as
@@ -0,0 +1,49 @@
+//
+// $Id: GameMarshaller.java 4145 2006-05-24 01:24:24Z ray $
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2006 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.parlor.game.data {
+
+import com.threerings.parlor.game.client.GameService;
+import com.threerings.presents.client.Client;
+import com.threerings.presents.data.InvocationMarshaller;
+import com.threerings.presents.dobj.InvocationResponseEvent;
+
+/**
+ * Provides the implementation of the {@link GameService} interface
+ * that marshalls the arguments and delivers the request to the provider
+ * on the server. Also provides an implementation of the response listener
+ * interfaces that marshall the response arguments and deliver them back
+ * to the requesting client.
+ */
+public class GameMarshaller extends InvocationMarshaller
+ implements GameService
+{
+ /** The method id used to dispatch {@link #playerReady} requests. */
+ public static const PLAYER_READY :int = 1;
+
+ // documentation inherited from interface
+ public function playerReady (arg1 :Client) :void
+ {
+ sendRequest(arg1, PLAYER_READY, [ ]);
+ }
+
+}
+}
diff --git a/src/as/com/threerings/parlor/game/data/GameObject.as b/src/as/com/threerings/parlor/game/data/GameObject.as
new file mode 100644
index 00000000..bf04f9dd
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/data/GameObject.as
@@ -0,0 +1,442 @@
+//
+// $Id: GameObject.java 4191 2006-06-13 22:42:20Z 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.parlor.game.data {
+
+import com.threerings.util.Integer;
+import com.threerings.util.langBoolean;
+import com.threerings.util.Name;
+
+import com.threerings.io.TypedArray;
+
+import com.threerings.crowd.data.PlaceObject;
+
+/**
+ * A game object hosts the shared data associated with a game played by
+ * one or more players. The game object extends the place object so that
+ * the game can act as a place where players actually go when playing the
+ * game. Only very basic information is maintained in the base game
+ * object. It serves as the base for a hierarchy of game object
+ * derivatives that handle basic gameplay for a suite of different game
+ * types (ie. turn based games, party games, board games, card games,
+ * etc.).
+ */
+public class GameObject extends PlaceObject
+{
+ // AUTO-GENERATED: FIELDS START
+ /** The field name of the gameService field. */
+ public static const GAME_SERVICE :String = "gameService";
+
+ /** The field name of the state field. */
+ public static const STATE :String = "state";
+
+ /** The field name of the isRated field. */
+ public static const IS_RATED :String = "isRated";
+
+ /** The field name of the isPrivate field. */
+ public static const IS_PRIVATE :String = "isPrivate";
+
+ /** The field name of the players field. */
+ public static const PLAYERS :String = "players";
+
+ /** The field name of the winners field. */
+ public static const WINNERS :String = "winners";
+
+ /** The field name of the roundId field. */
+ public static const ROUND_ID :String = "roundId";
+
+ /** The field name of the playerStatus field. */
+ public static const PLAYER_STATUS :String = "playerStatus";
+ // AUTO-GENERATED: FIELDS END
+
+ /** A game state constant indicating that the game has not yet started
+ * and is still awaiting the arrival of all of the players. */
+ public static const PRE_GAME :int = 0;
+
+ /** A game state constant indicating that the game is in play. */
+ public static const IN_PLAY :int = 1;
+
+ /** A game state constant indicating that the game ended normally. */
+ public static const GAME_OVER :int = 2;
+
+ /** A game state constant indicating that the game was cancelled. */
+ public static const CANCELLED :int = 3;
+
+ /** The player status constant for a player whose game is in play. */
+ public static const PLAYER_IN_PLAY :int = 0;
+
+ /** The player status constant for a player whose has been knocked out
+ * of the game. NOTE: This can include a player choosing to leave a
+ * game prematurely. */
+ public static const PLAYER_LEFT_GAME :int = 1;
+
+ /** Provides general game invocation services. */
+ public var gameService :GameMarshaller;
+
+ /** The game state, one of {@link #PRE_GAME}, {@link #IN_PLAY},
+ * {@link #GAME_OVER}, or {@link #CANCELLED}. */
+ public var state :int = PRE_GAME;
+
+ /** Indicates whether or not this game is rated. */
+ public var isRated :Boolean;
+
+ /** Indicates whether the game is "private". */
+ public var isPrivate :Boolean;
+
+ /** The usernames of the players involved in this game. */
+ public var players :TypedArray; /* of Name */
+
+ /** Whether each player in the game is a winner, or null
+ * if the game is not yet over. */
+ public var winners :TypedArray; /* of Boolean */
+
+ /** The unique round identifier for the current round. */
+ public var roundId :int;
+
+ /** If null, indicates that all present players are active, or for
+ * more complex games can be non-null to indicate the current status
+ * of each player in the game. The status value is one of
+ * {@link #PLAYER_LEFT_GAME} or {@link #PLAYER_IN_PLAY}. */
+ public var playerStatus :TypedArray; /* of int */
+
+ /**
+ * Returns the number of players in the game.
+ */
+ public function getPlayerCount () :int
+ {
+ var count :int = 0;
+ var size :int = players.length;
+ for (var ii :int = 0; ii < size; ii++) {
+ if (players[ii] != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns the number of active players in the game.
+ */
+ public function getActivePlayerCount () :int
+ {
+ var count :int = 0;
+ var size :int = players.length;
+ for (var ii :int = 0; ii < size; ii++) {
+ if (isActivePlayer(ii)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns whether the given player is still an active player in
+ * the game. (Ie. whether or not they are still participating.)
+ */
+ public function isActivePlayer (pidx :int) :Boolean
+ {
+ return isOccupiedPlayer(pidx) &&
+ (playerStatus == null || isActivePlayerStatus(playerStatus[pidx]));
+ }
+
+ /**
+ * Returns the player index of the given user in the game, or
+ * -1 if the player is not involved in the game.
+ */
+ public function getPlayerIndex (username :Name) :int
+ {
+ return ArrayUtil.indexOf(players, username);
+ }
+
+ /**
+ * Returns whether the game is in play. A game that is not in play
+ * could either be awaiting players, ended, or cancelled.
+ */
+ public function isInPlay () :Boolean
+ {
+ return (state == IN_PLAY);
+ }
+
+ /**
+ * Returns whether the given player index in the game is occupied.
+ */
+ public function isOccupiedPlayer (pidx :int) :Boolean
+ {
+ return (pidx >= 0 && pidx < players.length) && (players[pidx] != null);
+ }
+
+ /**
+ * Returns whether the given player index is a winner, or false if the
+ * winners are not yet assigned.
+ */
+ public function isWinner (pidx :int) :Boolean
+ {
+ return (winners != null) && winners[pidx];
+ }
+
+ /**
+ * Returns the number of winners for this game, or 0 if
+ * the winners array is not populated, e.g., the game is not yet over.
+ */
+ public function getWinnerCount () :int
+ {
+ int count = 0;
+ if (winners != null) {
+ for (var ii :int = 0; ii < winners.length; ii++) {
+ if (winners[ii]) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns true if the game is ended in a draw.
+ */
+ public function isDraw () :Boolean
+ {
+ return getWinnerCount() == getPlayerCount();
+ }
+
+ /**
+ * Returns the winner index of the first winning player for this game,
+ * or -1 if there are no winners or the winners array is
+ * not yet assigned. This is only likely to be useful for games that
+ * are known to have a single winner.
+ */
+ public function getWinnerIndex () :int
+ {
+ return ArrayUtil.indexOf(winners, true);
+ }
+
+ /**
+ * Returns the type of party game being played or NOT_PARTY_GAME.
+ * {@link PartyGameConfig}.
+ */
+ public function getPartyGameType () :int
+ {
+ return PartyGameConfig.NOT_PARTY_GAME;
+ }
+
+ /**
+ * Used by {@link #isActivePlayer} to determine if the supplied status is
+ * associated with an active player (one that has not resigned from the
+ * game and/or left the game room).
+ */
+ protected function isActivePlayerStatus (playerStatus :int) :Boolean
+ {
+ return playerStatus == PLAYER_IN_PLAY;
+ }
+
+ override protected function whichBuf (buf :StringBuilder) :void
+ {
+ super.whichBuf(buf);
+ buf.append(players.join());
+ buf.append(":").append(state);
+ }
+
+ // AUTO-GENERATED: METHODS START
+ /**
+ * Requests that the gameService field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setGameService (value :GameMarshaller) :void
+ {
+ var ovalue :GameMarshaller = this.gameService;
+ requestAttributeChange(
+ GAME_SERVICE, value, ovalue);
+ this.gameService = value;
+ }
+
+ /**
+ * Requests that the state field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setState (value :int) :void
+ {
+ var ovalue :int = this.state;
+ requestAttributeChange(
+ STATE, Integer.valueOf(value), Integer.valueOf(ovalue));
+ this.state = value;
+ }
+
+ /**
+ * Requests that the isRated field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setIsRated (value :Boolean) :void
+ {
+ var ovalue :Boolean = this.isRated;
+ requestAttributeChange(
+ IS_RATED, langBoolean.valueOf(value), langBoolean.valueOf(ovalue));
+ this.isRated = value;
+ }
+
+ /**
+ * Requests that the isPrivate field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setIsPrivate (value :Boolean) :void
+ {
+ var ovalue :Boolean = this.isPrivate;
+ requestAttributeChange(
+ IS_PRIVATE, langBoolean.valueOf(value),
+ langBoolean.valueOf(ovalue));
+ this.isPrivate = value;
+ }
+
+ /**
+ * Requests that the players field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setPlayers (value :TypedArray) :void
+ {
+ var ovalue :TypedArray = this.players;
+ requestAttributeChange(
+ PLAYERS, value, ovalue);
+ this.players = (value == null) ? null : (value.clone() as TypedArray);
+ }
+
+ /**
+ * Requests that the indexth element of
+ * players field be set to the specified value.
+ * The local value will be updated immediately and an event will be
+ * propagated through the system to notify all listeners that the
+ * attribute did change. Proxied copies of this object (on clients)
+ * will apply the value change when they received the attribute
+ * changed notification.
+ */
+ public function setPlayersAt (value :Name, index :int) :void
+ {
+ var ovalue :Name = (this.players[index] as Name);
+ requestElementUpdate(
+ PLAYERS, index, value, ovalue);
+ this.players[index] = value;
+ }
+
+ /**
+ * Requests that the winners field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setWinners (value :TypedArray) :void
+ {
+ var ovalue :TypedArray = this.winners;
+ requestAttributeChange(
+ WINNERS, value, ovalue);
+ this.winners = (value == null) ? null : (value.clone() as TypedArray);
+ }
+
+ /**
+ * Requests that the indexth element of
+ * winners field be set to the specified value.
+ * The local value will be updated immediately and an event will be
+ * propagated through the system to notify all listeners that the
+ * attribute did change. Proxied copies of this object (on clients)
+ * will apply the value change when they received the attribute
+ * changed notification.
+ */
+ public function setWinnersAt (value :Boolean, index :int) :void
+ {
+ var ovalue :Boolean = (this.winners[index] as Boolean);
+ requestElementUpdate(
+ WINNERS, index, langBoolean.valueOf(value),
+ langBoolean.valueOf(ovalue));
+ this.winners[index] = value;
+ }
+
+ /**
+ * Requests that the roundId field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setRoundId (value :int) :void
+ {
+ var ovalue :int = this.roundId;
+ requestAttributeChange(
+ ROUND_ID, Integer.valueOf(value), Integer.valueOf(ovalue));
+ this.roundId = value;
+ }
+
+ /**
+ * Requests that the playerStatus field be set to the
+ * specified value. The local value will be updated immediately and an
+ * event will be propagated through the system to notify all listeners
+ * that the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public function setPlayerStatus (value :TypedArray) :void
+ {
+ var ovalue :TypedArray = this.playerStatus;
+ requestAttributeChange(
+ PLAYER_STATUS, value, ovalue);
+ this.playerStatus = (value == null) ? null
+ : (value.clone() as TypedArray);
+ }
+
+ /**
+ * Requests that the indexth element of
+ * playerStatus field be set to the specified value.
+ * The local value will be updated immediately and an event will be
+ * propagated through the system to notify all listeners that the
+ * attribute did change. Proxied copies of this object (on clients)
+ * will apply the value change when they received the attribute
+ * changed notification.
+ */
+ public function setPlayerStatusAt (value :int, index :int) :void
+ {
+ var ovalue :int = (this.playerStatus[index] as int);
+ requestElementUpdate(
+ PLAYER_STATUS, index, Integer.valueOf(value),
+ Integer.valueOf(ovalue));
+ this.playerStatus[index] = value;
+ }
+ // AUTO-GENERATED: METHODS END
+}
+}
diff --git a/src/as/com/threerings/parlor/game/data/PartyGameConfig.as b/src/as/com/threerings/parlor/game/data/PartyGameConfig.as
new file mode 100644
index 00000000..f678d3a3
--- /dev/null
+++ b/src/as/com/threerings/parlor/game/data/PartyGameConfig.as
@@ -0,0 +1,51 @@
+//
+// $Id: PartyGameConfig.java 3804 2006-01-13 01:52:36Z 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.parlor.game.data {
+
+/**
+ * Provides additional information for party games.
+ * A party game is a game in which the players are not set prior to starting,
+ * but players can come and go at will during the normal progression of
+ * the game.
+ */
+public interface PartyGameConfig
+{
+ /** Party game constant indicating that this game, while it does
+ * implement PartyGameConfig, is not currently being played in party
+ * mode. */
+ public static const NOT_PARTY_GAME :int = 0;
+
+ /** Party game constant indicating that we're in a party game in which
+ * players must sit at an available seat to play, otherwise they're
+ * an observer. */
+ public static const SEATED_PARTY_GAME :int = 1;
+
+ /** Party game constant indicating that everyone in the game place is
+ * a "player", meaning that they do not need to claim a seat to play. */
+ public static const FREE_FOR_ALL_PARTY_GAME :int = 2;
+
+ /**
+ * Get the type of party game being played.
+ */
+ function getPartyGameType () :int;
+}
+}
diff --git a/src/as/com/threerings/parlor/util/ParlorContext.as b/src/as/com/threerings/parlor/util/ParlorContext.as
new file mode 100644
index 00000000..4f4fb5e4
--- /dev/null
+++ b/src/as/com/threerings/parlor/util/ParlorContext.as
@@ -0,0 +1,38 @@
+//
+// $Id: ParlorContext.java 4191 2006-06-13 22:42:20Z 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.parlor.util {
+
+import com.threerings.crowd.util.CrowdContext;
+import com.threerings.parlor.client.ParlorDirector;
+
+/**
+ * The parlor context provides access to the various managers, etc. that
+ * are needed by the parlor client code.
+ */
+public interface ParlorContext extends CrowdContext
+{
+ /**
+ * Returns a reference to the parlor director.
+ */
+ function getParlorDirector () :ParlorDirector;
+}
+}