782cf97f86
git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@269 c613c5cb-e716-0410-b11b-feb51c14d237
927 lines
30 KiB
ActionScript
927 lines
30 KiB
ActionScript
//
|
|
// $Id$
|
|
//
|
|
// Vilya library - tools for developing networked games
|
|
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
|
// http://www.threerings.net/code/vilya/
|
|
//
|
|
// 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.ezgame.client {
|
|
|
|
import flash.errors.IllegalOperationError;
|
|
|
|
import flash.events.Event;
|
|
import flash.events.EventDispatcher;
|
|
import flash.events.IEventDispatcher;
|
|
import flash.events.KeyboardEvent;
|
|
|
|
import flash.events.MouseEvent;
|
|
|
|
import flash.display.DisplayObject;
|
|
import flash.display.InteractiveObject;
|
|
|
|
import flash.utils.IExternalizable;
|
|
import flash.utils.ByteArray;
|
|
import flash.utils.Dictionary;
|
|
import flash.utils.getQualifiedSuperclassName;
|
|
|
|
import com.threerings.io.TypedArray;
|
|
|
|
import com.threerings.util.ArrayUtil;
|
|
import com.threerings.util.ClassUtil;
|
|
import com.threerings.util.Integer;
|
|
import com.threerings.util.Iterator;
|
|
import com.threerings.util.MessageBundle;
|
|
import com.threerings.util.Name;
|
|
import com.threerings.util.StringUtil;
|
|
|
|
import com.threerings.presents.client.ConfirmAdapter;
|
|
import com.threerings.presents.client.ResultWrapper;
|
|
import com.threerings.presents.client.InvocationService_ConfirmListener;
|
|
import com.threerings.presents.client.InvocationService_ResultListener;
|
|
|
|
import com.threerings.presents.dobj.ElementUpdateListener;
|
|
import com.threerings.presents.dobj.ElementUpdatedEvent;
|
|
import com.threerings.presents.dobj.EntryAddedEvent;
|
|
import com.threerings.presents.dobj.EntryRemovedEvent;
|
|
import com.threerings.presents.dobj.EntryUpdatedEvent;
|
|
import com.threerings.presents.dobj.MessageAdapter;
|
|
import com.threerings.presents.dobj.MessageEvent;
|
|
import com.threerings.presents.dobj.MessageListener;
|
|
import com.threerings.presents.dobj.ObjectAddedEvent;
|
|
import com.threerings.presents.dobj.ObjectRemovedEvent;
|
|
import com.threerings.presents.dobj.OidListListener;
|
|
import com.threerings.presents.dobj.SetListener;
|
|
|
|
import com.threerings.crowd.data.BodyObject;
|
|
import com.threerings.crowd.data.OccupantInfo;
|
|
import com.threerings.crowd.data.PlaceObject;
|
|
import com.threerings.crowd.util.CrowdContext;
|
|
|
|
import com.threerings.parlor.game.data.GameObject;
|
|
|
|
import com.threerings.ezgame.data.EZGameConfig;
|
|
import com.threerings.ezgame.data.EZGameObject;
|
|
import com.threerings.ezgame.data.PropertySetEvent;
|
|
import com.threerings.ezgame.data.PropertySetListener;
|
|
import com.threerings.ezgame.data.UserCookie;
|
|
import com.threerings.ezgame.util.EZObjectMarshaller;
|
|
|
|
/**
|
|
* Manages the backend of the game.
|
|
*/
|
|
public class GameControlBackend
|
|
implements MessageListener, SetListener, ElementUpdateListener,
|
|
PropertySetListener
|
|
{
|
|
public var log :Log = Log.getLog(this);
|
|
|
|
public function GameControlBackend (
|
|
ctx :CrowdContext, ezObj :EZGameObject, ctrl :EZGameController)
|
|
{
|
|
_ctx = ctx;
|
|
_ezObj = ezObj;
|
|
_ctrl = ctrl;
|
|
_gameData = _ezObj.getUserProps();
|
|
|
|
_ezObj.addListener(this);
|
|
_ctx.getClient().getClientObject().addListener(_userListener);
|
|
}
|
|
|
|
public function setSharedEvents (disp :IEventDispatcher) :void
|
|
{
|
|
disp.addEventListener("ezgameQuery", handleEZQuery);
|
|
}
|
|
|
|
/**
|
|
* Are we connected to the usercode on the front-end?
|
|
*/
|
|
public function isConnected () :Boolean
|
|
{
|
|
return (_userFuncs != null);
|
|
}
|
|
|
|
public function setContainer (container :GameContainer) :void
|
|
{
|
|
_container = container;
|
|
}
|
|
|
|
public function shutdown () :void
|
|
{
|
|
_ezObj.removeListener(this);
|
|
_ctx.getClient().getClientObject().removeListener(_userListener);
|
|
|
|
_userFuncs = null; // disconnect
|
|
}
|
|
|
|
protected function handleEZQuery (evt :Object) :void
|
|
{
|
|
setUserCodeProperties(evt.userProps);
|
|
evt.ezProps = new Object();
|
|
populateProperties(evt.ezProps);
|
|
|
|
// ok, we're now hooked-up with the game code
|
|
_ctrl.userCodeIsConnected();
|
|
}
|
|
|
|
protected function setUserCodeProperties (o :Object) :void
|
|
{
|
|
// here we would handle adapting old functions to a new version
|
|
|
|
_ezDispatcher = (o["dispatchEvent_v1"] as Function);
|
|
|
|
_userFuncs = o;
|
|
}
|
|
|
|
protected function callUserCode (name :String, ... args) :*
|
|
{
|
|
if (_userFuncs != null) {
|
|
try {
|
|
var func :Function = (_userFuncs[name] as Function);
|
|
if (func != null) {
|
|
return func.apply(null, args);
|
|
}
|
|
|
|
} catch (err :Error) {
|
|
log.warning("Error in user-code: " + err);
|
|
log.logStackTrace(err);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
protected function populateProperties (o :Object) :void
|
|
{
|
|
// add in any backwards-compatible functions
|
|
new GameControlCompatibility(_ezObj, this).populateProperties(o);
|
|
|
|
// straight data
|
|
o["gameData"] = _gameData;
|
|
o["gameConfig"] = (_ctrl.getPlaceConfig() as EZGameConfig).customConfig;
|
|
|
|
// functions
|
|
o["setProperty_v1"] = setProperty_v1;
|
|
o["testAndSetProperty_v1"] = testAndSetProperty_v1;
|
|
o["mergeCollection_v1"] = mergeCollection_v1;
|
|
o["setTicker_v1"] = setTicker_v1;
|
|
o["sendChat_v1"] = sendChat_v1;
|
|
o["localChat_v1"] = localChat_v1;
|
|
o["setUserCookie_v1"] = setUserCookie_v1;
|
|
o["isMyTurn_v1"] = isMyTurn_v1;
|
|
o["isInPlay_v1"] = isInPlay_v1;
|
|
o["getDictionaryLetterSet_v1"] = getDictionaryLetterSet_v1;
|
|
o["checkDictionaryWord_v1"] = checkDictionaryWord_v1;
|
|
o["populateCollection_v1"] = populateCollection_v1;
|
|
o["alterKeyEvents_v1"] = alterKeyEvents_v1;
|
|
o["focusContainer_v1"] = focusContainer_v1;
|
|
|
|
// newest
|
|
o["getFromCollection_v2"] = getFromCollection_v2;
|
|
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["endRound_v1"] = endRound_v1;
|
|
o["endGame_v2"] = endGame_v2;
|
|
o["getTurnHolder_v1"] = getTurnHolder_v1;
|
|
o["getRound_v1"] = getRound_v1;
|
|
o["getOccupantName_v1"] = getOccupantName_v1;
|
|
o["getPlayers_v1"] = getPlayers_v1;
|
|
o["getPlayerPosition_v1"] = getPlayerPosition_v1;
|
|
o["getMyPosition_v1"] = getMyPosition_v1;
|
|
}
|
|
|
|
/**
|
|
* Set a property.
|
|
*
|
|
* Note: immediate defaults to true, even though immediate=false is
|
|
* the general case. We are providing some backwards compatibility
|
|
* to old versions of setProperty_v1() that assumed immediate and did
|
|
* not pass a 4th value.
|
|
* All callers should now specify that value explicitely.
|
|
*/
|
|
public function setProperty_v1 (
|
|
propName :String, value :Object, index :int, immediate :Boolean = true) :void
|
|
{
|
|
validateConnected();
|
|
validatePropertyChange(propName, value, index);
|
|
|
|
var encoded :Object = EZObjectMarshaller.encode(value, (index == -1));
|
|
_ezObj.ezGameService.setProperty(
|
|
_ctx.getClient(), propName, encoded, index,
|
|
false, null, createLoggingConfirmListener("setProperty"));
|
|
if (immediate) {
|
|
_ezObj.applyPropertySet(propName, value, index);
|
|
}
|
|
}
|
|
|
|
public function testAndSetProperty_v1 (
|
|
propName :String, value :Object, testValue :Object, index :int) :void
|
|
{
|
|
validateConnected();
|
|
validatePropertyChange(propName, value, index);
|
|
|
|
var encodedValue :Object = EZObjectMarshaller.encode(value, (index == -1));
|
|
var encodedTestValue :Object = EZObjectMarshaller.encode(testValue, (index == -1));
|
|
_ezObj.ezGameService.setProperty(
|
|
_ctx.getClient(), propName, encodedValue, index,
|
|
true, encodedTestValue, createLoggingConfirmListener("setProperty"));
|
|
}
|
|
|
|
|
|
public function mergeCollection_v1 (
|
|
srcColl :String, intoColl :String) :void
|
|
{
|
|
validateConnected();
|
|
validateName(srcColl);
|
|
validateName(intoColl);
|
|
_ezObj.ezGameService.mergeCollection(_ctx.getClient(),
|
|
srcColl, intoColl, createLoggingConfirmListener("mergeCollection"));
|
|
}
|
|
|
|
public function sendMessage_v2 (
|
|
messageName :String, value :Object, playerId :int) :void
|
|
{
|
|
validateConnected();
|
|
validateName(messageName);
|
|
validateValue(value);
|
|
|
|
var encoded :Object = EZObjectMarshaller.encode(value, false);
|
|
_ezObj.ezGameService.sendMessage(_ctx.getClient(),
|
|
messageName, encoded, playerId,
|
|
createLoggingConfirmListener("sendMessage"));
|
|
}
|
|
|
|
public function setTicker_v1 (tickerName :String, msOfDelay :int) :void
|
|
{
|
|
validateConnected();
|
|
validateName(tickerName);
|
|
_ezObj.ezGameService.setTicker(_ctx.getClient(),
|
|
tickerName, msOfDelay, createLoggingConfirmListener("setTicker"));
|
|
}
|
|
|
|
public function sendChat_v1 (msg :String) :void
|
|
{
|
|
validateConnected();
|
|
validateChat(msg);
|
|
// Post a message to the game object, the controller
|
|
// will listen and call localChat().
|
|
_ezObj.postMessage(EZGameObject.GAME_CHAT, [ msg ]);
|
|
}
|
|
|
|
public function localChat_v1 (msg :String) :void
|
|
{
|
|
validateChat(msg);
|
|
// The sendChat() messages will end up being routed
|
|
// through this method on each client.
|
|
// TODO: make this look distinct from other system chat
|
|
_ctx.getChatDirector().displayInfo(null, MessageBundle.taint(msg));
|
|
}
|
|
|
|
public function getOccupants_v1 () :Array
|
|
{
|
|
validateConnected();
|
|
var occs :Array = [];
|
|
for (var ii :int = _ezObj.occupants.size() - 1; ii >= 0; ii--) {
|
|
occs.push(_ezObj.occupants.get(ii));
|
|
}
|
|
|
|
return occs;
|
|
}
|
|
|
|
public function getPlayers_v1 () :Array
|
|
{
|
|
validateConnected();
|
|
if (_ezObj.players.length == 0) {
|
|
// party game
|
|
return getOccupants_v1();
|
|
}
|
|
var playerIds :Array = [];
|
|
for (var ii :int = 0; ii < _ezObj.players.length; ii++) {
|
|
var occInfo :OccupantInfo = _ezObj.getOccupantInfo(_ezObj.players[ii] as Name);
|
|
playerIds.push((occInfo == null) ? 0 : occInfo.bodyOid);
|
|
}
|
|
return playerIds;
|
|
}
|
|
|
|
public function getOccupantName_v1 (playerId :int) :String
|
|
{
|
|
validateConnected();
|
|
var occInfo :OccupantInfo =
|
|
(_ezObj.occupantInfo.get(playerId) as OccupantInfo);
|
|
return (occInfo == null) ? null : occInfo.username.toString();
|
|
}
|
|
|
|
public function getMyId_v1 () :int
|
|
{
|
|
validateConnected();
|
|
return _ctx.getClient().getClientObject().getOid();
|
|
}
|
|
|
|
public function getControllerId_v1 () :int
|
|
{
|
|
validateConnected();
|
|
return _ezObj.controllerOid;
|
|
}
|
|
|
|
// TODO: table games only
|
|
public function getPlayerPosition_v1 (playerId :int) :int
|
|
{
|
|
validateConnected();
|
|
var occInfo :OccupantInfo =
|
|
(_ezObj.occupantInfo.get(playerId) as OccupantInfo);
|
|
if (occInfo == null) {
|
|
return -1;
|
|
}
|
|
return _ezObj.getPlayerIndex(occInfo.username);
|
|
}
|
|
|
|
// TODO: table only
|
|
public function getMyPosition_v1 () :int
|
|
{
|
|
validateConnected();
|
|
return _ezObj.getPlayerIndex(
|
|
(_ctx.getClient().getClientObject() as BodyObject).getVisibleName());
|
|
}
|
|
|
|
// TODO: table only
|
|
public function getTurnHolder_v1 () :int
|
|
{
|
|
validateConnected();
|
|
var occInfo :OccupantInfo = _ezObj.getOccupantInfo(_ezObj.turnHolder);
|
|
return (occInfo == null) ? 0 : occInfo.bodyOid;
|
|
}
|
|
|
|
public function getRound_v1 () :int
|
|
{
|
|
validateConnected();
|
|
return _ezObj.roundId;
|
|
}
|
|
|
|
public function getUserCookie_v2 (
|
|
playerId :int, callback :Function) :void
|
|
{
|
|
validateConnected();
|
|
// see if that cookie is already published
|
|
if (_ezObj.userCookies != null) {
|
|
var uc :UserCookie =
|
|
(_ezObj.userCookies.get(playerId) as UserCookie);
|
|
if (uc != null) {
|
|
callback(uc.cookie);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (_cookieCallbacks == null) {
|
|
_cookieCallbacks = new Dictionary();
|
|
}
|
|
var arr :Array = (_cookieCallbacks[playerId] as Array);
|
|
if (arr == null) {
|
|
arr = [];
|
|
_cookieCallbacks[playerId] = arr;
|
|
}
|
|
arr.push(callback);
|
|
|
|
// request it to be made so by the server
|
|
_ezObj.ezGameService.getCookie(_ctx.getClient(), playerId,
|
|
createLoggingConfirmListener("getUserCookie"));
|
|
}
|
|
|
|
public function setUserCookie_v1 (cookie :Object) :Boolean
|
|
{
|
|
validateConnected();
|
|
var ba :ByteArray =
|
|
(EZObjectMarshaller.encode(cookie, false) as ByteArray);
|
|
if (ba.length > MAX_USER_COOKIE) {
|
|
// not saved!
|
|
return false;
|
|
}
|
|
|
|
_ezObj.ezGameService.setCookie(_ctx.getClient(), ba,
|
|
createLoggingConfirmListener("setUserCookie"));
|
|
return true;
|
|
}
|
|
|
|
public function isMyTurn_v1 () :Boolean
|
|
{
|
|
validateConnected();
|
|
return getUsername().equals(_ezObj.turnHolder);
|
|
}
|
|
|
|
public function isInPlay_v1 () :Boolean
|
|
{
|
|
validateConnected();
|
|
return _ezObj.isInPlay();
|
|
}
|
|
|
|
public function endTurn_v2 (nextPlayerId :int) :void
|
|
{
|
|
validateConnected();
|
|
_ezObj.ezGameService.endTurn(
|
|
_ctx.getClient(), nextPlayerId, createLoggingConfirmListener("endTurn"));
|
|
}
|
|
|
|
public function endRound_v1 (nextRoundDelay :int) :void
|
|
{
|
|
validateConnected();
|
|
_ezObj.ezGameService.endRound(
|
|
_ctx.getClient(), nextRoundDelay, createLoggingConfirmListener("endRound"));
|
|
}
|
|
|
|
public function endGame_v2 (... winnerIds) :void
|
|
{
|
|
validateConnected();
|
|
var winners :TypedArray = TypedArray.create(int);
|
|
while (winnerIds.length > 0) {
|
|
winners.push(int(winnerIds.shift()));
|
|
}
|
|
_ezObj.ezGameService.endGame(
|
|
_ctx.getClient(), winners, createLoggingConfirmListener("endGame"));
|
|
}
|
|
|
|
public function getDictionaryLetterSet_v1 (locale :String, count :int, callback :Function) :void
|
|
{
|
|
validateConnected();
|
|
var listener :InvocationService_ResultListener;
|
|
if (callback != null) {
|
|
var failure :Function = function (cause :String = null) :void {
|
|
// ignore the cause, return an empty array
|
|
callback ([]);
|
|
}
|
|
var success :Function = function (result :String = null) :void {
|
|
// splice the resulting string, and return as array
|
|
var r : Array = result.split(",");
|
|
callback (r);
|
|
};
|
|
listener = new ResultWrapper (failure, success);
|
|
} else {
|
|
listener = createLoggingResultListener ("checkDictionaryWord");
|
|
}
|
|
|
|
// just relay the data over to the server
|
|
_ezObj.ezGameService.getDictionaryLetterSet(_ctx.getClient(), locale, count, listener);
|
|
}
|
|
|
|
public function checkDictionaryWord_v1 (
|
|
locale :String, word :String, callback :Function) :void
|
|
{
|
|
validateConnected();
|
|
var listener :InvocationService_ResultListener;
|
|
if (callback != null) {
|
|
var failure :Function = function (cause :String = null) :void {
|
|
// ignore the cause, return failure
|
|
callback (word, false);
|
|
}
|
|
var success :Function = function (result :Object = null) :void {
|
|
// server returns a boolean, so convert it and send it over
|
|
var r : Boolean = Boolean(result);
|
|
callback (word, r);
|
|
};
|
|
listener = new ResultWrapper (failure, success);
|
|
} else {
|
|
listener = createLoggingResultListener ("checkDictionaryWord");
|
|
}
|
|
|
|
// just relay the data over to the server
|
|
_ezObj.ezGameService.checkDictionaryWord(_ctx.getClient(), locale, word, listener);
|
|
|
|
}
|
|
|
|
/**
|
|
* Helper method for setCollection and addToCollection.
|
|
*/
|
|
public function populateCollection_v1 (
|
|
collName :String, values :Array, clearExisting :Boolean) :void
|
|
{
|
|
validateConnected();
|
|
validateName(collName);
|
|
if (values == null) {
|
|
throw new ArgumentError("Collection values may not be null.");
|
|
}
|
|
validateValue(values);
|
|
|
|
var encodedValues :TypedArray =
|
|
(EZObjectMarshaller.encode(values) as TypedArray);
|
|
|
|
_ezObj.ezGameService.addToCollection(
|
|
_ctx.getClient(), collName, encodedValues, clearExisting,
|
|
createLoggingConfirmListener("populateCollection"));
|
|
}
|
|
|
|
/**
|
|
* Helper method for pickFromCollection and dealFromCollection.
|
|
*/
|
|
public function getFromCollection_v2 (
|
|
collName :String, count :int, msgOrPropName :String, playerId :int,
|
|
consume :Boolean, callback :Function) :void
|
|
{
|
|
validateConnected();
|
|
validateName(collName);
|
|
validateName(msgOrPropName);
|
|
if (count < 1) {
|
|
throw new ArgumentError("Must retrieve at least one element!");
|
|
}
|
|
|
|
var listener :InvocationService_ConfirmListener;
|
|
if (callback != null) {
|
|
// TODO: Figure out the method sig of the callback, and what it
|
|
// means
|
|
var fn :Function = function (cause :String = null) :void {
|
|
if (cause == null) {
|
|
callback(count);
|
|
} else {
|
|
callback(parseInt(cause));
|
|
}
|
|
};
|
|
listener = new ConfirmAdapter(fn, fn);
|
|
|
|
} else {
|
|
listener = createLoggingConfirmListener("getFromCollection");
|
|
}
|
|
|
|
_ezObj.ezGameService.getFromCollection(
|
|
_ctx.getClient(), collName, consume, count, msgOrPropName,
|
|
playerId, listener);
|
|
}
|
|
|
|
public function alterKeyEvents_v1 (
|
|
keyEventType :String, add :Boolean) :void
|
|
{
|
|
validateConnected();
|
|
if (add) {
|
|
_container.addEventListener(keyEventType, handleKeyEvent);
|
|
} else {
|
|
_container.removeEventListener(keyEventType, handleKeyEvent);
|
|
}
|
|
}
|
|
|
|
public function focusContainer_v1 () :void
|
|
{
|
|
validateConnected();
|
|
_container.setFocus();
|
|
}
|
|
|
|
/**
|
|
* Convenience function to get our name.
|
|
*/
|
|
public function getUsername () :Name
|
|
{
|
|
var body :BodyObject =
|
|
(_ctx.getClient().getClientObject() as BodyObject);
|
|
return body.getVisibleName();
|
|
}
|
|
|
|
/**
|
|
* Handle key events on our container and pass them into the game.
|
|
*/
|
|
protected function handleKeyEvent (evt :KeyboardEvent) :void
|
|
{
|
|
// dispatch a cloned copy of the event, so that it's safe
|
|
_ezDispatcher(evt.clone());
|
|
}
|
|
|
|
/**
|
|
* Create a logging confirm listener for service requests.
|
|
*/
|
|
protected function createLoggingConfirmListener (
|
|
service :String) :InvocationService_ConfirmListener
|
|
{
|
|
return new ConfirmAdapter(function (cause :String) :void {
|
|
Log.getLog(this).warning("Service failure " +
|
|
"[service=" + service + ", cause=" + cause + "].");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a logging result listener for service requests.
|
|
*/
|
|
protected function createLoggingResultListener (
|
|
service :String) :InvocationService_ResultListener
|
|
{
|
|
return new ResultWrapper(function (cause :String) :void {
|
|
Log.getLog(this).warning("Service failure " +
|
|
"[service=" + service + ", cause=" + cause + "].");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate that we're not shutdown.
|
|
*/
|
|
public function validateConnected () :void
|
|
{
|
|
if (_userFuncs == null) {
|
|
throw new Error("Not connected.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that the property name / value are valid.
|
|
*/
|
|
protected function validatePropertyChange (
|
|
propName :String, value :Object, index :int) :void
|
|
{
|
|
validateName(propName);
|
|
|
|
// check that we're setting an array element on an array
|
|
if (index >= 0) {
|
|
if (!(_gameData[propName] is Array)) {
|
|
throw new ArgumentError("Property " + propName +
|
|
" is not an Array.");
|
|
}
|
|
|
|
} else if (index != -1) {
|
|
throw new ArgumentError("Invalid index specified: " + index);
|
|
}
|
|
|
|
// validate the value too
|
|
validateValue(value);
|
|
}
|
|
|
|
/**
|
|
* Verify that the specified name is valid.
|
|
*/
|
|
protected function validateName (name :String) :void
|
|
{
|
|
if (name == null) {
|
|
throw new ArgumentError(
|
|
"Property, message, and collection names must not be null.");
|
|
}
|
|
}
|
|
|
|
protected function validateChat (msg :String) :void
|
|
{
|
|
if (StringUtil.isBlank(msg)) {
|
|
throw new ArgumentError(
|
|
"Empty chat may not be displayed.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that the value is legal to be streamed to other clients.
|
|
*/
|
|
protected function validateValue (value :Object) :void
|
|
{
|
|
if (value == null) {
|
|
return;
|
|
|
|
} else if (value is IExternalizable) {
|
|
throw new ArgumentError(
|
|
"IExternalizable is not yet supported");
|
|
|
|
} else if (value is Array) {
|
|
if (ClassUtil.getClassName(value) != "Array") {
|
|
// We can't allow arrays to be serialized as IExternalizables
|
|
// because we need to know element values (opaquely) on the
|
|
// server. Also, we don't allow other types because we wouldn't
|
|
// create the right class on the other side.
|
|
throw new ArgumentError(
|
|
"Custom array subclasses are not supported");
|
|
}
|
|
// then, continue on with the sub-properties check (below)
|
|
|
|
} else {
|
|
var type :String = typeof(value);
|
|
if (type == "number" || type == "string" || type == "boolean" ) {
|
|
// kosher!
|
|
return;
|
|
}
|
|
if (value is ByteArray) {
|
|
return; // kosher
|
|
}
|
|
var clazz :Class = ClassUtil.getClass(value);
|
|
var clazzparentname :String = getQualifiedSuperclassName(clazz);
|
|
var rootclass :Boolean = (clazzparentname == null);
|
|
if (! rootclass) {
|
|
throw new ArgumentError(
|
|
"Non-simple properties may not be set.");
|
|
}
|
|
// fall through and verify the object's sub-properties
|
|
}
|
|
|
|
// check sub-properties (of arrays and objects)
|
|
for each (var arrValue :Object in (value as Array)) {
|
|
validateValue(arrValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the EZGameController when the controller changes.
|
|
*/
|
|
public function controlDidChange () :void
|
|
{
|
|
callUserCode("controlDidChange_v1");
|
|
}
|
|
|
|
/**
|
|
* Called by the EZGameController when the turn changes.
|
|
*/
|
|
public function turnDidChange () :void
|
|
{
|
|
callUserCode("turnDidChange_v1");
|
|
}
|
|
|
|
/**
|
|
* Called by the EZGameController when the game starts or ends.
|
|
*/
|
|
public function gameStateChanged (started :Boolean) :void
|
|
{
|
|
if (started && _userFuncs["gameDidStart_v1"] != null) {
|
|
callUserCode("gameDidStart_v1"); // backwards compatibility
|
|
} else if (!started && _userFuncs["gameDidEnd_v1"] != null) {
|
|
callUserCode("gameDidEnd_v1"); // backwards compatibility
|
|
} else {
|
|
callUserCode("gameStateChanged_v1", started); // new hotness
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the EZGameController when a round starts or ends.
|
|
*/
|
|
public function roundStateChanged (started :Boolean) :void
|
|
{
|
|
callUserCode("roundStateChanged_v1", started);
|
|
}
|
|
|
|
// from SetListener
|
|
public function entryAdded (event :EntryAddedEvent) :void
|
|
{
|
|
var name :String = event.getName();
|
|
switch (name) {
|
|
case EZGameObject.USER_COOKIES:
|
|
receivedUserCookie(event.getEntry() as UserCookie);
|
|
break;
|
|
|
|
case PlaceObject.OCCUPANT_INFO:
|
|
var occInfo :OccupantInfo = (event.getEntry() as OccupantInfo)
|
|
callUserCode("occupantChanged_v1", occInfo.bodyOid, isPlayer(occInfo.username), true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// from SetListener
|
|
public function entryUpdated (event :EntryUpdatedEvent) :void
|
|
{
|
|
var name :String = event.getName();
|
|
switch (name) {
|
|
case EZGameObject.USER_COOKIES:
|
|
receivedUserCookie(event.getEntry() as UserCookie);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// from SetListener
|
|
public function entryRemoved (event :EntryRemovedEvent) :void
|
|
{
|
|
var name :String = event.getName();
|
|
switch (name) {
|
|
case PlaceObject.OCCUPANT_INFO:
|
|
var occInfo :OccupantInfo = (event.getOldEntry() as OccupantInfo)
|
|
callUserCode("occupantChanged_v1", occInfo.bodyOid, isPlayer(occInfo.username), false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// from ElementUpdateListener
|
|
public function elementUpdated (event :ElementUpdatedEvent) :void
|
|
{
|
|
var name :String = event.getName();
|
|
if (name == GameObject.PLAYERS) {
|
|
var oldPlayer :Name = (event.getOldValue() as Name);
|
|
var newPlayer :Name = (event.getValue() as Name);
|
|
var occInfo :OccupantInfo;
|
|
if (oldPlayer != null) {
|
|
occInfo = _ezObj.getOccupantInfo(oldPlayer);
|
|
if (occInfo != null) {
|
|
// old player became a watcher
|
|
// send player-left, then occupant-added
|
|
callUserCode("occupantChanged_v1", occInfo.bodyOid, true, false);
|
|
callUserCode("occupantChanged_v1", occInfo.bodyOid, false, true);
|
|
}
|
|
}
|
|
if (newPlayer != null) {
|
|
occInfo = _ezObj.getOccupantInfo(newPlayer);
|
|
if (occInfo != null) {
|
|
// watcher became a player
|
|
// send occupant-left, then player-added
|
|
callUserCode("occupantChanged_v1", occInfo.bodyOid, false, false);
|
|
callUserCode("occupantChanged_v1", occInfo.bodyOid, true, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// from PropertySetListener
|
|
public function propertyWasSet (event :PropertySetEvent) :void
|
|
{
|
|
callUserCode("propertyWasSet_v1", event.getName(), event.getValue(),
|
|
event.getOldValue(), event.getIndex());
|
|
}
|
|
|
|
public function messageReceived (event :MessageEvent) :void
|
|
{
|
|
var name :String = event.getName();
|
|
if (EZGameObject.USER_MESSAGE == name) {
|
|
var args :Array = event.getArgs();
|
|
callUserCode("messageReceived_v1", (args[0] as String),
|
|
EZObjectMarshaller.decode(args[1]));
|
|
|
|
} else if (EZGameObject.GAME_CHAT == name) {
|
|
// this is chat send by the game, let's route it like
|
|
// localChat, which is also sent by the game
|
|
localChat_v1(String(event.getArgs()[0]));
|
|
|
|
} else if (EZGameObject.TICKER == name) {
|
|
var targs :Array = event.getArgs();
|
|
callUserCode("messageReceived_v1", (targs[0] as String), (targs[1] as int));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by our user listener when we receive a message event
|
|
* on the user object.
|
|
*/
|
|
protected function messageReceivedOnUserObject (event :MessageEvent) :void
|
|
{
|
|
// see if it's a message about user games
|
|
var msgName :String =
|
|
EZGameObject.USER_MESSAGE + ":" + _ezObj.getOid();
|
|
if (msgName == event.getName()) {
|
|
var args :Array = event.getArgs();
|
|
callUserCode("messageReceived_v1", (args[0] as String),
|
|
EZObjectMarshaller.decode(args[1]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the arrival of a new UserCookie.
|
|
*/
|
|
protected function receivedUserCookie (cookie :UserCookie) :void
|
|
{
|
|
if (_cookieCallbacks != null) {
|
|
var arr :Array = (_cookieCallbacks[cookie.playerId] as Array);
|
|
if (arr != null) {
|
|
delete _cookieCallbacks[cookie.playerId];
|
|
for each (var fn :Function in arr) {
|
|
try {
|
|
fn(cookie.cookie);
|
|
} catch (err :Error) {
|
|
// cope
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given the specified occupant name, return if they are a player.
|
|
*/
|
|
protected function isPlayer (occupantName :Name) :Boolean
|
|
{
|
|
if (_ezObj.players.length == 0) {
|
|
return true; // party game: all occupants are players
|
|
}
|
|
return (-1 != _ezObj.getPlayerIndex(occupantName));
|
|
}
|
|
|
|
protected var _ctx :CrowdContext;
|
|
|
|
protected var _userListener :MessageAdapter =
|
|
new MessageAdapter(messageReceivedOnUserObject);
|
|
|
|
protected var _container :GameContainer;
|
|
|
|
protected var _ezObj :EZGameObject;
|
|
|
|
/** Handles trusted clientside control. */
|
|
protected var _ctrl :EZGameController;
|
|
|
|
protected var _userFuncs :Object;
|
|
|
|
/** The function on the EZGameControl which we can use to directly
|
|
* dispatch events to the user's game. */
|
|
protected var _ezDispatcher :Function;
|
|
|
|
protected var _gameData :Object;
|
|
|
|
/** playerIndex -> callback functions waiting for the cookie. */
|
|
protected var _cookieCallbacks :Dictionary;
|
|
|
|
protected static const MAX_USER_COOKIE :int = 4096;
|
|
}
|
|
}
|