Added a server managed notion of which client is controlling the game.

Doing this on the client, while theoretically possible, is more complex. We
have a server, we use it to provide commonly needed services, the assignment of
a single client to control the game is a commonly needed service. This also
matches the way other services like turn change and game start and end are
implemented.

A side note: the client-side code was not properly handling disconnected
players, which the server code properly handles.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@224 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Michael Bayne
2007-03-04 20:05:58 +00:00
parent 3c595d85a5
commit d77b8363f7
8 changed files with 318 additions and 268 deletions
+1 -1
View File
@@ -149,7 +149,7 @@
</javac>
</target>
<!-- build the javadoc documentation -->
<!-- builds the javadoc documentation -->
<target name="javadoc" depends="prepare">
<javadoc sourcepath="src/java" packagenames="com.threerings.*"
destdir="${javadoc.home}" stylesheetfile="docs/stylesheet.css"
+124 -116
View File
@@ -44,6 +44,13 @@ import flash.display.DisplayObject;
*/
[Event(name="keyUp", type="flash.events.KeyboardEvent")]
/**
* Dispatched when the controller changes for the game.
*
* @eventType com.threerings.ezgame.StateChangedEvent.CONTROL_CHANGED
*/
[Event(name="ControlChanged", type="com.threerings.ezgame.StateChangedEvent")]
/**
* Dispatched when the game starts, usually after all players are present.
*
@@ -56,7 +63,7 @@ import flash.display.DisplayObject;
*
* @eventType com.threerings.ezgame.StateChangedEvent.TURN_CHANGED
*/
[Event(name="GameStarted", type="com.threerings.ezgame.StateChangedEvent")]
[Event(name="TurnChanged", type="com.threerings.ezgame.StateChangedEvent")]
/**
* Dispatched when the game ends.
@@ -73,8 +80,7 @@ import flash.display.DisplayObject;
[Event(name="PropChanged", type="com.threerings.ezgame.PropertyChangedEvent")]
/**
* Dispatched when a message arrives with information that is not part
* of the shared game state.
* Dispatched when a message arrives with information that is not part of the shared game state.
*
* @eventType com.threerings.ezgame.MessageReceivedEvent.TYPE
*/
@@ -89,8 +95,7 @@ import flash.display.DisplayObject;
public class EZGameControl extends BaseControl
{
/**
* Create an EZGameControl object using some display object currently
* on the hierarchy.
* Create an EZGameControl object using some display object currently on the hierarchy.
*/
public function EZGameControl (disp :DisplayObject)
{
@@ -114,8 +119,7 @@ public class EZGameControl extends BaseControl
type :String, listener :Function, useCapture :Boolean = false,
priority :int = 0, useWeakReference :Boolean = false) :void
{
super.addEventListener(type, listener, useCapture, priority,
useWeakReference);
super.addEventListener(type, listener, useCapture, priority, useWeakReference);
switch (type) {
case KeyboardEvent.KEY_UP:
@@ -144,8 +148,8 @@ public class EZGameControl extends BaseControl
}
/**
* Are we connected and running inside the EZGame environment, or
* has someone just loaded up this swf by itself?
* Are we connected and running inside the EZGame environment, or has someone just loaded up
* this swf by itself?
*/
public function isConnected () :Boolean
{
@@ -153,8 +157,8 @@ public class EZGameControl extends BaseControl
}
/**
* Get the CollectionsControl, which contains methods for utilizing
* the server to dispatch private information.
* Get the CollectionsControl, which contains methods for utilizing the server to dispatch
* private information.
*/
public function get collections () :CollectionsControl
{
@@ -165,8 +169,8 @@ public class EZGameControl extends BaseControl
}
/**
* Get the SeatingControl, which contains methods for checking
* and assigning player seating positions.
* Get the SeatingControl, which contains methods for checking and assigning player seating
* positions.
*/
public function get seating () :SeatingControl
{
@@ -186,8 +190,7 @@ public class EZGameControl extends BaseControl
return (value as Array)[index];
} else {
throw new ArgumentError("Property " + propName +
" is not an array.");
throw new ArgumentError("Property " + propName + " is not an array.");
}
}
return value;
@@ -202,13 +205,12 @@ public class EZGameControl extends BaseControl
}
/**
* Set a property, but have this client immediately set the value so that
* it can be re-read. The property change event will still arrive and
* will be your clue as to when the other clients will see the newly
* set value. Be careful with this method, as it can allow data
* inconsistency: two clients may see different values for a property
* if one of them recently set it immediately, and the resultant
* PropertyChangedEvent's oldValue also may not be consistent.
* Set a property, but have this client immediately set the value so that it can be
* re-read. The property change event will still arrive and will be your clue as to when the
* other clients will see the newly set value. Be careful with this method, as it can allow
* data inconsistency: two clients may see different values for a property if one of them
* recently set it immediately, and the resultant PropertyChangedEvent's oldValue also may not
* be consistent.
*/
public function setImmediate (propName :String, value :Object, index :int = -1) :void
{
@@ -216,18 +218,17 @@ public class EZGameControl extends BaseControl
}
/**
* Set a property that will be distributed, but only if it's equal
* to the specified test value.
* Set a property that will be distributed, but only if it's equal to the specified test value.
*
* Please note that, unlike in the standard set() function, the property
* will not be updated right away, but will require a request to the server
* and a response back. For this reason, there may be a considerable delay
* between calling testAndSet, and seeing the result of the update.
* <p> Please note that, unlike in the standard set() function, the property will not be
* updated right away, but will require a request to the server and a response back. For this
* reason, there may be a considerable delay between calling testAndSet, and seeing the result
* of the update.
*
* The operation is 'atomic', in the sense that testing and setting take place
* during the same server event. In comparison, a separate 'get' followed by
* a 'set' operation would involve two events with two network round-trips,
* and no guarantee that the value won't change between the events.
* <p> The operation is 'atomic', in the sense that testing and setting take place during the
* same server event. In comparison, a separate 'get' followed by a 'set' operation would
* involve two events with two network round-trips, and no guarantee that the value won't
* change between the events.
*/
public function testAndSet (
propName :String, newValue :Object, testValue :Object, index :int = -1) :void
@@ -236,41 +237,31 @@ public class EZGameControl extends BaseControl
}
/**
* Register an object to receive whatever events it should receive,
* based on which event listeners it implements.
* Register an object to receive whatever events it should receive, based on which event
* listeners it implements.
*/
public function registerListener (obj :Object) :void
{
if (obj is MessageReceivedListener) {
var mrl :MessageReceivedListener = (obj as MessageReceivedListener);
addEventListener(
MessageReceivedEvent.TYPE, mrl.messageReceived,
false, 0, true);
addEventListener(MessageReceivedEvent.TYPE, mrl.messageReceived, false, 0, true);
}
if (obj is PropertyChangedListener) {
var pcl :PropertyChangedListener = (obj as PropertyChangedListener);
addEventListener(
PropertyChangedEvent.TYPE, pcl.propertyChanged,
false, 0, true);
addEventListener(PropertyChangedEvent.TYPE, pcl.propertyChanged, false, 0, true);
}
if (obj is StateChangedListener) {
var scl :StateChangedListener = (obj as StateChangedListener);
addEventListener(
StateChangedEvent.GAME_STARTED, scl.stateChanged,
false, 0, true);
addEventListener(
StateChangedEvent.TURN_CHANGED, scl.stateChanged,
false, 0, true);
addEventListener(
StateChangedEvent.GAME_ENDED, scl.stateChanged,
false, 0, true);
addEventListener(StateChangedEvent.CONTROL_CHANGED, scl.stateChanged, false, 0, true);
addEventListener(StateChangedEvent.GAME_STARTED, scl.stateChanged, false, 0, true);
addEventListener(StateChangedEvent.TURN_CHANGED, scl.stateChanged, false, 0, true);
addEventListener(StateChangedEvent.GAME_ENDED, scl.stateChanged, false, 0, true);
}
if (obj is OccupantChangedListener) {
var ocl :OccupantChangedListener = (obj as OccupantChangedListener);
addEventListener(OccupantChangedEvent.OCCUPANT_ENTERED, ocl.occupantEntered,
false, 0, true);
addEventListener(OccupantChangedEvent.OCCUPANT_LEFT, ocl.occupantLeft,
false, 0, true);
false, 0, true);
addEventListener(OccupantChangedEvent.OCCUPANT_LEFT, ocl.occupantLeft, false, 0, true);
}
}
@@ -306,61 +297,54 @@ public class EZGameControl extends BaseControl
}
/**
* Requests a set of random letters from the dictionary service.
* The letters will arrive in a separate message with the specified key,
* as an array of strings.
* Requests a set of random letters from the dictionary service. The letters will arrive in a
* separate message with the specified key, as an array of strings.
*
* @param locale RFC 3066 string that represents language settings
* @param count the number of letters to be produced
* @param callback the function that will process the results, of the form:
* function (letters :Array) :void
* where letters is an array of strings containing letters
* for the given language settings (potentially empty).
* <pre>function (letters :Array) :void</pre>
* where letters is an array of strings containing letters for the given language settings
* (potentially empty).
*/
public function getDictionaryLetterSet (
locale :String, count :int, callback :Function) :void
public function getDictionaryLetterSet (locale :String, count :int, callback :Function) :void
{
callEZCode("getDictionaryLetterSet_v1", locale, count, callback);
}
/**
* Requests a set of random letters from the dictionary service.
* The letters will arrive in a separate message with the specified key,
* as an array of strings.
* Requests a set of random letters from the dictionary service. The letters will arrive in a
* separate message with the specified key, as an array of strings.
*
* @param RFC 3066 string that represents language settings
* @param word the string contains the word to be checked
* @param callback the function that will process the results, of the form:
* function (word :String, result :Boolean) :void
* where word is a copy of the word that was requested, and result
* specifies whether the word is valid given language settings
* <pre>function (word :String, result :Boolean) :void</pre>
* where word is a copy of the word that was requested, and result specifies whether the word
* is valid given language settings
*/
public function checkDictionaryWord (
locale :String, word :String, callback :Function) :void
public function checkDictionaryWord (locale :String, word :String, callback :Function) :void
{
callEZCode("checkDictionaryWord_v1", locale, word, callback);
}
/**
* Send a "message" to other clients subscribed to the game.
* These is similar to setting a property, except that the
* value will not be saved- it will merely end up coming out
* as a MessageReceivedEvent.
* Send a "message" to other clients subscribed to the game. These is similar to setting a
* property, except that the value will not be saved- it will merely end up coming out as a
* MessageReceivedEvent.
*
* @param playerId if 0 (or unset), sends to all players, otherwise
* the message will be private to just one player
* @param playerId if 0 (or unset), sends to all players, otherwise the message will be private
* to just one player
*/
public function sendMessage (
messageName :String, value :Object, playerId :int = 0) :void
public function sendMessage (messageName :String, value :Object, playerId :int = 0) :void
{
callEZCode("sendMessage_v2", messageName, value, playerId);
}
/**
* Start the ticker with the specified name. It will deliver
* messages to the game object at the specified delay,
* the value of each message being a single integer, starting with 0
* and increasing by one with each messsage.
* Start the ticker with the specified name. It will deliver messages to the game object at the
* specified delay, the value of each message being a single integer, starting with 0 and
* increasing by one with each messsage.
*/
public function startTicker (tickerName :String, msOfDelay :int) :void
{
@@ -376,8 +360,7 @@ public class EZGameControl extends BaseControl
}
/**
* Send a message that will be heard by everyone in the game room,
* even observers.
* Send a message that will be heard by everyone in the game room, even observers.
*/
public function sendChat (msg :String) :void
{
@@ -385,25 +368,25 @@ public class EZGameControl extends BaseControl
}
/**
* Display the specified message immediately locally: not sent
* to any other players or observers in the game room.
* Display the specified message immediately locally: not sent to any other players or
* observers in the game room.
*/
public function localChat (msg :String) :void
{
callEZCode("localChat_v1", msg);
}
// TODO: NEW
/**
* Returns the player ids of all occupants in the game room.
*/
public function getOccupants () :Array /* of playerId */
{
return (callEZCode("getOccupants_v1") as Array);
}
// TODO: NEW
/**
* Get the display name of the specified occupant.
* Two players may have the same name: always use playerId to
* purposes of identification and comparison. The name is for display
* Get the display name of the specified occupant. Two players may have the same name: always
* use playerId to purposes of identification and comparison. The name is for display
* only. Will be null is the specified playerId is not in the game.
*/
public function getOccupantName (playerId :int) :String
@@ -411,21 +394,42 @@ public class EZGameControl extends BaseControl
return String(callEZCode("getOccupantName_v1", playerId));
}
// TODO: NEW
/**
* Returns this client's player id.
*/
public function getMyId () :int
{
return int(callEZCode("getMyId_v1"));
}
/**
* Returns true if we are in control of this game. False if another client is in control.
*/
public function amInControl () :Boolean
{
return getControllerId() == getMyId();
}
/**
* Returns the player id of the client that is in control of this game.
*/
public function getControllerId () :int
{
return int(callEZCode("getControllerId_v1"));
}
/**
* Returns the player id of the current turn holder.
*/
public function getTurnHolder () :int
{
return int(callEZCode("getTurnHolder_v1"));
}
/**
* Get the user-specific game data for the specified user. The
* first time this is requested per game instance it will be retrieved
* from the database. After that, it will be returned from memory.
* Get the user-specific game data for the specified user. The first time this is requested per
* game instance it will be retrieved from the database. After that, it will be returned from
* memory.
*/
public function getUserCookie (occupantId :int, callback :Function) :void
{
@@ -433,18 +437,14 @@ public class EZGameControl extends BaseControl
}
/**
* Store persistent data that can later be retrieved by an instance
* of this game. The maximum size of this data is 4096 bytes AFTER
* AMF3 encoding.
* Store persistent data that can later be retrieved by an instance of this game. The maximum
* size of this data is 4096 bytes AFTER AMF3 encoding. Note: there is no playerId parameter
* because a cookie may only be stored for the current player.
*
* Note: there is no playerIndex parameter because a cookie may only
* be stored for the current player.
*
* @return false if the cookie could not be encoded to 4096 bytes
* or less; true if the cookie is going to try to be saved. There is
* no guarantee it will be saved and no way to find out if it failed,
* but if it fails it will be because the shit hit the fan so hard that
* there's nothing you can do anyway.
* @return false if the cookie could not be encoded to 4096 bytes or less; true if the cookie
* is going to try to be saved. There is no guarantee it will be saved and no way to find out
* if it failed, but if it fails it will be because the shit hit the fan so hard that there's
* nothing you can do anyway.
*/
public function setUserCookie (cookie :Object) :Boolean
{
@@ -468,8 +468,8 @@ public class EZGameControl extends BaseControl
}
/**
* End the current turn. If no next player id is specified,
* then the next player after the current one is used.
* End the current turn. If no next player id is specified, then the next player after the
* current one is used.
*/
public function endTurn (nextPlayerId :int = 0) :void
{
@@ -489,12 +489,13 @@ public class EZGameControl extends BaseControl
}
/**
* Populate any properties or functions we want to expose to
* the other side of the ezgame security boundary.
* Populate any properties or functions we want to expose to the other side of the ezgame
* security boundary.
*/
protected function populateProperties (o :Object) :void
{
o["propertyWasSet_v1"] = propertyWasSet_v1;
o["controlDidChange_v1"] = controlDidChange_v1;
o["turnDidChange_v1"] = turnDidChange_v1;
o["messageReceived_v1"] = messageReceived_v1;
o["gameDidStart_v1"] = gameDidStart_v1;
@@ -513,6 +514,14 @@ public class EZGameControl extends BaseControl
new PropertyChangedEvent(this, name, newValue, oldValue, index));
}
/**
* Private method to post a StateChangedEvent.
*/
private function controlDidChange_v1 () :void
{
dispatch(new StateChangedEvent(StateChangedEvent.CONTROL_CHANGED, this));
}
/**
* Private method to post a StateChangedEvent.
*/
@@ -551,14 +560,13 @@ public class EZGameControl extends BaseControl
private function occupantChanged_v1 (occupantId :int, player :Boolean, enter :Boolean) :void
{
dispatch(new OccupantChangedEvent(
enter ? OccupantChangedEvent.OCCUPANT_ENTERED
: OccupantChangedEvent.OCCUPANT_LEFT,
this, occupantId, player));
enter ? OccupantChangedEvent.OCCUPANT_ENTERED :
OccupantChangedEvent.OCCUPANT_LEFT, this, occupantId, player));
}
/**
* Sets the properties we received from the EZ game framework
* on the other side of the security boundary.
* Sets the properties we received from the EZ game framework on the other side of the security
* boundary.
*/
protected function setEZProps (o :Object) :void
{
@@ -606,9 +614,9 @@ public class EZGameControl extends BaseControl
protected function checkIsConnected () :void
{
if (!isConnected()) {
throw new IllegalOperationError("The game is not connected " +
"to The Whirled, please check isConnected(). If false, your " +
"game is being viewed standalone and should adjust.");
throw new IllegalOperationError(
"The game is not connected to The Whirled, please check isConnected(). " +
"If false, your game is being viewed standalone and should adjust.");
}
}
@@ -34,6 +34,9 @@ public class StateChangedEvent extends EZEvent
/** Indicates that the game has transitioned to a ended state. */
public static const GAME_ENDED :String = "GameEnded";
/** Indicates that a new controller has been assigned. */
public static const CONTROL_CHANGED :String = "ControlChanged";
/** Indicates that the turn has changed. */
// TODO: move to own event?
public static const TURN_CHANGED :String = "TurnChanged";
@@ -25,6 +25,8 @@ import flash.events.Event;
import com.threerings.util.Name;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceConfig;
@@ -44,22 +46,21 @@ import com.threerings.ezgame.data.EZGameObject;
public class EZGameController extends GameController
implements TurnGameController
{
/**
*/
public function EZGameController ()
{
addDelegate(_turnDelegate = new TurnGameControllerDelegate(this));
}
/**
* This is called by the GameControlBackend once it has initialized
* and made contact with usercode.
* This is called by the GameControlBackend once it has initialized and made contact with
* usercode.
*/
public function userCodeIsConnected () :void
{
playerReady();
}
// from PlaceController
override public function willEnterPlace (plobj :PlaceObject) :void
{
_ezObj = (plobj as EZGameObject);
@@ -67,6 +68,7 @@ public class EZGameController extends GameController
super.willEnterPlace(plobj);
}
// from PlaceController
override public function didLeavePlace (plobj :PlaceObject) :void
{
super.didLeavePlace(plobj);
@@ -80,13 +82,24 @@ public class EZGameController extends GameController
_panel.backend.turnDidChange();
}
// from GameController
override public function attributeChanged (event :AttributeChangedEvent) :void
{
var name :String = event.getName();
if (EZGameObject.CONTROLLER_OID == name) {
_panel.backend.controlDidChange();
} else {
super.attributeChanged(event);
}
}
// from GameController
override protected function playerReady () :void
{
// we require the user to be connected, and we redundantly only
// do this if the user is in the players array
// we require the user to be connected, and we redundantly only do this if the user is in
// the players array
if (_panel.backend.isConnected()) {
var bobj :BodyObject =
(_ctx.getClient().getClientObject() as BodyObject);
var bobj :BodyObject = (_ctx.getClient().getClientObject() as BodyObject);
if (_gobj.getPlayerIndex(bobj.getVisibleName()) != -1) {
super.playerReady();
}
@@ -96,36 +109,28 @@ public class EZGameController extends GameController
}
}
// from GameController
override protected function gameDidStart () :void
{
super.gameDidStart();
_panel.backend.gameDidStart();
}
// from GameController
override protected function gameDidEnd () :void
{
super.gameDidEnd();
_panel.backend.gameDidEnd();
}
// from PlaceController
override protected function createPlaceView (ctx :CrowdContext) :PlaceView
{
return new EZGamePanel(ctx, this);
}
override protected function didInit () :void
{
super.didInit();
// retain a casted reference to our panel
_panel = (_view as EZGamePanel);
return _panel = new EZGamePanel(ctx, this);
}
protected var _ezObj :EZGameObject;
protected var _turnDelegate :TurnGameControllerDelegate;
protected var _panel :EZGamePanel;
}
}
@@ -189,6 +189,7 @@ public class GameControlBackend
o["sendMessage_v2"] = sendMessage_v2;
o["getOccupants_v1"] = getOccupants_v1;
o["getMyId_v1"] = getMyId_v1;
o["getControllerId_v1"] = getControllerId_v1;
o["getUserCookie_v2"] = getUserCookie_v2;
o["endTurn_v2"] = endTurn_v2;
o["endGame_v2"] = endGame_v2;
@@ -316,6 +317,11 @@ public class GameControlBackend
return _ctx.getClient().getClientObject().getOid();
}
public function getControllerId_v1 () :int
{
return _ezObj.controllerOid;
}
// TODO: table games only
public function getPlayerPosition_v1 (playerId :int) :int
{
@@ -659,6 +665,14 @@ public class GameControlBackend
}
}
/**
* Called by the EZGameController when the controller changes.
*/
public function controlDidChange () :void
{
callUserCode("controlDidChange_v1");
}
/**
* Called by the EZGameController when the turn changes.
*/
@@ -51,6 +51,9 @@ public class EZGameObject extends GameObject
public static const TICKER :String = "Utick";
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>controllerOid</code> field. */
public static const CONTROLLER_OID :String = "controllerOid";
/** The field name of the <code>turnHolder</code> field. */
public static const TURN_HOLDER :String = "turnHolder";
@@ -61,6 +64,10 @@ public class EZGameObject extends GameObject
public static const EZ_GAME_SERVICE :String = "ezGameService";
// AUTO-GENERATED: FIELDS END
/** The client that is in control of this game. The first client to enter will be assigned
* control and control will subsequently be reassigned if that client disconnects or leaves. */
public var controllerOid :int;
/** The current turn holder. */
public var turnHolder :Name;
@@ -96,30 +103,11 @@ public class EZGameObject extends GameObject
return players;
}
// // AUTO-GENERATED: METHODS START
// /**
// * Requests that the <code>turnHolder</code> 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 setTurnHolder (value :Name) :void
// {
// var ovalue :Name = this.turnHolder;
// requestAttributeChange(
// TURN_HOLDER, value, ovalue);
// this.turnHolder = value;
// }
// // AUTO-GENERATED: METHODS END
/**
* Called by a PropertySetEvent to enact a property change.
* @return the old value
*/
public function applyPropertySet (
propName :String, value :Object, index :int) :Object
public function applyPropertySet (propName :String, value :Object, index :int) :Object
{
var oldValue :Object = _props[propName];
if (index >= 0) {
@@ -143,20 +131,6 @@ public class EZGameObject extends GameObject
return oldValue;
}
// override public function writeObject (out :ObjectOutputStream) :void
// {
// super.writeObject(out);
//
// out.writeObject(turnHolder);
// out.writeObject(ezGameService);
//
// out.writeInt(_props.length);
// for (var key :String in _props) {
// out.writeUTF(key);
// out.writeObject(EZObjectMarshaller.encode(_props[key]));
// }
// }
override public function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
@@ -178,6 +152,7 @@ public class EZGameObject extends GameObject
*/
protected function readDefaultFields (ins :ObjectInputStream) :void
{
controllerOid = ins.readInt();
turnHolder = (ins.readObject() as Name);
userCookies = (ins.readObject() as DSet);
ezGameService = (ins.readObject() as EZGameMarshaller);
@@ -58,6 +58,9 @@ public class EZGameObject extends GameObject
public static final String TICKER = "Utick";
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>controllerOid</code> field. */
public static final String CONTROLLER_OID = "controllerOid";
/** The field name of the <code>turnHolder</code> field. */
public static final String TURN_HOLDER = "turnHolder";
@@ -68,6 +71,10 @@ public class EZGameObject extends GameObject
public static final String EZ_GAME_SERVICE = "ezGameService";
// AUTO-GENERATED: FIELDS END
/** The client that is in control of this game. The first client to enter will be assigned
* control and control will subsequently be reassigned if that client disconnects or leaves. */
public int controllerOid;
/** The current turn holder. */
public Name turnHolder;
@@ -223,6 +230,22 @@ public class EZGameObject extends GameObject
// AUTO-GENERATED: METHODS START
/**
* Requests that the <code>controllerOid</code> 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 void setControllerOid (int value)
{
int ovalue = this.controllerOid;
requestAttributeChange(
CONTROLLER_OID, Integer.valueOf(value), Integer.valueOf(ovalue));
this.controllerOid = value;
}
/**
* Requests that the <code>turnHolder</code> field be set to the
* specified value. The local value will be updated immediately and an
@@ -47,6 +47,7 @@ import com.threerings.presents.client.InvocationService;
import com.threerings.presents.server.InvocationException;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.CrowdServer;
@@ -92,9 +93,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void endTurn (
ClientObject caller, int nextPlayerId,
InvocationService.InvocationListener listener)
public void endTurn (ClientObject caller, int nextPlayerId,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateStateModification(caller);
@@ -111,9 +111,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void endGame (
ClientObject caller, int[] winnerOids,
InvocationService.InvocationListener listener)
public void endGame (ClientObject caller, int[] winnerOids,
InvocationService.InvocationListener listener)
throws InvocationException
{
if (!_gameObj.isInPlay()) {
@@ -126,27 +125,23 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void sendMessage (
ClientObject caller, String msg, Object data, int playerId,
InvocationService.InvocationListener listener)
public void sendMessage (ClientObject caller, String msg, Object data, int playerId,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateUser(caller);
if (playerId == 0) {
_gameObj.postMessage(EZGameObject.USER_MESSAGE,
new Object[] { msg, data });
_gameObj.postMessage(EZGameObject.USER_MESSAGE, new Object[] { msg, data });
} else {
sendPrivateMessage(playerId, msg, data);
}
}
// from EZGameProvider
public void setProperty (
ClientObject caller, String propName, Object data, int index,
boolean testAndSet, Object testValue,
InvocationService.InvocationListener listener)
public void setProperty (ClientObject caller, String propName, Object data, int index,
boolean testAndSet, Object testValue,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateUser(caller);
@@ -157,9 +152,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void getDictionaryLetterSet (
ClientObject caller, String locale, int count,
InvocationService.ResultListener listener)
public void getDictionaryLetterSet (ClientObject caller, String locale, int count,
InvocationService.ResultListener listener)
throws InvocationException
{
DictionaryManager dictionary = getDictionaryManager ();
@@ -167,9 +161,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void checkDictionaryWord (
ClientObject caller, String locale, String word,
InvocationService.ResultListener listener)
public void checkDictionaryWord (ClientObject caller, String locale, String word,
InvocationService.ResultListener listener)
throws InvocationException
{
DictionaryManager dictionary = getDictionaryManager ();
@@ -191,9 +184,9 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void addToCollection (
ClientObject caller, String collName, byte[][] data,
boolean clearExisting, InvocationService.InvocationListener listener)
public void addToCollection (ClientObject caller, String collName, byte[][] data,
boolean clearExisting,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateUser(caller);
@@ -201,8 +194,7 @@ public class EZGameManager extends GameManager
_collections = new HashMap<String, ArrayList<byte[]>>();
}
// figure out if we're adding to an existing collection
// or creating a new one
// figure out if we're adding to an existing collection or creating a new one
ArrayList<byte[]> list = null;
if (!clearExisting) {
list = _collections.get(collName);
@@ -216,10 +208,9 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void getFromCollection (
ClientObject caller, String collName, boolean consume, int count,
String msgOrPropName, int playerId,
InvocationService.ConfirmListener listener)
public void getFromCollection (ClientObject caller, String collName, boolean consume, int count,
String msgOrPropName, int playerId,
InvocationService.ConfirmListener listener)
throws InvocationException
{
validateUser(caller);
@@ -243,12 +234,10 @@ public class EZGameManager extends GameManager
if (playerId == 0) {
setProperty(msgOrPropName, result, -1);
} else {
sendPrivateMessage(playerId, msgOrPropName, result);
}
// SUCCESS!
listener.requestProcessed();
listener.requestProcessed(); // SUCCESS!
return;
}
}
@@ -258,15 +247,14 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void mergeCollection (
ClientObject caller, String srcColl, String intoColl,
InvocationService.InvocationListener listener)
public void mergeCollection (ClientObject caller, String srcColl, String intoColl,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateUser(caller);
// non-existent collections are treated as empty, so if the
// source doesn't exist, we silently accept it
// non-existent collections are treated as empty, so if the source doesn't exist, we
// silently accept it
if (_collections != null) {
ArrayList<byte[]> src = _collections.remove(srcColl);
if (src != null) {
@@ -281,9 +269,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void setTicker (
ClientObject caller, String tickerName, int msOfDelay,
InvocationService.InvocationListener listener)
public void setTicker (ClientObject caller, String tickerName, int msOfDelay,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateUser(caller);
@@ -292,11 +279,11 @@ public class EZGameManager extends GameManager
if (msOfDelay >= MIN_TICKER_DELAY) {
if (_tickers != null) {
t = _tickers.get(tickerName);
} else {
_tickers = new HashMap<String, Ticker>();
t = null;
}
if (t == null) {
if (_tickers.size() >= MAX_TICKERS) {
throw new InvocationException(ACCESS_DENIED);
@@ -320,9 +307,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void getCookie (
ClientObject caller, final int playerId,
InvocationService.InvocationListener listener)
public void getCookie (ClientObject caller, final int playerId,
InvocationService.InvocationListener listener)
throws InvocationException
{
GameCookieManager gcm = getCookieManager();
@@ -338,31 +324,25 @@ public class EZGameManager extends GameManager
if (!_cookieLookups.contains(playerId)) {
BodyObject body = getOccupantByOid(playerId);
if (body == null) {
log.fine("getCookie() called with invalid occupantId " +
"[occupantId=" + playerId + "].");
log.fine("getCookie() called with invalid occupant [occupantId=" + playerId + "].");
throw new InvocationException(INTERNAL_ERROR);
}
gcm.getCookie(getPersistentGameId(), body,
new ResultListener<byte[]>() {
public void requestCompleted (byte[] result) {
// Result may be null: that's ok, it means
// we've looked up the user's nonexistant cookie.
// Only set the cookie if the playerIndex is
// still in the lookup set, otherwise they left!
if (_cookieLookups.remove(playerId) &&
_gameObj.isActive()) {
_gameObj.addToUserCookies(
new UserCookie(playerId, result));
}
gcm.getCookie(getPersistentGameId(), body, new ResultListener<byte[]>() {
public void requestCompleted (byte[] result) {
// Result may be null: that's ok, it means we've looked up the user's
// nonexistant cookie. Only set the cookie if the playerIndex is still in the
// lookup set, otherwise they left!
if (_cookieLookups.remove(playerId) && _gameObj.isActive()) {
_gameObj.addToUserCookies(new UserCookie(playerId, result));
}
}
public void requestFailed (Exception cause) {
log.warning("Unable to retrieve cookie " +
"[cause=" + cause + "].");
requestCompleted(null);
}
});
public void requestFailed (Exception cause) {
log.warning("Unable to retrieve cookie [cause=" + cause + "].");
requestCompleted(null);
}
});
// indicate that we're looking up a cookie
_cookieLookups.add(playerId);
@@ -370,9 +350,8 @@ public class EZGameManager extends GameManager
}
// from EZGameProvider
public void setCookie (
ClientObject caller, byte[] value,
InvocationService.InvocationListener listener)
public void setCookie (ClientObject caller, byte[] value,
InvocationService.InvocationListener listener)
throws InvocationException
{
validateUser(caller);
@@ -408,8 +387,8 @@ public class EZGameManager extends GameManager
}
/**
* Helper method to send a private message to the specified player oid
* (must already be verified).
* Helper method to send a private message to the specified player oid (must already be
* verified).
*/
protected void sendPrivateMessage (
int playerId, String msg, Object data)
@@ -421,21 +400,19 @@ public class EZGameManager extends GameManager
throw new InvocationException("m.player_not_around");
}
target.postMessage(
EZGameObject.USER_MESSAGE + ":" + _gameObj.getOid(),
new Object[] { msg, data });
target.postMessage(EZGameObject.USER_MESSAGE + ":" + _gameObj.getOid(),
new Object[] { msg, data });
}
/**
* Helper method to post a property set event.
*/
protected void setProperty (
String propName, Object value, int index)
protected void setProperty (String propName, Object value, int index)
{
// apply the property set immediately
Object oldValue = _gameObj.applyPropertySet(propName, value, index);
_gameObj.postEvent(new PropertySetEvent(
_gameObj.getOid(), propName, value, index, oldValue));
_gameObj.postEvent(
new PropertySetEvent(_gameObj.getOid(), propName, value, index, oldValue));
}
/**
@@ -470,8 +447,7 @@ public class EZGameManager extends GameManager
}
/**
* Validate that the specified listener has access to make a
* change.
* Validate that the specified listener has access to make a change.
*/
protected void validateStateModification (ClientObject caller)
throws InvocationException
@@ -479,8 +455,7 @@ public class EZGameManager extends GameManager
validateUser(caller);
Name holder = _gameObj.turnHolder;
if (holder != null &&
!holder.equals(((BodyObject) caller).getVisibleName())) {
if (holder != null && !holder.equals(((BodyObject) caller).getVisibleName())) {
throw new InvocationException(InvocationCodes.ACCESS_DENIED);
}
}
@@ -539,6 +514,44 @@ public class EZGameManager extends GameManager
}
}
@Override // from PlaceManager
protected void bodyEntered (int bodyOid)
{
super.bodyEntered(bodyOid);
// if we have no controller, then our new friend gets control
if (_gameObj.controllerOid == 0) {
_gameObj.setControllerOid(bodyOid);
}
}
@Override // from PlaceManager
protected void bodyUpdated (OccupantInfo info)
{
super.bodyUpdated(info);
// if the controller just disconnected, reassign control
if (info.status == OccupantInfo.DISCONNECTED && info.bodyOid == _gameObj.controllerOid) {
_gameObj.setControllerOid(getControllerOid());
// if everyone in the room was disconnected and this client just reconnected, it becomes
// the new controller
} else if (_gameObj.controllerOid == 0) {
_gameObj.setControllerOid(info.bodyOid);
}
}
@Override // from PlaceManager
protected void bodyLeft (int bodyOid)
{
super.bodyLeft(bodyOid);
// if this player was the controller, reassign control
if (bodyOid == _gameObj.controllerOid) {
_gameObj.setControllerOid(getControllerOid());
}
}
@Override
protected void didShutdown ()
{
@@ -562,8 +575,7 @@ public class EZGameManager extends GameManager
super.playerGameDidEnd(pidx);
// kill any of their cookies
if (_gameObj.userCookies != null &&
_gameObj.userCookies.containsKey(pidx)) {
if (_gameObj.userCookies != null && _gameObj.userCookies.containsKey(pidx)) {
_gameObj.removeFromUserCookies(pidx);
}
// halt the loading of their cookie, if in progress
@@ -599,6 +611,20 @@ public class EZGameManager extends GameManager
}
}
/**
* Returns the oid of a player to whom to assign control of the game or zero if no players
* qualify for control.
*/
protected int getControllerOid ()
{
for (OccupantInfo info : _gameObj.occupantInfo) {
if (info.status != OccupantInfo.DISCONNECTED) {
return info.bodyOid;
}
}
return 0;
}
/**
* A timer that fires message events to a game.
*/
@@ -610,8 +636,7 @@ public class EZGameManager extends GameManager
public Ticker (String name, EZGameObject gameObj)
{
_name = name;
// once we are constructed, we want to avoid calling
// methods on dobjs.
// once we are constructed, we want to avoid calling methods on dobjs.
_oid = gameObj.getOid();
_omgr = gameObj.getManager();
}
@@ -627,20 +652,17 @@ public class EZGameManager extends GameManager
_interval.cancel();
}
/** The interval that does our work. Note well that this is
* not a 'safe' interval that operates using a RunQueue.
* This interval instead does something that we happen to know
* is safe for any thread: posting an event to the dobj manager.
* If we were using a RunQueue it would be the same event queue
* and we would be posted there, wait our turn, and then do the same
* thing: post this event. We just expedite the process.
/**
* The interval that does our work. Note well that this is not a 'safe' interval that
* operates using a RunQueue. This interval instead does something that we happen to know
* is safe for any thread: posting an event to the dobj manager. If we were using a
* RunQueue it would be the same event queue and we would be posted there, wait our turn,
* and then do the same thing: post this event. We just expedite the process.
*/
protected Interval _interval = new Interval() {
public void expired ()
{
public void expired () {
_omgr.postEvent(
new MessageEvent(_oid, EZGameObject.TICKER,
new Object[] { _name, _value++ }));
new MessageEvent(_oid, EZGameObject.TICKER, new Object[] { _name, _value++ }));
}
};