Checkpoint: converting parlor stuff to actionscript.

git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@30 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Ray Greenwell
2006-07-28 02:47:21 +00:00
parent b82ab59b1d
commit 7ee319bb2b
33 changed files with 3870 additions and 0 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
}
}
@@ -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();
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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 <code>TableLobbyController</code> 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.
*
* <p> 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.
*
* <p> 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();
}
}
@@ -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;
}
}