Hold on to your seats kids, it's another installment of "A Boy and His

Blowtorch". Refactored GameConfig and the EZ game framework, cleaning up some
old cruft from GameConfig (which will break other projects and which I'll fix
ASAP) and moved the XML based configuration system from ToyBox into EZGame.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@286 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Michael Bayne
2007-05-01 22:26:25 +00:00
parent 04dd58d759
commit 7538ddcc7f
47 changed files with 2392 additions and 1114 deletions
+10 -5
View File
@@ -93,11 +93,16 @@
classname="com.threerings.presents.tools.GenActionScriptTask"/> classname="com.threerings.presents.tools.GenActionScriptTask"/>
<!-- now generate the associated files --> <!-- now generate the associated files -->
<genscript header="lib/SOURCE_HEADER" asroot="src/as"> <genscript header="lib/SOURCE_HEADER" asroot="src/as">
<!-- TODO: expand this to the whole enchilada when we're ready --> <fileset dir="src/java">
<fileset dir="src/java" includes="**/parlor/data/*.java"/> <include name="**/parlor/data/*.java"/>
<fileset dir="src/java" includes="**/parlor/**/data/*.java"/> <include name="**/parlor/**/data/*.java"/>
<fileset dir="src/java" includes="**/whirled/data/*.java"/> <exclude name="**/parlor/game/data/GameConfig.java"/> <!-- fiddly bits -->
<fileset dir="src/java" includes="**/whirled/**/data/*.java"/> <exclude name="**/parlor/tourney/**"/>
<include name="**/ezgame/data/*.java"/>
<include name="**/whirled/data/*.java"/>
<include name="**/whirled/**/data/*.java"/>
<exclude name="**/whirled/spot/data/Cluster.java"/> <!-- fiddly bits -->
</fileset>
</genscript> </genscript>
</target> </target>
@@ -27,179 +27,117 @@ import mx.controls.ComboBox;
import mx.controls.HSlider; import mx.controls.HSlider;
import mx.controls.Label; import mx.controls.Label;
import com.threerings.util.StreamableHashMap;
import com.threerings.util.StringUtil; import com.threerings.util.StringUtil;
import com.threerings.flex.LabeledSlider; import com.threerings.flex.LabeledSlider;
import com.threerings.parlor.game.client.FlexGameConfigurator; import com.threerings.parlor.game.client.FlexGameConfigurator;
import com.threerings.ezgame.data.ChoiceParameter;
import com.threerings.ezgame.data.EZGameConfig; import com.threerings.ezgame.data.EZGameConfig;
import com.threerings.ezgame.data.Parameter;
import com.threerings.ezgame.data.RangeParameter;
import com.threerings.ezgame.data.ToggleParameter;
/** /**
* Adds custom configuration of options specified in XML. * Adds custom configuration of options specified in XML.
*/ */
public class EZGameConfigurator extends FlexGameConfigurator public class EZGameConfigurator extends FlexGameConfigurator
{ {
/** // from GameConfigurator
* Set XML game configuration options. override protected function gotGameConfig () :void
*/
public function setXMLConfig (config :XML) :void
{ {
var log :Log = Log.getLog(this); super.gotGameConfig();
for each (var param :XML in config..params.children()) { var params :Array = (_config as EZGameConfig).getGameDefinition().params;
if (StringUtil.isBlank(param.@ident)) { if (params == null) {
log.warning("Bad configuration option: no 'ident' in " + return;
param + ", ignoring."); }
continue;
}
switch (param.localName().toLowerCase()) {
case "range":
addRangeOption(param, log);
break;
case "choice": for each (var param :Parameter in params) {
addChoiceOption(param, log); if (param is RangeParameter) {
break; var range :RangeParameter = (param as RangeParameter);
var slider :HSlider = new HSlider();
slider.minimum = range.minimum;
slider.maximum = range.maximum;
slider.value = range.start;
slider.liveDragging = true;
slider.snapInterval = 1;
addLabeledControl(param, new LabeledSlider(slider));
case "toggle": } else if (param is ChoiceParameter) {
addToggleOption(param, log); var choice :ChoiceParameter = (param as ChoiceParameter);
break; var startDex :int = choice.choices.indexOf(choice.start);
if (startDex == -1) {
Log.getLog(this).warning(
"Start value does not appear in list of choices [param=" + choice + "].");
} else {
var combo :ComboBox = new ComboBox();
combo.dataProvider = choice.choices;
combo.selectedIndex = startDex;
addLabeledControl(param, combo);
}
default: } else if (param is ToggleParameter) {
log.warning("Unknown XML configuration option: " + param + var check :CheckBox = new CheckBox();
", ignoring."); check.selected = (param as ToggleParameter).start;
break; addLabeledControl(param, check);
} else {
Log.getLog(this).warning("Unknown parameter in config [param=" + param + "].");
} }
} }
} }
/**
* Add a control that came from parsing our custom option XML.
*/
protected function addXMLControl (spec :XML, control :UIComponent) :void
{
var ident :String = String(spec.@ident);
var name :String = String(spec.@name);
var tip :String = String(spec.@tip);
if (StringUtil.isBlank(name)) {
name = ident;
}
var lbl :Label = new Label();
lbl.text = name + ":";
lbl.styleName = "lobbyLabel";
lbl.toolTip = tip == "" ? name : tip;
control.toolTip = tip == "" ? name : tip;
addControl(lbl, control);
_customConfigs.push(ident, control);
}
/**
* Adds a 'range' option specified in XML
*/
protected function addRangeOption (spec :XML, log :Log) :void
{
var min :Number = Number(spec.@minimum);
var max :Number = Number(spec.@maximum);
var start :Number = Number(spec.@start);
if (isNaN(min) || isNaN(max) || isNaN(start)) {
log.warning("Unable to parse range specification. " +
"Required numeric values: minimum, maximum, start " +
"[xml=" + spec + "].");
return;
}
var slider :HSlider = new HSlider();
slider.minimum = min;
slider.maximum = max;
slider.value = start;
slider.liveDragging = true;
slider.snapInterval = 1;
addXMLControl(spec, new LabeledSlider(slider));
}
/**
* Adds a 'choice' option specified in XML
*/
protected function addChoiceOption (spec :XML, log :Log) :void
{
if (spec.@choices.length == 0 || spec.@start.length == 0) {
log.warning("Unable to parse choice specification. " +
"Required 'choices' or 'start' is missing. " +
"[xml=" + spec + "].");
return;
}
var choiceString :String = String(spec.@choices);
var start :String = String(spec.@start);
var choices :Array = choiceString.split(",");
var startDex :int = choices.indexOf(start);
if (startDex == -1) {
log.warning("Choice start value does not appear in list of choices "+
"[xml=" + spec + "].");
return;
}
var box :ComboBox = new ComboBox();
box.dataProvider = choices;
box.selectedIndex = startDex;
addXMLControl(spec, box);
}
/**
* Adds a 'toggle' option specified in XML
*/
protected function addToggleOption (spec :XML, log :Log) :void
{
if (spec.@start.length == 0) {
log.warning("Unable to parse toggle specification. " +
"Required 'start' is missing. " +
"[xml=" + spec + "].");
return;
}
var startStr :String = String(spec.@start).toLowerCase();
var box :CheckBox = new CheckBox();
box.selected = ("true" == startStr);
addXMLControl(spec, box);
}
override protected function flushGameConfig () :void override protected function flushGameConfig () :void
{ {
super.flushGameConfig(); super.flushGameConfig();
// if there were any custom XML configs, flush those as well. // if there were any custom XML configs, flush those as well.
if (_customConfigs.length > 0) { if (_customConfigs.length > 0) {
var options :Object = {}; var params :StreamableHashMap = new StreamableHashMap();
for (var ii :int = 0; ii < _customConfigs.length; ii += 2) { for (var ii :int = 0; ii < _customConfigs.length; ii += 2) {
var ident :String = String(_customConfigs[ii]); var ident :String = String(_customConfigs[ii]);
var control :UIComponent = (_customConfigs[ii + 1] as UIComponent); var control :UIComponent = (_customConfigs[ii + 1] as UIComponent);
if (control is LabeledSlider) { if (control is LabeledSlider) {
options[ident] = (control as LabeledSlider).slider.value; params.put(ident, (control as LabeledSlider).slider.value);
} else if (control is CheckBox) { } else if (control is CheckBox) {
options[ident] = (control as CheckBox).selected; params.put(ident, (control as CheckBox).selected);
} else if (control is ComboBox) { } else if (control is ComboBox) {
options[ident] = (control as ComboBox).value; params.put(ident, (control as ComboBox).value);
} else { } else {
Log.getLog(this).warning("Unknow custom config type " + control); Log.getLog(this).warning("Unknow custom config type " + control);
} }
} }
(_config as EZGameConfig).customConfig = options; (_config as EZGameConfig).params = params;
} }
} }
/**
* Add a control that came from parsing our custom option XML.
*/
protected function addLabeledControl (param :Parameter, control :UIComponent) :void
{
if (StringUtil.isBlank(param.name)) {
param.name = param.ident;
}
var lbl :Label = new Label();
lbl.text = param.name + ":";
lbl.styleName = "lobbyLabel";
lbl.toolTip = param.tip;
control.toolTip = param.tip;
addControl(lbl, control);
_customConfigs.push(param.ident, control);
}
/** Contains pairs of identString, control, identString, control.. */ /** Contains pairs of identString, control, identString, control.. */
protected var _customConfigs :Array = []; protected var _customConfigs :Array = [];
} }
@@ -63,12 +63,11 @@ public class EZGamePanel extends Canvas
_ezObj = (plobj as EZGameObject); _ezObj = (plobj as EZGameObject);
backend = createBackend(); backend = createBackend();
_gameView = new GameContainer(cfg.gameMedia); _gameView = new GameContainer(cfg.getGameDefinition().getMediaPath(cfg.getGameId()));
_gameView.percentWidth = 100; _gameView.percentWidth = 100;
_gameView.percentHeight = 100; _gameView.percentHeight = 100;
backend.setSharedEvents( backend.setSharedEvents(
Loader(_gameView.getMediaContainer().getMedia()). Loader(_gameView.getMediaContainer().getMedia()).contentLoaderInfo.sharedEvents);
contentLoaderInfo.sharedEvents);
backend.setContainer(_gameView); backend.setContainer(_gameView);
addChild(_gameView); addChild(_gameView);
} }
@@ -47,6 +47,7 @@ import com.threerings.util.Iterator;
import com.threerings.util.MessageBundle; import com.threerings.util.MessageBundle;
import com.threerings.util.Name; import com.threerings.util.Name;
import com.threerings.util.StringUtil; import com.threerings.util.StringUtil;
import com.threerings.util.Wrapped;
import com.threerings.presents.client.ConfirmAdapter; import com.threerings.presents.client.ConfirmAdapter;
import com.threerings.presents.client.ResultWrapper; import com.threerings.presents.client.ResultWrapper;
@@ -176,7 +177,14 @@ public class GameControlBackend
// straight data // straight data
o["gameData"] = _gameData; o["gameData"] = _gameData;
o["gameConfig"] = (_ctrl.getPlaceConfig() as EZGameConfig).customConfig;
// convert our game config from a HashMap to a Dictionary
var gameConfig :Object = {};
var cfg :EZGameConfig = (_ctrl.getPlaceConfig() as EZGameConfig);
cfg.params.forEach(function (key :Object, value :Object) :void {
gameConfig[key] = (value is Wrapped) ? Wrapped(value).unwrap() : value;
});
o["gameConfig"] = gameConfig;
// functions // functions
o["setProperty_v1"] = setProperty_v1; o["setProperty_v1"] = setProperty_v1;
@@ -388,7 +396,7 @@ public class GameControlBackend
var uc :UserCookie = var uc :UserCookie =
(_ezObj.userCookies.get(playerId) as UserCookie); (_ezObj.userCookies.get(playerId) as UserCookie);
if (uc != null) { if (uc != null) {
callback(uc.cookie); callback(EZObjectMarshaller.decode(uc.cookie));
return; return;
} }
} }
@@ -901,7 +909,7 @@ public class GameControlBackend
delete _cookieCallbacks[cookie.playerId]; delete _cookieCallbacks[cookie.playerId];
for each (var fn :Function in arr) { for each (var fn :Function in arr) {
try { try {
fn(cookie.cookie); fn(EZObjectMarshaller.decode(cookie.cookie));
} catch (err :Error) { } catch (err :Error) {
// cope // cope
} }
@@ -0,0 +1,65 @@
//
// $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.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.TypedArray;
/**
* Models a parameter that allows the selection of one of a list of choices (specified as strings).
*/
public class ChoiceParameter extends Parameter
{
/** The set of choices available for this parameter. */
public var choices :TypedArray;
/** The starting selection. */
public var start :String;
public function ChoiceParameter ()
{
}
// documentation inherited
override public function getDefaultValue () :Object
{
return start;
}
// from interface Streamable
override public function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
choices = (ins.readObject() as TypedArray);
start = (ins.readField(String) as String);
}
// from interface Streamable
override public function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeObject(choices);
out.writeField(start);
}
}
}
@@ -23,6 +23,7 @@ package com.threerings.ezgame.data {
import com.threerings.util.Hashable; import com.threerings.util.Hashable;
import com.threerings.util.MessageBundle; import com.threerings.util.MessageBundle;
import com.threerings.util.StreamableHashMap;
import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream; import com.threerings.io.ObjectOutputStream;
@@ -39,53 +40,45 @@ import com.threerings.ezgame.util.EZObjectMarshaller;
* A game config for a simple multiplayer ez game. * A game config for a simple multiplayer ez game.
*/ */
public class EZGameConfig extends GameConfig public class EZGameConfig extends GameConfig
implements Hashable
{ {
/** The name of the game. */ /** Our configuration parameters. These will be seeded with the defaults from the game
public var name :String; * definition and then configured by the player in the lobby. */
public var params :StreamableHashMap = new StreamableHashMap();
/** If non-zero, a game id used to persistently identify the game. public function EZGameConfig (gameId :int = 0, gameDef :GameDefinition = null)
* This could be thought of as a new-style rating id. */
public var persistentGameId:int;
/** The media for the game. In flash, this is the URL to the SWF file. */
public var gameMedia :String;
/** The game type. */
public var gameType :int = SEATED_GAME;
/** Any custom configuration options. */
public var customConfig :Object;
public function EZGameConfig ()
{ {
// nothing needed _gameId = gameId;
_gameDef = gameDef;
} }
override public function getGameType () :int /** Returns the game definition associated with this config instance. */
public function getGameDefinition () :GameDefinition
{ {
return gameType; return _gameDef;
} }
// from abstract GameConfig // from GameConfig
override public function getBundleName () :String override public function getGameId () :int
{ {
return "general"; return _gameId;
} }
// TODO // from GameConfig
//override public function createConfigurator () :GameConfigurator override public function getGameIdent () :String
override public function getGameName () :String
{ {
return MessageBundle.taint(name); return _gameDef.ident;
} }
// from PlaceConfig // from GameConfig
override public function createController () :PlaceController override public function getMatchType () :int
{ {
return new EZGameController(); return _gameDef.match.getMatchType();
}
// from GameConfig
override public function createConfigurator () :GameConfigurator
{
return null;
} }
// from abstract PlaceConfig // from abstract PlaceConfig
@@ -94,46 +87,40 @@ public class EZGameConfig extends GameConfig
throw new Error("Not implemented."); throw new Error("Not implemented.");
} }
override public function hashCode () :int
{
return super.hashCode() ^ persistentGameId;
}
override public function equals (other :Object) :Boolean
{
if (!super.equals(other)) {
return false;
}
var that :EZGameConfig = (other as EZGameConfig);
return (this.persistentGameId == that.persistentGameId) &&
(this.gameMedia === that.gameMedia);
}
// from interface Streamable // from interface Streamable
override public function readObject (ins :ObjectInputStream) :void override public function readObject (ins :ObjectInputStream) :void
{ {
super.readObject(ins); super.readObject(ins);
params = (ins.readObject() as StreamableHashMap);
name = (ins.readField(String) as String) _gameId = ins.readInt();
persistentGameId = ins.readInt(); _gameDef = (ins.readObject() as GameDefinition);
gameMedia = (ins.readField(String) as String);
gameType = ins.readByte();
customConfig = EZObjectMarshaller.decode(ins.readObject());
} }
// from interface Streamable // from interface Streamable
override public function writeObject (out :ObjectOutputStream) :void override public function writeObject (out :ObjectOutputStream) :void
{ {
super.writeObject(out); super.writeObject(out);
out.writeObject(params);
out.writeField(name); out.writeInt(_gameId);
out.writeInt(persistentGameId); out.writeObject(_gameDef);
out.writeField(gameMedia);
out.writeByte(gameType);
out.writeObject(EZObjectMarshaller.encode(customConfig, false));
} }
/** Returns true if this is a party game, false otherwise. */
public function isPartyGame () :Boolean
{
return getMatchType() == PARTY;
}
// from PlaceConfig
override public function createController () :PlaceController
{
return new EZGameController();
}
/** Our game's unique id. */
protected var _gameId :int;
/** Our game definition. */
protected var _gameDef :GameDefinition;
} }
} }
@@ -0,0 +1,108 @@
//
// $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.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
import com.threerings.io.TypedArray;
/**
* Contains the information about a game as described by the game definition XML file.
*/
public /*abstract*/ class GameDefinition
implements Streamable
{
/** A string identifier for the game. */
public var ident :String;
/** The class name of the <code>GameController</code> derivation that we use to bootstrap on
* the client. */
public var controller :String;
/** The class name of the <code>GameManager</code> derivation that we use to manage the game on
* the server. */
public var manager :String;
/** The MD5 digest of the game media file. */
public var digest :String;
/** The configuration of the match-making mechanism. */
public var match :MatchConfig;
/** Parameters used to configure the game itself. */
public var params :TypedArray;
public function GameDefinition ()
{
}
/**
* Provides the path to this game's media (a jar file or an SWF).
*
* @param gameId the unique id of the game provided when this game definition was registered
* with the system, or -1 if we're running in test mode.
*/
public function getMediaPath (gameId :int) :String
{
throw new Error("abstract");
}
/**
* Returns true if a single player can play this game (possibly against AI opponents), or if
* opponents are needed.
*/
public function isSinglePlayerPlayable () :Boolean
{
throw new Error("Not implemented");
}
/** Generates a string representation of this instance. */
public function toString () :String
{
return "[ident=" + ident + ", ctrl=" + controller + ", mgr=" + manager +
", match=" + match + ", params=" + params + ", digest=" + digest + "]";
}
// from interface Streamable
public function readObject (ins :ObjectInputStream) :void
{
ident = (ins.readField(String) as String);
controller = (ins.readField(String) as String);
manager = (ins.readField(String) as String);
digest = (ins.readField(String) as String);
match = (ins.readObject() as MatchConfig);
params = (ins.readObject() as TypedArray);
}
// from interface Streamable
public function writeObject (out :ObjectOutputStream) :void
{
out.writeField(ident);
out.writeField(controller);
out.writeField(manager);
out.writeField(digest);
out.writeObject(match);
out.writeObject(params);
}
}
}
@@ -0,0 +1,50 @@
//
// $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.data {
import com.threerings.io.SimpleStreamableObject;
import com.threerings.parlor.game.data.GameConfig;
/**
* Used to configure the match-making interface for a game. Particular match-making mechanisms
* extend this class and specify their own special configuration parameters.
*/
public /*abstract*/ class MatchConfig extends SimpleStreamableObject
{
public function MatchConfig ()
{
}
/** Returns the matchmaking type to use for this game, e.g. {@link GameConfig.SEATED_GAME}. */
public function getMatchType () :int
{
throw new Error("Abstract");
}
/** Returns the minimum number of players needed to play this game. */
public function getMinimumPlayers () :int
{
throw new Error("Abstract");
}
}
}
@@ -0,0 +1,81 @@
//
// $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.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
/**
* Defines a configuration parameter for a game. Various derived classes exist that define
* particular types of configuration parameters including choices, toggles, ranges, etc.
*/
public /*abstract*/ class Parameter
implements Streamable
{
/** A string identifier that names this parameter. */
public var ident :String;
/** A human readable name for this configuration parameter. */
public var name :String;
/** A human readable tooltip to display when the mouse is hovered over this configuration
* parameter. */
public var tip :String;
public function Parameter ()
{
}
public function toString () :String
{
return ident;
}
public function getDefaultValue () :Object
{
throw new Error("Abstract");
}
/** Returns the translation key for this parameter's label. */
public function getLabel () :String
{
return "m." + ident;
}
// from interface Streamable
public function readObject (ins :ObjectInputStream) :void
{
ident = (ins.readField(String) as String);
name = (ins.readField(String) as String);
tip = (ins.readField(String) as String);
}
// from interface Streamable
public function writeObject (out :ObjectOutputStream) :void
{
out.writeField(ident);
out.writeField(name);
out.writeField(tip);
}
}
}
@@ -53,8 +53,7 @@ public class PropertySetEvent extends NamedEvent
override public function applyToObject (target :DObject) :Boolean override public function applyToObject (target :DObject) :Boolean
{ {
// since we're in actionscript, we're always on the client // since we're in actionscript, we're always on the client
_oldValue = _oldValue = EZGameObject(target).applyPropertySet(_name, _data, _index);
EZGameObject(target).applyPropertySet(_name, _data, _index);
return true; return true;
} }
@@ -63,7 +62,7 @@ public class PropertySetEvent extends NamedEvent
*/ */
public function getValue () :Object public function getValue () :Object
{ {
return _data; return EZObjectMarshaller.decode(_data);
} }
/** /**
@@ -87,7 +86,7 @@ public class PropertySetEvent extends NamedEvent
{ {
super.readObject(ins); super.readObject(ins);
_index = ins.readInt(); _index = ins.readInt();
_data = EZObjectMarshaller.decode(ins.readObject()); _data = (ins.readObject() as Object);
} }
// from interface Streamable // from interface Streamable
@@ -0,0 +1,69 @@
//
// $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.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* Models a parameter that can contain an integer value in a specified range.
*/
public class RangeParameter extends Parameter
{
/** The minimum value of this parameter. */
public var minimum :int;
/** The maximum value of this parameter. */
public var maximum :int;
/** The starting value for this parameter. */
public var start :int;
public function RangeParameter ()
{
}
// documentation inherited
override public function getDefaultValue () :Object
{
return start;
}
// from interface Streamable
override public function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
minimum = ins.readInt();
maximum = ins.readInt();
start = ins.readInt();
}
// from interface Streamable
override public function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeInt(minimum);
out.writeInt(maximum);
out.writeInt(start);
}
}
}
@@ -0,0 +1,82 @@
//
// $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.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.parlor.game.data.GameConfig;
/**
* Extends {@link MatchConfig} with information about match-making in the table style.
*/
public class TableMatchConfig extends MatchConfig
{
/** The minimum number of seats at this table. */
public var minSeats :int;
/** The starting setting for the number of seats at this table. */
public var startSeats :int;
/** The maximum number of seats at this table. */
public var maxSeats :int;
/** This is set to true if this is a party game. */
public var isPartyGame :Boolean;
public function TableMatchConfig ()
{
}
// from MatchConfig
override public function getMatchType () :int
{
return isPartyGame ? GameConfig.PARTY : GameConfig.SEATED_GAME;
}
// from MatchConfig
override public function getMinimumPlayers () :int
{
return minSeats;
}
// from interface Streamable
override public function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
minSeats = ins.readInt();
startSeats = ins.readInt();
maxSeats = ins.readInt();
isPartyGame = ins.readBoolean();
}
// from interface Streamable
override public function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeInt(minSeats);
out.writeInt(startSeats);
out.writeInt(maxSeats);
out.writeBoolean(isPartyGame);
}
}
}
@@ -0,0 +1,59 @@
//
// $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.data {
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* Models a parameter that allows the toggling of a single value.
*/
public class ToggleParameter extends Parameter
{
/** The starting state for this parameter. */
public var start :Boolean;
public function ToggleParameter ()
{
}
// documentation inherited
override public function getDefaultValue () :Object
{
return start;
}
// from interface Streamable
override public function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
start = ins.readBoolean();
}
// from interface Streamable
override public function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeBoolean(start);
}
}
}
@@ -28,16 +28,23 @@ import com.threerings.io.ObjectOutputStream;
import com.threerings.presents.dobj.DSet_Entry; import com.threerings.presents.dobj.DSet_Entry;
import com.threerings.ezgame.util.EZObjectMarshaller; /**
* Represents a user's game-specific cookie data.
*/
public class UserCookie public class UserCookie
implements DSet_Entry implements DSet_Entry
{ {
/** The id of the player that has this cookie. */ /** The id of the player that has this cookie. */
public var playerId :int; public var playerId :int;
/** The decoded cookie value. */ /** The cookie value. */
public var cookie :Object; public var cookie :ByteArray;
public function UserCookie (playerId :int = 0, cookie :ByteArray = null)
{
this.playerId = playerId;
this.cookie = cookie;
}
// from DSet_Entry // from DSet_Entry
public function getKey () :Object public function getKey () :Object
@@ -49,14 +56,14 @@ public class UserCookie
public function readObject (ins :ObjectInputStream) :void public function readObject (ins :ObjectInputStream) :void
{ {
playerId = ins.readInt(); playerId = ins.readInt();
var ba :ByteArray = (ins.readField(ByteArray) as ByteArray); cookie = (ins.readField(ByteArray) as ByteArray);
cookie = EZObjectMarshaller.decode(ba);
} }
// from superinterface Streamable // from superinterface Streamable
public function writeObject (out :ObjectOutputStream) :void public function writeObject (out :ObjectOutputStream) :void
{ {
throw new Error(); out.writeInt(playerId);
out.writeField(cookie);
} }
} }
} }
+60 -165
View File
@@ -40,8 +40,7 @@ import com.threerings.parlor.data.ParlorCodes;
import com.threerings.parlor.game.data.GameConfig; import com.threerings.parlor.game.data.GameConfig;
/** /**
* This class represents a table that is being used to matchmake a game by * This class represents a table that is being used to matchmake a game by the Parlor services.
* the Parlor services.
*/ */
public class Table public class Table
implements DSet_Entry, Hashable implements DSet_Entry, Hashable
@@ -49,28 +48,25 @@ public class Table
/** The unique identifier for this table. */ /** The unique identifier for this table. */
public var tableId :int; public var tableId :int;
/** The object id of the lobby object with which this table is /** The object id of the lobby object with which this table is associated. */
* associated. */
public var lobbyOid :int; public var lobbyOid :int;
/** The oid of the game that was created from this table or -1 if the /** The oid of the game that was created from this table or -1 if the table is still in
* table is still in matchmaking mode. */ * matchmaking mode. */
public var gameOid :int = -1; public var gameOid :int = -1;
/** An array of the usernames of the occupants of this table (some /** An array of the usernames of the occupants of this table (some slots may not be filled), or
* slots may not be filled), or null if a party game. */ * null if a party game. */
public var occupants :TypedArray; public var occupants :TypedArray;
/** The body oids of the occupants of this table, or null if a party game. /** The body oids of the occupants of this table, or null if a party game. (This is not
* (This is not propagated to remote instances.) */ * propagated to remote instances.) */
public /*transient*/ var bodyOids :TypedArray; public var bodyOids :TypedArray;
/** For a running game, the total number of players. For FFA party games, /** For a running game, the total number of players. For FFA party games, this is everyone. */
* this is everyone. */
public var playerCount :int; public var playerCount :int;
/** For a running game, the total number of watchers. For FFA party games, /** For a running game, the total number of watchers. For FFA party games, this is always 0. */
* this is always 0. */
public var watcherCount :int; public var watcherCount :int;
/** The game config for the game that is being matchmade. */ /** The game config for the game that is being matchmade. */
@@ -84,6 +80,25 @@ public class Table
{ {
} }
/**
* Once a table is ready to play (see {@link #mayBeStarted} and {@link #shouldBeStarted}), the
* players array can be fetched using this method. It will return an array containing the
* usernames of all of the players in the game, sized properly and with each player in the
* appropriate position.
*/
public function getPlayers () :Array
{
// create and populate the players array
var players :Array = new Array();
for (var ii :int = 0; ii < occupants.length; ii++) {
if (occupants[ii] != null) {
players.push(occupants[ii]);
}
}
return players;
}
/** /**
* Count the number of players currently occupying this table. * Count the number of players currently occupying this table.
*/ */
@@ -101,28 +116,8 @@ public class Table
} }
/** /**
* Once a table is ready to play (see {@link #mayBeStarted} and {@link * For a team game, get the team member indices of the compressed players array returned by
* #shouldBeStarted}), the players array can be fetched using this * getPlayers().
* method. It will return an array containing the usernames of all of
* the players in the game, sized properly and with each player in the
* appropriate position.
*/
public function getPlayers () :Array
{
// create and populate the players array
var players :Array = new Array();
for (var ii :int = 0; ii < occupants.length; ii++) {
if (occupants[ii] != null) {
players.push(occupants[ii]);
}
}
return players;
}
/**
* For a team game, get the team member indices of the compressed
* players array returned by getPlayers().
*/ */
public function getTeamMemberIndices () :TypedArray /* of Array of int */ public function getTeamMemberIndices () :TypedArray /* of Array of int */
{ {
@@ -150,98 +145,13 @@ public class Table
return newTeams; return newTeams;
} }
// /**
// * Requests to seat the specified user at the specified position in
// * this table.
// *
// * @param position the position in which to seat the user.
// * @param occupant the occupant to set.
// *
// * @return null if the user was successfully seated, a string error
// * code explaining the failure if the user was not able to be seated
// * at that position.
// */
// public function setOccupant (position :int, occupant :BodyObject) :String
// {
// // make sure the requested position is a valid one
// if (position >= tconfig.desiredPlayerCount || position < 0) {
// return ParlorCodes.INVALID_TABLE_POSITION;
// }
//
// // make sure the requested position is not already occupied
// if (occupants[position] != null) {
// return ParlorCodes.TABLE_POSITION_OCCUPIED;
// }
//
// // otherwise all is well, stick 'em in
// setOccupantPos(position, occupant);
// return null;
// }
//
// /**
// * This method is used for party games, it does no bounds checking
// * or verification of the player's ability to join, if you are unsure
// * you should call 'setOccupant'.
// */
// public function setOccupantPos (position :int, occupant :BodyObject) :void
// {
// occupants[position] = occupant.getVisibleName();
// bodyOids[position] = occupant.getOid();
// }
//
// /**
// * Requests that the specified user be removed from their seat at this
// * table.
// *
// * @return true if the user was seated at the table and has now been
// * removed, false if the user was never seated at the table in the
// * first place.
// */
// public function clearOccupant (username :Name) :Boolean
// {
// var dex :int = ArrayUtil.indexOf(occupants, username);
// if (dex != -1) {
// clearOccupantPos(dex);
// return true;
// }
// return false;
// }
//
// /**
// * Requests that the user identified by the specified body object id
// * be removed from their seat at this table.
// *
// * @return true if the user was seated at the table and has now been
// * removed, false if the user was never seated at the table in the
// * first place.
// */
// public function clearOccupantByOid (bodyOid :int) :Boolean
// {
// var dex :int = ArrayUtil.indexOf(bodyOids, bodyOid);
// if (dex != -1) {
// clearOccupantPos(dex);
// return true;
// }
// return false;
// }
//
// /**
// * Called to clear an occupant at the specified position.
// * Only call this method if you know what you're doing.
// */
// public function clearOccupantPos (position :int) :void
// {
// occupants[position] = null;
// bodyOids[position] = 0;
// }
/** /**
* Returns true if this table has a sufficient number of occupants * Returns true if this table has a sufficient number of occupants that the game can be
* that the game can be started. * started.
*/ */
public function mayBeStarted () :Boolean public function mayBeStarted () :Boolean
{ {
switch (config.getGameType()) { switch (config.getMatchType()) {
case GameConfig.SEATED_CONTINUOUS: case GameConfig.SEATED_CONTINUOUS:
case GameConfig.PARTY: case GameConfig.PARTY:
return true; return true;
@@ -270,25 +180,23 @@ public class Table
} }
} }
// /** /**
// * Returns true if sufficient seats are occupied that the game should * Returns true if sufficient seats are occupied that the game should be automatically started.
// * be automatically started. */
// */ public function shouldBeStarted () :Boolean
// public function shouldBeStarted () :Boolean {
// { switch (config.getMatchType()) {
// switch (config.getGameType()) { case GameConfig.SEATED_CONTINUOUS:
// case GameConfig.SEATED_CONTINUOUS: case GameConfig.PARTY:
// case GameConfig.PARTY: return true;
// return true;
// default:
// default: return (tconfig.desiredPlayerCount <= getOccupiedCount());
// return (tconfig.desiredPlayerCount <= getOccupiedCount()); }
// } }
// }
/** /**
* Returns true if this table is in play, false if it is still being * Returns true if this table is in play, false if it is still being matchmade.
* matchmade.
*/ */
public function inPlay () :Boolean public function inPlay () :Boolean
{ {
@@ -304,8 +212,7 @@ public class Table
// from Hashable // from Hashable
public function equals (other :Object) :Boolean public function equals (other :Object) :Boolean
{ {
return (other is Table) && return (other is Table) && (tableId == (other as Table).tableId);
(tableId == (other as Table).tableId);
} }
/** /**
@@ -327,19 +234,6 @@ public class Table
return tableId; return tableId;
} }
// /**
// * Returns true if there is no one sitting at this table.
// */
// public function isEmpty () :Boolean
// {
// for (var ii :int = 0; ii < bodyOids.length; ii++) {
// if ((bodyOids[ii] as int) !== 0) {
// return false;
// }
// }
// return true;
// }
// from Streamable // from Streamable
public function readObject (ins :ObjectInputStream) :void public function readObject (ins :ObjectInputStream) :void
{ {
@@ -356,13 +250,14 @@ public class Table
// from Streamable // from Streamable
public function writeObject (out :ObjectOutputStream) :void public function writeObject (out :ObjectOutputStream) :void
{ {
throw new Error(); out.writeInt(tableId);
// out.writeInt(tableId); out.writeInt(lobbyOid);
// out.writeInt(lobbyOid); out.writeInt(gameOid);
// out.writeInt(gameOid); out.writeObject(occupants);
// out.writeObject(occupants); out.writeShort(playerCount);
// out.writeObject(config); out.writeShort(watcherCount);
// out.writeObject(tconfig); out.writeObject(config);
out.writeObject(tconfig);
} }
/** /**
@@ -373,7 +268,7 @@ public class Table
buf.append("tableId=").append(tableId); buf.append("tableId=").append(tableId);
buf.append(", lobbyOid=").append(lobbyOid); buf.append(", lobbyOid=").append(lobbyOid);
buf.append(", gameOid=").append(gameOid); buf.append(", gameOid=").append(gameOid);
buf.append(", occupants=").append(occupants.join()); buf.append(", occupants=").append(occupants == null ? "<null?>" : occupants.join());
buf.append(", config=").append(config); buf.append(", config=").append(config);
} }
@@ -27,24 +27,21 @@ import com.threerings.io.SimpleStreamableObject;
import com.threerings.io.TypedArray; import com.threerings.io.TypedArray;
/** /**
* Table configuration parameters for a game that is to be matchmade * Table configuration parameters for a game that is to be matchmade using the table services.
* using the table services.
*/ */
public class TableConfig extends SimpleStreamableObject public class TableConfig extends SimpleStreamableObject
{ {
/** The total number of players that are desired for the table. /** The total number of players that are desired for the table. For team games, this should be
* For team games, this should be set to the total number of players * set to the total number of players overall, as teams may be unequal. */
* overall, as teams may be unequal. */
public var desiredPlayerCount :int; public var desiredPlayerCount :int;
/** The minimum number of players needed overall (or per-team if a /** The minimum number of players needed overall (or per-team if a team-based game) for the
* team-based game) for the game to start at the creator's discretion. */ * game to start at the creator's discretion. */
public var minimumPlayerCount :int; public var minimumPlayerCount :int;
/** If non-null, indicates that this is a team-based game and contains /** If non-null, indicates that this is a team-based game and contains the team assignments for
* the team assignments for each player. For example, a game with * each player. For example, a game with three players in two teams- players 0 and 2 versus
* three players in two teams- players 0 and 2 versus player 1- would * player 1- would have { {0, 2}, {1} }; */
* have { {0, 2}, {1} }; */
public var teamMemberIndices :TypedArray; public var teamMemberIndices :TypedArray;
/** Whether the table is "private". */ /** Whether the table is "private". */
@@ -31,14 +31,12 @@ import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.util.ParlorContext; import com.threerings.parlor.util.ParlorContext;
/** /**
* Provides the base from which interfaces can be built to configure games * Provides the base from which interfaces can be built to configure games prior to starting them.
* prior to starting them. Derived classes would extend the base * Derived classes would extend the base configurator adding interface elements and wiring them up
* configurator adding interface elements and wiring them up properly to * properly to allow the user to configure an instance of their game.
* allow the user to configure an instance of their game.
* *
* <p> Clients that use the game configurator will want to instantiate one * <p> Clients that use the game configurator will want to instantiate one based on the class
* based on the class returned from the {@link GameConfig} and then * returned from the {@link GameConfig} and then initialize it with a call to {@link #init}.
* initialize it with a call to {@link #init}.
*/ */
public /*abstract*/ class FlexGameConfigurator extends GameConfigurator public /*abstract*/ class FlexGameConfigurator extends GameConfigurator
{ {
@@ -54,14 +52,12 @@ public /*abstract*/ class FlexGameConfigurator extends GameConfigurator
} }
/** /**
* Add a control to the interface. This should be the standard way * Add a control to the interface. This should be the standard way that configurator controls
* that configurator controls are added, but note also that external * are added, but note also that external entities may add their own controls that are related
* entities may add their own controls that are related to the game, * to the game, but do not directly alter the game config, so that all the controls are added
* but do not directly alter the game config, so that all the controls * in a uniform manner and are well aligned.
* are added in a uniform manner and are well aligned.
*/ */
public function addControl ( public function addControl (label :UIComponent, control :UIComponent) :void
label :UIComponent, control :UIComponent) :void
{ {
var item :HBox = new HBox(); var item :HBox = new HBox();
item.width = 225; item.width = 225;
@@ -25,20 +25,18 @@ import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.util.ParlorContext; import com.threerings.parlor.util.ParlorContext;
/** /**
* Provides the base from which interfaces can be built to configure games * Provides the base from which interfaces can be built to configure games prior to starting
* prior to starting them. Derived classes would extend the base * them. Derived classes would extend the base configurator adding interface elements and wiring
* configurator adding interface elements and wiring them up properly to * them up properly to allow the user to configure an instance of their game.
* allow the user to configure an instance of their game.
* *
* <p> Clients that use the game configurator will want to instantiate one * <p> Clients that use the game configurator will want to instantiate one based on the class
* based on the class returned from the {@link GameConfig} and then * returned from the {@link GameConfig} and then initialize it with a call to {@link #init}.
* initialize it with a call to {@link #init}.
*/ */
public /*abstract*/ class GameConfigurator public /*abstract*/ class GameConfigurator
{ {
/** /**
* Initializes this game configurator, creates its user interface * Initializes this game configurator, creates its user interface elements and prepares it for
* elements and prepares it for display. * display.
*/ */
public function init (ctx :ParlorContext) :void public function init (ctx :ParlorContext) :void
{ {
@@ -57,8 +55,8 @@ public /*abstract*/ class GameConfigurator
} }
/** /**
* Provides this configurator with its configuration. It should set up * Provides this configurator with its configuration. It should set up all of its user
* all of its user interface elements to reflect the configuration. * interface elements to reflect the configuration.
*/ */
public function setGameConfig (config :GameConfig) :void public function setGameConfig (config :GameConfig) :void
{ {
@@ -68,8 +66,8 @@ public /*abstract*/ class GameConfigurator
} }
/** /**
* Derived classes will likely want to override this method and * Derived classes will likely want to override this method and configure their user interface
* configure their user interface elements accordingly. * elements accordingly.
*/ */
protected function gotGameConfig () :void protected function gotGameConfig () :void
{ {
@@ -86,10 +84,9 @@ public /*abstract*/ class GameConfigurator
} }
/** /**
* Derived classes will want to override this method, flushing values * Derived classes will want to override this method, flushing values from the user interface
* from the user interface to the game config object so that it is * to the game config object so that it is properly configured prior to being returned to the
* properly configured prior to being returned to the {@link * {@link #getGameConfig} caller.
* #getGameConfig} caller.
*/ */
protected function flushGameConfig () :void protected function flushGameConfig () :void
{ {
@@ -294,9 +294,9 @@ public /*abstract*/ class GameController extends PlaceController
/** /**
* Convenience method to determine the type of game. * Convenience method to determine the type of game.
*/ */
protected function getGameType () :int protected function getMatchType () :int
{ {
return _gconfig.getGameType(); return _gconfig.getMatchType();
} }
/** A reference to the active parlor context. */ /** A reference to the active parlor context. */
@@ -37,48 +37,43 @@ import com.threerings.parlor.client.TableConfigurator;
import com.threerings.parlor.game.client.GameConfigurator; import com.threerings.parlor.game.client.GameConfigurator;
/** /**
* The game config class encapsulates the configuration information for a * The game config class encapsulates the configuration information for a particular type of
* particular type of game. The hierarchy of game config objects mimics the * game. The hierarchy of game config objects mimics the hierarchy of game managers and
* hierarchy of game managers and controllers. Both the game manager and game * controllers. Both the game manager and game controller are provided with the game config object
* controller are provided with the game config object when the game is * when the game is created.
* created.
* *
* <p> The game config object is also the mechanism used to instantiate the * <p> The game config object is also the mechanism used to instantiate the appropriate game
* appropriate game manager and controller. Every game must have an associated * manager and controller. Every game must have an associated game config derived class that
* game config derived class that overrides {@link #createController} and * overrides {@link #createController} and {@link #getManagerClassName}, returning the appropriate
* {@link #getManagerClassName}, returning the appropriate game controller and * game controller and manager class for that game. Thus the entire chain of events that causes a
* manager class for that game. Thus the entire chain of events that causes a * particular game to be created is the construction of the appropriate game config instance which
* particular game to be created is the construction of the appropriate game * is provided to the server as part of an invitation or via some other matchmaking mechanism.
* config instance which is provided to the server as part of an invitation or
* via some other matchmaking mechanism.
*/ */
public /*abstract*/ class GameConfig extends PlaceConfig public /*abstract*/ class GameConfig extends PlaceConfig
implements Cloneable, Hashable implements Cloneable, Hashable
{ {
/** Game type constant: a game that is started with a list of players, /** Game type constant: a game that is started with a list of players, and those are the only
* and those are the only players that may play. */ * players that may play. */
public static const SEATED_GAME :int = 0; public static const SEATED_GAME :int = 0;
/** Game type constant: a game that starts immediately, but only has /** Game type constant: a game that starts immediately, but only has a certain number of player
* a certain number of player slots. Users enter the game room, and * slots. Users enter the game room, and then choose where to sit. */
* then choose where to sit. */
public static const SEATED_CONTINUOUS :int = 1; public static const SEATED_CONTINUOUS :int = 1;
/** Game type constant: a game that starts immediately, and every /** Game type constant: a game that starts immediately, and every user that enters is a
* user that enters is a player. */ * player. */
public static const PARTY :int = 2; public static const PARTY :int = 2;
/** The usernames of the players involved in this game, or an empty /** The usernames of the players involved in this game, or an empty array if such information
* array if such information is not needed by this particular game. */ * is not needed by this particular game. */
public var players :TypedArray = TypedArray.create(Name); public var players :TypedArray = TypedArray.create(Name);
/** Indicates whether or not this game is rated. */ /** Indicates whether or not this game is rated. */
public var rated :Boolean = true; public var rated :Boolean = true;
/** Configurations for AIs to be used in this game. Slots with real /** Configurations for AIs to be used in this game. Slots with real players should be null and
* players should be null and slots with AIs should contain * slots with AIs should contain configuration for those AIs. A null array indicates no use of
* configuration for those AIs. A null array indicates no use of AIs * AIs at all. */
* at all. */
public var ais :TypedArray = TypedArray.create(GameAI); public var ais :TypedArray = TypedArray.create(GameAI);
public function GameConfig () public function GameConfig ()
@@ -87,27 +82,31 @@ public /*abstract*/ class GameConfig extends PlaceConfig
} }
/** /**
* Get the type of game. * Returns a numeric identifier for this game class. This may be used to track persisent
* information on a per-game basis.
*/ */
public function getGameType () :int public function getGameId () :int
{ {
return SEATED_GAME; throw new Error("abstract");
} }
/** public function getGameIdent () :String
* Returns the message bundle identifier for the bundle that should be
* used to translate the translatable strings used to describe the
* game config parameters.
*/
public /*abstract*/ function getBundleName () :String
{ {
throw new Error("abstract"); throw new Error("abstract");
} }
/** /**
* Creates a configurator that can be used to create a user interface * Get the type of game.
* for configuring this instance prior to starting the game. If no */
* configuration is necessary, this method should return null. public function getMatchType () :int
{
return SEATED_GAME;
}
/**
* Creates a configurator that can be used to create a user interface for configuring this
* instance prior to starting the game. If no configuration is necessary, this method should
* return null.
*/ */
public /*abstract*/ function createConfigurator () :GameConfigurator public /*abstract*/ function createConfigurator () :GameConfigurator
{ {
@@ -115,8 +114,8 @@ public /*abstract*/ class GameConfig extends PlaceConfig
} }
/** /**
* Creates a table configurator for initializing 'table' properties * Creates a table configurator for initializing 'table' properties of the game. The default
* of the game. The default implementation returns null. * implementation returns null.
*/ */
public function createTableConfigurator () :TableConfigurator public function createTableConfigurator () :TableConfigurator
{ {
@@ -124,34 +123,13 @@ public /*abstract*/ class GameConfig extends PlaceConfig
} }
/** /**
* Returns a translatable label describing this game. * Computes a hashcode for this game config object that supports our {@link #equals}
*/ * implementation. Objects that are equal should have the same hashcode.
public function getGameName () :String
{
// the whole getRatingTypeId(), getGameName(), getBundleName()
// business should be cleaned up. we should have getGameIdent()
// and everything should have a default implementation using that
return "m." + getBundleName();
}
/**
* Returns the game rating type, if the system uses such things.
*/
public function getRatingTypeId () :int
{
return -1;
}
/**
* Computes a hashcode for this game config object that supports our
* {@link #equals} implementation. Objects that are equal should have
* the same hashcode.
*/ */
public function hashCode () :int public function hashCode () :int
{ {
// look ma, it's so sophisticated! // look ma, it's so sophisticated!
return StringUtil.hashCode(ClassUtil.getClassName(this)) + return StringUtil.hashCode(ClassUtil.getClassName(this)) + (rated ? 1 : 0);
(rated ? 1 : 0);
} }
// from Cloneable // from Cloneable
@@ -161,30 +139,27 @@ public /*abstract*/ class GameConfig extends PlaceConfig
copy.players = this.players; copy.players = this.players;
copy.rated = this.rated; copy.rated = this.rated;
copy.ais = this.ais; copy.ais = this.ais;
return copy; return copy;
} }
/** /**
* Returns true if this game config object is equal to the supplied * Returns true if this game config object is equal to the supplied object (meaning it is also
* object (meaning it is also a game config object and its * a game config object and its configuration settings are the same as ours).
* configuration settings are the same as ours).
*/ */
public function equals (other :Object) :Boolean public function equals (other :Object) :Boolean
{ {
// make sure they're of the same class // make sure they're of the same class
if (ClassUtil.isSameClass(other, this)) { if (ClassUtil.isSameClass(other, this)) {
var that :GameConfig = GameConfig(other); var that :GameConfig = GameConfig(other);
return this.rated == that.rated; return this.getGameId() == that.getGameId() && this.rated == that.rated;
} else { } else {
return false; return false;
} }
} }
/** /**
* Returns an Array of strings that describe the configuration of this * Returns an Array of strings that describe the configuration of this game. Default
* game. Default implementation returns an empty array. * implementation returns an empty array.
*/ */
public function getDescription () :Array public function getDescription () :Array
{ {
+58
View File
@@ -0,0 +1,58 @@
//
// $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;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Contains a reference to the log object used by this package.
*/
public class Log
{
/** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger("com.threerings.ezgame");
/** Convenience function. */
public static void debug (String message)
{
log.fine(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.log(Level.WARNING, t.getMessage(), t);
}
}
@@ -0,0 +1,309 @@
//
// $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 java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Level;
import org.apache.commons.io.IOUtils;
import com.samskivert.swing.HGroupLayout;
import com.samskivert.swing.SimpleSlider;
import com.threerings.util.MessageBundle;
import com.threerings.util.MessageManager;
import com.threerings.parlor.game.client.SwingGameConfigurator;
import com.threerings.parlor.game.data.GameAI;
import com.threerings.ezgame.data.AIParameter;
import com.threerings.ezgame.data.ChoiceParameter;
import com.threerings.ezgame.data.EZGameConfig;
import com.threerings.ezgame.data.FileParameter;
import com.threerings.ezgame.data.GameDefinition;
import com.threerings.ezgame.data.Parameter;
import com.threerings.ezgame.data.RangeParameter;
import com.threerings.ezgame.data.ToggleParameter;
import com.threerings.ezgame.util.EZGameContext;
import static com.threerings.ezgame.Log.log;
/**
* Handles the configuration of an EZ game. This works in conjunction with the {@link EZGameConfig}
* to provide a generic mechanism for defining and obtaining game configuration settings.
*/
public class EZGameConfigurator extends SwingGameConfigurator
{
// documentation inherited
protected void gotGameConfig ()
{
super.gotGameConfig();
EZGameContext ctx = (EZGameContext)_ctx;
EZGameConfig config = (EZGameConfig)_config;
GameDefinition gamedef = config.getGameDefinition();
// create our parameter editors
if (_editors == null) {
MessageBundle msgs = ctx.getMessageManager().getBundle(config.getGameIdent());
_editors = new ParamEditor[gamedef.params.length];
for (int ii = 0; ii < _editors.length; ii++) {
_editors[ii] = createEditor(ctx, msgs, gamedef.params[ii]);
addControl(new JLabel(msgs.get(gamedef.params[ii].getLabel())),
(JComponent) _editors[ii]);
}
}
// now read our parameters
for (int ii = 0; ii < gamedef.params.length; ii++) {
_editors[ii].readParameter(gamedef.params[ii], config);
}
}
// documentation inherited
protected void flushGameConfig ()
{
super.flushGameConfig();
EZGameConfig config = (EZGameConfig)_config;
GameDefinition gamedef = config.getGameDefinition();
for (int ii = 0; ii < gamedef.params.length; ii++) {
_editors[ii].writeParameter(gamedef.params[ii], config);
}
}
protected ParamEditor createEditor (EZGameContext ctx, MessageBundle msgs, Parameter param)
{
if (param instanceof AIParameter) {
return new AIEditor((AIParameter)param);
} else if (param instanceof RangeParameter) {
return new RangeEditor((RangeParameter)param);
} else if (param instanceof ToggleParameter) {
return new ToggleEditor((ToggleParameter)param);
} else if (param instanceof ChoiceParameter) {
return new ChoiceEditor(msgs, (ChoiceParameter)param);
} else if (param instanceof FileParameter) {
return new FileEditor(ctx, (FileParameter)param);
} else {
log.warning("Unknown parameter type! " + param + ".");
return null;
}
}
/** Provides a uniform interface to our UI components. */
protected static interface ParamEditor
{
public void readParameter (Parameter param, EZGameConfig config);
public void writeParameter (Parameter param, EZGameConfig config);
}
protected class RangeEditor extends SimpleSlider implements ParamEditor
{
public RangeEditor (RangeParameter param)
{
super(null, param.minimum, param.maximum, param.start);
}
public void readParameter (Parameter param, EZGameConfig config)
{
setValue((Integer)config.params.get(param.ident));
}
public void writeParameter (Parameter param, EZGameConfig config)
{
config.params.put(param.ident, getValue());
}
}
protected class ToggleEditor extends JPanel implements ParamEditor
{
public ToggleEditor (ToggleParameter param)
{
setLayout(new HGroupLayout(HGroupLayout.NONE, HGroupLayout.LEFT));
add(_box = new JCheckBox(null, null, param.start));
}
public void readParameter (Parameter param, EZGameConfig config)
{
_box.setSelected((Boolean)config.params.get(param.ident));
}
public void writeParameter (Parameter param, EZGameConfig config)
{
config.params.put(param.ident, _box.isSelected());
}
protected JCheckBox _box;
}
protected class ChoiceEditor extends JPanel implements ParamEditor
{
public ChoiceEditor (MessageBundle msgs, ChoiceParameter param)
{
setLayout(new HGroupLayout(HGroupLayout.NONE, HGroupLayout.LEFT));
Choice[] choices = new Choice[param.choices.length];
Choice selection = null;
for (int ii = 0; ii < choices.length; ii++) {
String choice = param.choices[ii];
choices[ii] = new Choice();
choices[ii].choice = choice;
choices[ii].label = msgs.get(param.getChoiceLabel(ii));
if (choice.equals(param.start)) {
selection = choices[ii];
}
}
add(_combo = new JComboBox(choices));
if (selection != null) {
_combo.setSelectedItem(selection);
}
}
public void readParameter (Parameter param, EZGameConfig config)
{
Choice selected = new Choice();
selected.choice = (String)config.params.get(param.ident);
_combo.setSelectedItem(selected);
}
public void writeParameter (Parameter param, EZGameConfig config)
{
config.params.put(param.ident, ((Choice)_combo.getSelectedItem()).choice);
}
protected JComboBox _combo;
}
protected static class Choice
{
public String choice;
public String label;
public String toString () {
return label;
}
public boolean equals (Object other) {
return (other instanceof Choice) && choice.equals(((Choice) other).choice);
}
}
protected class FileEditor extends JPanel
implements ParamEditor, ActionListener
{
public FileEditor (EZGameContext ctx, FileParameter param)
{
_ctx = ctx;
_param = param;
setLayout(new HGroupLayout(HGroupLayout.NONE, HGroupLayout.LEFT));
String label = ctx.getMessageManager().getBundle(
MessageManager.GLOBAL_BUNDLE).get("m.file_unset");
add(_show = new JButton(label));
_show.addActionListener(this);
}
public void readParameter (Parameter param, EZGameConfig config)
{
// nothing doing
}
public void writeParameter (Parameter param, EZGameConfig config)
{
if (_data != null) {
config.params.put(param.ident, _data);
}
}
public void actionPerformed (ActionEvent event)
{
if (event.getSource() == _show) {
if (_chooser == null) {
_chooser = new JFileChooser();
}
int rv = _chooser.showOpenDialog(this);
if (rv == JFileChooser.APPROVE_OPTION) {
File file = _chooser.getSelectedFile();
try {
if (_param.binary) {
_data = IOUtils.toByteArray(new FileInputStream(file));
} else {
_data = IOUtils.toString(new FileReader(file));
}
_show.setText(file.getName());
} catch (IOException ioe) {
String msg = MessageBundle.tcompose(
"m.file_read_failure", ioe.getMessage());
_ctx.getChatDirector().displayFeedback(null, msg);
log.warning("Failed to read '" + file + "': " + ioe.getMessage());
}
}
}
}
protected EZGameContext _ctx;
protected FileParameter _param;
protected JButton _show;
protected JFileChooser _chooser;
protected Object _data;
}
protected class AIEditor extends SimpleSlider implements ParamEditor
{
public AIEditor (AIParameter param)
{
super(null, 0, param.maximum, 0);
}
public void readParameter (Parameter param, EZGameConfig config)
{
setValue((config.ais == null) ? 0 : config.ais.length);
}
public void writeParameter (Parameter param, EZGameConfig config)
{
config.ais = new GameAI[getValue()];
for (int ii = 0; ii < config.ais.length; ii++) {
// TODO: allow specification of difficulty and personality
config.ais[ii] = new GameAI(0, 0);
}
}
}
protected ParamEditor[] _editors;
}
@@ -38,7 +38,6 @@ import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext; import com.threerings.crowd.util.CrowdContext;
import com.threerings.ezgame.data.EZGameConfig;
import com.threerings.ezgame.data.EZGameObject; import com.threerings.ezgame.data.EZGameObject;
import com.threerings.ezgame.Game; import com.threerings.ezgame.Game;
@@ -56,16 +55,17 @@ public class EZGamePanel extends JPanel
// add a listener so that we hear about all new children // add a listener so that we hear about all new children
addContainerListener(this); addContainerListener(this);
EZGameConfig cfg = (EZGameConfig) ctrl.getPlaceConfig(); // TODO: sort out if and how EZ games will create their views
try { // EZGameConfig cfg = (EZGameConfig) ctrl.getPlaceConfig();
_gameView = (Component) Class.forName(cfg.gameMedia).newInstance(); // try {
add(_gameView); // _gameView = (Component) Class.forName(cfg.gameMedia).newInstance();
} catch (RuntimeException re) { // add(_gameView);
throw re; // } catch (RuntimeException re) {
// throw re;
} catch (Exception e) { // } catch (Exception e) {
throw new RuntimeException(e); // throw new RuntimeException(e);
} // }
// TODO: Add a standard chat display? // TODO: Add a standard chat display?
//addChild(new ChatDisplayBox(ctx)); //addChild(new ChatDisplayBox(ctx));
@@ -0,0 +1,49 @@
//
// $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.data;
import com.threerings.util.ActionScript;
/**
* Models a parameter that is used to configure AIs.
*/
@ActionScript(omit=true)
public class AIParameter extends Parameter
{
/** Indicates the maximum number of AIs in the game. */
public int maximum;
// TODO: allow specification of difficulty range
// TODO: allow specification of personality types
@Override // documentation inherited
public String getLabel ()
{
return "m.ai_" + ident;
}
@Override // documentation inherited
public Object getDefaultValue ()
{
return 0;
}
}
@@ -0,0 +1,57 @@
//
// $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.data;
import com.threerings.util.ActionScript;
/**
* Models a parameter that allows the selection of one of a list of choices (specified as strings).
*/
public class ChoiceParameter extends Parameter
{
/** The set of choices available for this parameter. */
public String[] choices;
/** The starting selection. */
public String start;
/**
* Returns the translation key for the specified choice.
*/
@ActionScript(omit=true)
public String getChoiceLabel (int index)
{
return "m.choice_" + choices[index];
}
@Override @ActionScript(omit=true) // documentation inherited
public String getLabel ()
{
return "m.choice_" + ident;
}
@Override // documentation inherited
public Object getDefaultValue ()
{
return start;
}
}
@@ -21,90 +21,80 @@
package com.threerings.ezgame.data; package com.threerings.ezgame.data;
import com.threerings.util.MessageBundle; import com.threerings.util.StreamableHashMap;
import com.threerings.crowd.client.PlaceController;
import com.threerings.parlor.game.client.GameConfigurator; import com.threerings.parlor.game.client.GameConfigurator;
import com.threerings.parlor.game.data.GameConfig; import com.threerings.parlor.game.data.GameConfig;
import com.threerings.ezgame.client.EZGameController; import com.threerings.ezgame.client.EZGameConfigurator;
/** /**
* A game config for a simple multiplayer game. * A game config for a simple multiplayer game.
*/ */
public class EZGameConfig extends GameConfig public class EZGameConfig extends GameConfig
{ {
/** The name of the game. */ /** Our configuration parameters. These will be seeded with the defaults from the game
public String name; * definition and then configured by the player in the lobby. */
public StreamableHashMap<String,Object> params = new StreamableHashMap<String,Object>();
/** If non-zero, a game id used to persistently identify the game. /** A zero argument constructor used when unserializing. */
* This could be thought of as a new-style rating id. */ public EZGameConfig ()
public int persistentGameId;
/** The media for the game. In flash this is the URL to the SWF file.
* In Java, this will be a class name, or maybe a Jar. TODO? */
public String gameMedia;
/** The game type. */
public byte gameType = SEATED_GAME;
/** Custom configuration for the game. May only be interpretable on the
* client. */
public Object customConfig;
@Override
public byte getGameType ()
{ {
return gameType;
} }
// from abstract GameConfig /** Constructs a game config based on the supplied game definition. */
public String getBundleName () public EZGameConfig (int gameId, GameDefinition gameDef)
{ {
return "general"; _gameId = gameId;
_gameDef = gameDef;
// set the default values for our parameters
for (int ii = 0; ii < gameDef.params.length; ii++) {
params.put(gameDef.params[ii].ident, gameDef.params[ii].getDefaultValue());
}
} }
// from abstract GameConfig /**
* Returns the non-changing metadata that defines this game.
*/
public GameDefinition getGameDefinition ()
{
return _gameDef;
}
@Override // from GameConfig
public int getGameId ()
{
return _gameId;
}
@Override // from GameConfig
public String getGameIdent ()
{
return _gameDef.ident;
}
@Override // from GameConfig
public int getMatchType ()
{
return _gameDef.match.getMatchType();
}
@Override // from GameConfig
public GameConfigurator createConfigurator () public GameConfigurator createConfigurator ()
{ {
// TODO return new EZGameConfigurator();
return null;
} }
@Override @Override // from GameConfig
public String getGameName ()
{
return MessageBundle.taint(name);
}
@Override // from PlaceConfig
public PlaceController createController ()
{
return new EZGameController();
}
// from abstract PlaceConfig
public String getManagerClassName () public String getManagerClassName ()
{ {
return "com.threerings.ezgame.server.EZGameManager"; return _gameDef.manager;
} }
@Override /** Our game's unique id. */
public boolean equals (Object other) protected int _gameId;
{
if (!super.equals(other)) {
return false;
}
EZGameConfig that = (EZGameConfig) other; /** Our game definition. */
return this.persistentGameId == that.persistentGameId && protected GameDefinition _gameDef;
this.gameMedia.equals(that.gameMedia);
}
@Override
public int hashCode ()
{
return super.hashCode() ^ persistentGameId;
}
} }
@@ -0,0 +1,52 @@
//
// $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.data;
import com.threerings.util.ActionScript;
/**
* Models a paramter that can be used to load the contents of a file into a byte array or string
* and ship it to the server.
*/
@ActionScript(omit=true)
public class FileParameter extends Parameter
{
/** Whether or not the contents of the file should be supplied as binary data. If false, the
* file will be loaded as text in the platform default encoding . */
public boolean binary = false;
@Override // documentation inherited
public String getLabel ()
{
return "m.file_" + ident;
}
@Override // documentation inherited
public Object getDefaultValue ()
{
if (binary) {
return new byte[0];
} else {
return "";
}
}
}
@@ -0,0 +1,101 @@
//
// $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.data;
import java.util.ArrayList;
import com.samskivert.util.StringUtil;
import com.threerings.io.Streamable;
import com.threerings.util.ActionScript;
/**
* Contains the information about a game as described by the game definition XML file.
*/
public abstract class GameDefinition implements Streamable
{
/** A string identifier for the game. */
public String ident;
/** The class name of the <code>GameController</code> derivation that we use to bootstrap on
* the client. */
public String controller;
/** The class name of the <code>GameManager</code> derivation that we use to manage the game on
* the server. */
public String manager;
/** The MD5 digest of the game media file. */
public String digest;
/** The configuration of the match-making mechanism. */
public MatchConfig match;
/** Parameters used to configure the game itself. */
public Parameter[] params;
/**
* Provides the path to this game's media (a jar file or an SWF).
*
* @param gameId the unique id of the game provided when this game definition was registered
* with the system, or -1 if we're running in test mode.
*/
public abstract String getMediaPath (int gameId);
/**
* Returns true if a single player can play this game (possibly against AI opponents), or if
* opponents are needed.
*/
public boolean isSinglePlayerPlayable ()
{
// maybe it's just single player no problem
int minPlayers = 2;
if (match != null) {
minPlayers = match.getMinimumPlayers();
if (minPlayers <= 1) {
return true;
}
}
// or maybe it has AIs
int aiCount = 0;
for (Parameter param : params) {
if (param instanceof AIParameter) {
aiCount = ((AIParameter)param).maximum;
}
}
return (minPlayers - aiCount) <= 1;
}
/** Called when parsing a game definition from XML. */
@ActionScript(omit=true)
public void setParams (ArrayList<Parameter> list)
{
params = list.toArray(new Parameter[list.size()]);
}
/** Generates a string representation of this instance. */
public String toString ()
{
return StringUtil.fieldsToString(this);
}
}
@@ -0,0 +1,39 @@
//
// $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.data;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.parlor.game.data.GameConfig;
/**
* Used to configure the match-making interface for a game. Particular match-making mechanisms
* extend this class and specify their own special configuration parameters.
*/
public abstract class MatchConfig extends SimpleStreamableObject
{
/** Returns the matchmaking type to use for this game, e.g. {@link GameConfig.SEATED_GAME}. */
public abstract int getMatchType ();
/** Returns the minimum number of players needed to play this game. */
public abstract int getMinimumPlayers ();
}
@@ -0,0 +1,55 @@
//
// $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.data;
import com.samskivert.util.StringUtil;
import com.threerings.io.Streamable;
/**
* Defines a configuration parameter for a game. Various derived classes exist that define
* particular types of configuration parameters including choices, toggles, ranges, etc.
*/
public abstract class Parameter implements Streamable
{
/** A string identifier that names this parameter. */
public String ident;
/** A human readable name for this configuration parameter. */
public String name;
/** A human readable tooltip to display when the mouse is hovered over this configuration
* parameter. */
public String tip;
/** Returns the translation key for this parameter's label. */
public abstract String getLabel ();
/** Returns the default value of this parameter. */
public abstract Object getDefaultValue ();
/** Generates a string representation of this instance. */
public String toString ()
{
return StringUtil.fieldsToString(this);
}
}
@@ -27,6 +27,7 @@ import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream; import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable; import com.threerings.io.Streamable;
import com.threerings.io.Streamer; import com.threerings.io.Streamer;
import com.threerings.util.ActionScript;
import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.NamedEvent; import com.threerings.presents.dobj.NamedEvent;
@@ -34,8 +35,7 @@ import com.threerings.presents.dobj.NamedEvent;
import com.threerings.ezgame.util.EZObjectMarshaller; import com.threerings.ezgame.util.EZObjectMarshaller;
/** /**
* Represents a property change on the actionscript object we * Represents a property change on the actionscript object we use in EZGameObject.
* use in EZGameObject.
*/ */
public class PropertySetEvent extends NamedEvent public class PropertySetEvent extends NamedEvent
{ {
@@ -47,17 +47,16 @@ public class PropertySetEvent extends NamedEvent
/** /**
* Create a PropertySetEvent. * Create a PropertySetEvent.
*/ */
public PropertySetEvent ( public PropertySetEvent (int targetOid, String propName, Object value, int index, Object ovalue)
int targetOid, String propName, Object value, int index, Object oldValue)
{ {
super(targetOid, propName); super(targetOid, propName);
_data = value; _data = value;
_index = index; _index = index;
_oldValue = oldValue; _oldValue = ovalue;
} }
/** /**
* Get the value that was set for the property. * Returns the value that was set for the property.
*/ */
public Object getValue () public Object getValue ()
{ {
@@ -65,7 +64,7 @@ public class PropertySetEvent extends NamedEvent
} }
/** /**
* Get the old value. * Returns the old value.
*/ */
public Object getOldValue () public Object getOldValue ()
{ {
@@ -73,7 +72,7 @@ public class PropertySetEvent extends NamedEvent
} }
/** /**
* Get the index, or -1 if not applicable. * Returns the index, or -1 if not applicable.
*/ */
public int getIndex () public int getIndex ()
{ {
@@ -102,7 +101,7 @@ public class PropertySetEvent extends NamedEvent
} }
} }
@Override @Override @ActionScript(name="toStringBuf")
protected void toString (StringBuilder buf) protected void toString (StringBuilder buf)
{ {
buf.append("PropertySetEvent "); buf.append("PropertySetEvent ");
@@ -0,0 +1,51 @@
//
// $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.data;
import com.threerings.util.ActionScript;
/**
* Models a parameter that can contain an integer value in a specified range.
*/
public class RangeParameter extends Parameter
{
/** The minimum value of this parameter. */
public int minimum;
/** The maximum value of this parameter. */
public int maximum;
/** The starting value for this parameter. */
public int start;
@Override @ActionScript(omit=true) // documentation inherited
public String getLabel ()
{
return "m.range_" + ident;
}
@Override // documentation inherited
public Object getDefaultValue ()
{
return start;
}
}
@@ -0,0 +1,54 @@
//
// $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.data;
import com.threerings.parlor.game.data.GameConfig;
/**
* Extends {@link MatchConfig} with information about match-making in the table style.
*/
public class TableMatchConfig extends MatchConfig
{
/** The minimum number of seats at this table. */
public int minSeats;
/** The starting setting for the number of seats at this table. */
public int startSeats;
/** The maximum number of seats at this table. */
public int maxSeats;
/** This is set to true if this is a party game. */
public boolean isPartyGame;
@Override // from MatchConfig
public int getMatchType ()
{
return isPartyGame ? GameConfig.PARTY : GameConfig.SEATED_GAME;
}
@Override // from MatchConfig
public int getMinimumPlayers ()
{
return minSeats;
}
}
@@ -0,0 +1,45 @@
//
// $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.data;
import com.threerings.util.ActionScript;
/**
* Models a parameter that allows the toggling of a single value.
*/
public class ToggleParameter extends Parameter
{
/** The starting state for this parameter. */
public boolean start;
@Override @ActionScript(omit=true) // documentation inherited
public String getLabel ()
{
return "m.toggle_" + ident;
}
@Override // documentation inherited
public Object getDefaultValue ()
{
return start;
}
}
@@ -58,9 +58,8 @@ import com.threerings.parlor.game.server.GameManager;
import com.threerings.parlor.turn.server.TurnGameManager; import com.threerings.parlor.turn.server.TurnGameManager;
import com.threerings.ezgame.data.EZGameConfig;
import com.threerings.ezgame.data.EZGameObject;
import com.threerings.ezgame.data.EZGameMarshaller; import com.threerings.ezgame.data.EZGameMarshaller;
import com.threerings.ezgame.data.EZGameObject;
import com.threerings.ezgame.data.PropertySetEvent; import com.threerings.ezgame.data.PropertySetEvent;
import com.threerings.ezgame.data.UserCookie; import com.threerings.ezgame.data.UserCookie;
@@ -355,7 +354,7 @@ public class EZGameManager extends GameManager
throw new InvocationException(INTERNAL_ERROR); throw new InvocationException(INTERNAL_ERROR);
} }
gcm.getCookie(getPersistentGameId(), body, new ResultListener<byte[]>() { gcm.getCookie(_gameconfig.getGameId(), body, new ResultListener<byte[]>() {
public void requestCompleted (byte[] result) { public void requestCompleted (byte[] result) {
// Result may be null: that's ok, it means we've looked up the user's // 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 // nonexistant cookie. Only set the cookie if the playerIndex is still in the
@@ -391,7 +390,7 @@ public class EZGameManager extends GameManager
_gameObj.addToUserCookies(cookie); _gameObj.addToUserCookies(cookie);
} }
gcm.setCookie(getPersistentGameId(), caller, value); gcm.setCookie(_gameconfig.getGameId(), caller, value);
} }
/** /**
@@ -442,26 +441,13 @@ public class EZGameManager extends GameManager
new PropertySetEvent(_gameObj.getOid(), propName, value, index, oldValue)); new PropertySetEvent(_gameObj.getOid(), propName, value, index, oldValue));
} }
/**
* Get the game id of this ezgame, as set in the config.
*/
protected int getPersistentGameId ()
throws InvocationException
{
int id = ((EZGameConfig) _config).persistentGameId;
if (id == 0) {
throw new InvocationException("Persistent game id not set.");
}
return id;
}
/** /**
* Validate that the specified user has access to do things in the game. * Validate that the specified user has access to do things in the game.
*/ */
protected void validateUser (ClientObject caller) protected void validateUser (ClientObject caller)
throws InvocationException throws InvocationException
{ {
switch (getGameType()) { switch (getMatchType()) {
case GameConfig.PARTY: case GameConfig.PARTY:
return; // always validate. return; // always validate.
@@ -495,7 +481,7 @@ public class EZGameManager extends GameManager
protected BodyObject getPlayerByOid (int oid) protected BodyObject getPlayerByOid (int oid)
{ {
// verify that they're a player // verify that they're a player
switch (getGameType()) { switch (getMatchType()) {
case GameConfig.PARTY: case GameConfig.PARTY:
// all occupants are players in a party game // all occupants are players in a party game
break; break;
@@ -0,0 +1,35 @@
//
// $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.util;
import com.threerings.util.MessageManager;
import com.threerings.parlor.util.ParlorContext;
/**
* Extends the Parlor context with bits needed by the EZ game framework.
*/
public interface EZGameContext extends ParlorContext
{
/** Returns a message manager that can be used to translate strings. */
public MessageManager getMessageManager ();
}
@@ -0,0 +1,203 @@
//
// $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.xml;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.ObjectCreateRule;
import org.apache.commons.digester.Rule;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.SetFieldRule;
import com.samskivert.xml.SetNextFieldRule;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.ezgame.data.AIParameter;
import com.threerings.ezgame.data.ChoiceParameter;
import com.threerings.ezgame.data.FileParameter;
import com.threerings.ezgame.data.GameDefinition;
import com.threerings.ezgame.data.MatchConfig;
import com.threerings.ezgame.data.RangeParameter;
import com.threerings.ezgame.data.TableMatchConfig;
import com.threerings.ezgame.data.ToggleParameter;
import com.threerings.parlor.game.data.GameConfig;
/**
* Parses the XML definition of a game.
*/
public class GameParser
{
public GameParser ()
{
// create and configure our digester
_digester = new Digester() {
public void fatalError (SAXParseException exception)
throws SAXException {
// the standard digester needlessly logs a fatal warning here
if (errorHandler != null) {
errorHandler.fatalError(exception);
}
}
};
// add the rules to parse the GameDefinition and its fields
_digester.addObjectCreate("game", getGameDefinitionClass());
_digester.addRule("game/ident", new SetFieldRule("ident"));
_digester.addRule("game/controller", new SetFieldRule("controller"));
_digester.addRule("game/manager", new SetFieldRule("manager"));
_digester.addRule("game/match", new Rule() {
public void begin (String namespace, String name, Attributes attrs) throws Exception {
String type = attrs.getValue("type");
if (StringUtil.isBlank(type)) {
String errmsg = "<match> block missing type attribute.";
throw new Exception(errmsg);
}
addMatchParsingRules(digester, type);
}
public void end (String namespace, String name) throws Exception {
MatchConfig match = (MatchConfig)digester.pop();
((GameDefinition)digester.peek()).match = match;
}
});
// these rules handle customization parameters
_digester.addRule("game/params", new ObjectCreateRule(ArrayList.class));
_digester.addSetNext("game/params", "setParams", ArrayList.class.getName());
addParameter("game/params/ai", AIParameter.class);
addParameter("game/params/range", RangeParameter.class);
addParameter("game/params/choice", ChoiceParameter.class);
addParameter("game/params/toggle", ToggleParameter.class);
addParameter("game/params/file", FileParameter.class);
// add a rule to put the parsed definition onto our list
_digester.addSetNext("game", "add", Object.class.getName());
}
/**
* Parses a game definition from the supplied XML file.
*
* @exception IOException thrown if an error occurs reading the file.
* @exception SAXException thrown if an error occurs parsing the XML.
*/
public GameDefinition parseGame (File source)
throws IOException, SAXException
{
return parseGame(new FileReader(source));
}
/**
* Parses a game definition from the supplied XML source.
*
* @exception IOException thrown if an error occurs reading the file.
* @exception SAXException thrown if an error occurs parsing the XML.
*/
public GameDefinition parseGame (Reader source)
throws IOException, SAXException
{
// make sure nothing is lingering on the stack from a previous failure
_digester.clear();
// push an array list on the digester which will receive the parsed game definition
ArrayList list = new ArrayList();
_digester.push(list);
_digester.parse(source);
return (list.size() > 0) ? (GameDefinition)list.get(0) : null;
}
/**
* Returns the {@link GameDefinition} class (or derived class) to use when parsing our game
* definition.
*/
protected String getGameDefinitionClass ()
{
return GameDefinition.class.getName();
}
protected void addParameter (String path, Class pclass)
{
_digester.addRule(path, new ObjectCreateRule(pclass));
_digester.addRule(path, new SetPropertyFieldsRule());
_digester.addSetNext(path, "add", Object.class.getName());
}
/**
* Adds the rules needed to parse a custom match config, as well as the {@link MatchConfig}
* derived instance itself, based on the supplied type.
*/
protected void addMatchParsingRules (Digester digester, String type)
throws Exception
{
int itype = -1;
try {
itype = Integer.valueOf(type);
} catch (Exception e) {
for (int ii = 0; ii < GameConfig.TYPE_STRINGS.length; ii++) {
if (GameConfig.TYPE_STRINGS[ii].equals(type)) {
itype = ii;
break;
}
}
}
switch (itype) {
case GameConfig.SEATED_GAME:
digester.push(createMatchConfig());
digester.addRule("game/match/min_seats", new SetFieldRule("minSeats"));
digester.addRule("game/match/max_seats", new SetFieldRule("maxSeats"));
digester.addRule("game/match/start_seats", new SetFieldRule("startSeats"));
break;
case GameConfig.PARTY:
// party games are handled by a specially configured table
TableMatchConfig config = createMatchConfig();
config.minSeats = config.maxSeats = config.startSeats = 1;
config.isPartyGame = true;
digester.push(config);
break;
case GameConfig.SEATED_CONTINUOUS:
// TODO
default:
String errmsg = "Unknown match-making config type '" + type + "'.";
throw new Exception(errmsg);
}
}
protected TableMatchConfig createMatchConfig ()
{
return new TableMatchConfig();
}
/** Used to process XML descriptions. */
protected Digester _digester;
}
@@ -40,24 +40,20 @@ public interface ParlorCodes extends InvocationCodes
/** The response code for a countered invitation. */ /** The response code for a countered invitation. */
public static final int INVITATION_COUNTERED = 2; public static final int INVITATION_COUNTERED = 2;
/** An error code explaining that an invitation was rejected because /** An error code explaining that an invitation was rejected because the invited user was not
* the invited user was not online at the time the invitation was * online at the time the invitation was received. */
* received. */
public static final String INVITEE_NOT_ONLINE = "m.invitee_not_online"; public static final String INVITEE_NOT_ONLINE = "m.invitee_not_online";
/** An error code returned when a user requests to join a table that /** An error code returned when a user requests to join a table that doesn't exist. */
* doesn't exist. */
public static final String NO_SUCH_TABLE = "m.no_such_table"; public static final String NO_SUCH_TABLE = "m.no_such_table";
/** An error code returned when a user requests to join a table at a /** An error code returned when a user requests to join a table at a position that is not
* position that is not valid. */ * valid. */
public static final String INVALID_TABLE_POSITION = public static final String INVALID_TABLE_POSITION = "m.invalid_table_position";
"m.invalid_table_position";
/** An error code returned when a user requests to join a table in a /** An error code returned when a user requests to join a table in a position that is already
* position that is already occupied. */ * occupied. */
public static final String TABLE_POSITION_OCCUPIED = public static final String TABLE_POSITION_OCCUPIED = "m.table_position_occupied";
"m.table_position_occupied";
/** An error code returned when a user requests to leave a table that /** An error code returned when a user requests to leave a table that
* they were not sitting at in the first place. */ * they were not sitting at in the first place. */
+50 -60
View File
@@ -36,8 +36,7 @@ import com.threerings.parlor.data.ParlorCodes;
import com.threerings.parlor.game.data.GameConfig; import com.threerings.parlor.game.data.GameConfig;
/** /**
* This class represents a table that is being used to matchmake a game by * This class represents a table that is being used to matchmake a game by the Parlor services.
* the Parlor services.
*/ */
public class Table public class Table
implements DSet.Entry, ParlorCodes implements DSet.Entry, ParlorCodes
@@ -45,28 +44,25 @@ public class Table
/** The unique identifier for this table. */ /** The unique identifier for this table. */
public int tableId; public int tableId;
/** The object id of the lobby object with which this table is /** The object id of the lobby object with which this table is associated. */
* associated. */
public int lobbyOid; public int lobbyOid;
/** The oid of the game that was created from this table or -1 if the /** The oid of the game that was created from this table or -1 if the table is still in
* table is still in matchmaking mode. */ * matchmaking mode. */
public int gameOid = -1; public int gameOid = -1;
/** An array of the usernames of the occupants of this table (some /** An array of the usernames of the occupants of this table (some slots may not be filled), or
* slots may not be filled), or null if a party game. */ * null if a party game. */
public Name[] occupants; public Name[] occupants;
/** The body oids of the occupants of this table, or null if a party game. /** The body oids of the occupants of this table, or null if a party game. (This is not
* (This is not propagated to remote instances.) */ * propagated to remote instances.) */
public transient int[] bodyOids; public transient int[] bodyOids;
/** For a running game, the total number of players. For FFA party games, /** For a running game, the total number of players. For FFA party games, this is everyone. */
* this is everyone. */
public short playerCount; public short playerCount;
/** For a running game, the total number of watchers. For FFA party games, /** For a running game, the total number of watchers. For FFA party games, this is always 0. */
* this is always 0. */
public short watcherCount; public short watcherCount;
/** The game config for the game that is being matchmade. */ /** The game config for the game that is being matchmade. */
@@ -83,15 +79,13 @@ public class Table
} }
/** /**
* Initializes a new table instance, and assigns it the next monotonically * Initializes a new table instance, and assigns it the next monotonically increasing table id.
* increasing table id.
* *
* @param lobbyOid the object id of the lobby in which this table is * @param lobbyOid the object id of the lobby in which this table is to live.
* to live.
* @param tconfig the table configuration for this table. * @param tconfig the table configuration for this table.
* @param config the configuration of the game being matchmade by this * @param config the configuration of the game being matchmade by this table.
* table.
*/ */
@ActionScript(omit=true)
public void init (int lobbyOid, TableConfig tconfig, GameConfig config) public void init (int lobbyOid, TableConfig tconfig, GameConfig config)
{ {
// assign a unique table id // assign a unique table id
@@ -105,7 +99,7 @@ public class Table
this.config = config; this.config = config;
// make room for the maximum number of players // make room for the maximum number of players
if (config.getGameType() != GameConfig.PARTY) { if (config.getMatchType() != GameConfig.PARTY) {
occupants = new Name[tconfig.desiredPlayerCount]; occupants = new Name[tconfig.desiredPlayerCount];
bodyOids = new int[occupants.length]; bodyOids = new int[occupants.length];
@@ -121,6 +115,7 @@ public class Table
/** /**
* Returns true if there is no one sitting at this table. * Returns true if there is no one sitting at this table.
*/ */
@ActionScript(omit=true)
public boolean isEmpty () public boolean isEmpty ()
{ {
for (int i = 0; i < bodyOids.length; i++) { for (int i = 0; i < bodyOids.length; i++) {
@@ -148,21 +143,20 @@ public class Table
} }
/** /**
* Once a table is ready to play (see {@link #mayBeStarted} and {@link * Once a table is ready to play (see {@link #mayBeStarted} and {@link #shouldBeStarted}), the
* #shouldBeStarted}), the players array can be fetched using this * players array can be fetched using this method. It will return an array containing the
* method. It will return an array containing the usernames of all of * usernames of all of the players in the game, sized properly and with each player in the
* the players in the game, sized properly and with each player in the
* appropriate position. * appropriate position.
*/ */
public Name[] getPlayers () public Name[] getPlayers ()
{ {
// seated party games need a spot for every seat // seated party games need a spot for every seat
if (GameConfig.SEATED_CONTINUOUS == config.getGameType()) { if (GameConfig.SEATED_CONTINUOUS == config.getMatchType()) {
return new Name[tconfig.desiredPlayerCount]; return new Name[tconfig.desiredPlayerCount];
} }
// FFA party games have 0-length players array, and non-party // FFA party games have 0-length players array, and non-party games will have the players
// games will have the players who are ready-to-go for the game start. // who are ready-to-go for the game start.
Name[] players = new Name[getOccupiedCount()]; Name[] players = new Name[getOccupiedCount()];
if (occupants != null) { if (occupants != null) {
for (int ii = 0, dex = 0; ii < occupants.length; ii++) { for (int ii = 0, dex = 0; ii < occupants.length; ii++) {
@@ -176,8 +170,8 @@ public class Table
} }
/** /**
* For a team game, get the team member indices of the compressed * For a team game, get the team member indices of the compressed players array returned by
* players array returned by getPlayers(). * getPlayers().
*/ */
public int[][] getTeamMemberIndices () public int[][] getTeamMemberIndices ()
{ {
@@ -205,16 +199,15 @@ public class Table
} }
/** /**
* Requests to seat the specified user at the specified position in * Requests to seat the specified user at the specified position in this table.
* this table.
* *
* @param position the position in which to seat the user. * @param position the position in which to seat the user.
* @param occupant the occupant to set. * @param occupant the occupant to set.
* *
* @return null if the user was successfully seated, a string error * @return null if the user was successfully seated, a string error code explaining the failure
* code explaining the failure if the user was not able to be seated * if the user was not able to be seated at that position.
* at that position.
*/ */
@ActionScript(omit=true)
public String setOccupant (int position, BodyObject occupant) public String setOccupant (int position, BodyObject occupant)
{ {
// make sure the requested position is a valid one // make sure the requested position is a valid one
@@ -233,10 +226,10 @@ public class Table
} }
/** /**
* This method is used for party games, it does no bounds checking * This method is used for party games, it does no bounds checking or verification of the
* or verification of the player's ability to join, if you are unsure * player's ability to join, if you are unsure you should call 'setOccupant'.
* you should call 'setOccupant'.
*/ */
@ActionScript(omit=true)
public void setOccupantPos (int position, BodyObject occupant) public void setOccupantPos (int position, BodyObject occupant)
{ {
occupants[position] = occupant.getVisibleName(); occupants[position] = occupant.getVisibleName();
@@ -244,13 +237,12 @@ public class Table
} }
/** /**
* Requests that the specified user be removed from their seat at this * Requests that the specified user be removed from their seat at this table.
* table.
* *
* @return true if the user was seated at the table and has now been * @return true if the user was seated at the table and has now been removed, false if the user
* removed, false if the user was never seated at the table in the * was never seated at the table in the first place.
* first place.
*/ */
@ActionScript(omit=true)
public boolean clearOccupant (Name username) public boolean clearOccupant (Name username)
{ {
if (occupants != null) { if (occupants != null) {
@@ -265,13 +257,13 @@ public class Table
} }
/** /**
* Requests that the user identified by the specified body object id * Requests that the user identified by the specified body object id be removed from their seat
* be removed from their seat at this table. * at this table.
* *
* @return true if the user was seated at the table and has now been * @return true if the user was seated at the table and has now been removed, false if the user
* removed, false if the user was never seated at the table in the * was never seated at the table in the first place.
* first place.
*/ */
@ActionScript(omit=true)
public boolean clearOccupantByOid (int bodyOid) public boolean clearOccupantByOid (int bodyOid)
{ {
if (bodyOids != null) { if (bodyOids != null) {
@@ -286,9 +278,10 @@ public class Table
} }
/** /**
* Called to clear an occupant at the specified position. * Called to clear an occupant at the specified position. Only call this method if you know
* Only call this method if you know what you're doing. * what you're doing.
*/ */
@ActionScript(omit=true)
public void clearOccupantPos (int position) public void clearOccupantPos (int position)
{ {
occupants[position] = null; occupants[position] = null;
@@ -296,12 +289,12 @@ public class Table
} }
/** /**
* Returns true if this table has a sufficient number of occupants * Returns true if this table has a sufficient number of occupants that the game can be
* that the game can be started. * started.
*/ */
public boolean mayBeStarted () public boolean mayBeStarted ()
{ {
switch (config.getGameType()) { switch (config.getMatchType()) {
case GameConfig.SEATED_CONTINUOUS: case GameConfig.SEATED_CONTINUOUS:
case GameConfig.PARTY: case GameConfig.PARTY:
return true; return true;
@@ -330,12 +323,11 @@ public class Table
} }
/** /**
* Returns true if sufficient seats are occupied that the game should * Returns true if sufficient seats are occupied that the game should be automatically started.
* be automatically started.
*/ */
public boolean shouldBeStarted () public boolean shouldBeStarted ()
{ {
switch (config.getGameType()) { switch (config.getMatchType()) {
case GameConfig.SEATED_CONTINUOUS: case GameConfig.SEATED_CONTINUOUS:
case GameConfig.PARTY: case GameConfig.PARTY:
return true; return true;
@@ -346,8 +338,7 @@ public class Table
} }
/** /**
* Returns true if this table is in play, false if it is still being * Returns true if this table is in play, false if it is still being matchmade.
* matchmade.
*/ */
public boolean inPlay () public boolean inPlay ()
{ {
@@ -363,8 +354,7 @@ public class Table
// documentation inherited // documentation inherited
public boolean equals (Object other) public boolean equals (Object other)
{ {
return (other instanceof Table) && return (other instanceof Table) && (tableId == ((Table) other).tableId);
(tableId == ((Table) other).tableId);
} }
// documentation inherited // documentation inherited
@@ -24,24 +24,21 @@ package com.threerings.parlor.data;
import com.threerings.io.SimpleStreamableObject; import com.threerings.io.SimpleStreamableObject;
/** /**
* Table configuration parameters for a game that is to be matchmade * Table configuration parameters for a game that is to be matchmade using the table services.
* using the table services.
*/ */
public class TableConfig extends SimpleStreamableObject public class TableConfig extends SimpleStreamableObject
{ {
/** The total number of players that are desired for the table. /** The total number of players that are desired for the table. For team games, this should be
* For team games, this should be set to the total number of players * set to the total number of players overall, as teams may be unequal. */
* overall, as teams may be unequal. */
public int desiredPlayerCount; public int desiredPlayerCount;
/** The minimum number of players needed overall (or per-team if a /** The minimum number of players needed overall (or per-team if a team-based game) for the
* team-based game) for the game to start at the creator's discretion. */ * game to start at the creator's discretion. */
public int minimumPlayerCount; public int minimumPlayerCount;
/** If non-null, indicates that this is a team-based game and contains /** If non-null, indicates that this is a team-based game and contains the team assignments for
* the team assignments for each player. For example, a game with * each player. For example, a game with three players in two teams- players 0 and 2 versus
* three players in two teams- players 0 and 2 versus player 1- would * player 1- would have { {0, 2}, {1} }; */
* have { {0, 2}, {1} }; */
public int[][] teamMemberIndices; public int[][] teamMemberIndices;
/** Whether the table is "private". */ /** Whether the table is "private". */
@@ -25,20 +25,18 @@ import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.util.ParlorContext; import com.threerings.parlor.util.ParlorContext;
/** /**
* Provides the base from which interfaces can be built to configure games * Provides the base from which interfaces can be built to configure games prior to starting
* prior to starting them. Derived classes would extend the base * them. Derived classes would extend the base configurator adding interface elements and wiring
* configurator adding interface elements and wiring them up properly to * them up properly to allow the user to configure an instance of their game.
* allow the user to configure an instance of their game.
* *
* <p> Clients that use the game configurator will want to instantiate one * <p> Clients that use the game configurator will want to instantiate one based on the class
* based on the class returned from the {@link GameConfig} and then * returned from the {@link GameConfig} and then initialize it with a call to {@link #init}.
* initialize it with a call to {@link #init}.
*/ */
public abstract class GameConfigurator public abstract class GameConfigurator
{ {
/** /**
* Initializes this game configurator, creates its user interface * Initializes this game configurator, creates its user interface elements and prepares it for
* elements and prepares it for display. * display.
*/ */
public void init (ParlorContext ctx) public void init (ParlorContext ctx)
{ {
@@ -57,8 +55,8 @@ public abstract class GameConfigurator
} }
/** /**
* Provides this configurator with its configuration. It should set up * Provides this configurator with its configuration. It should set up all of its user
* all of its user interface elements to reflect the configuration. * interface elements to reflect the configuration.
*/ */
public void setGameConfig (GameConfig config) public void setGameConfig (GameConfig config)
{ {
@@ -68,8 +66,8 @@ public abstract class GameConfigurator
} }
/** /**
* Derived classes will likely want to override this method and * Derived classes will likely want to override this method and configure their user interface
* configure their user interface elements accordingly. * elements accordingly.
*/ */
protected void gotGameConfig () protected void gotGameConfig ()
{ {
@@ -86,10 +84,9 @@ public abstract class GameConfigurator
} }
/** /**
* Derived classes will want to override this method, flushing values * Derived classes will want to override this method, flushing values from the user interface
* from the user interface to the game config object so that it is * to the game config object so that it is properly configured prior to being returned to the
* properly configured prior to being returned to the {@link * {@link #getGameConfig} caller.
* #getGameConfig} caller.
*/ */
protected void flushGameConfig () protected void flushGameConfig ()
{ {
@@ -40,24 +40,21 @@ import com.threerings.parlor.game.data.GameObject;
import com.threerings.parlor.util.ParlorContext; import com.threerings.parlor.util.ParlorContext;
/** /**
* The game controller manages the flow and control of a game on the * The game controller manages the flow and control of a game on the client side. This class serves
* client side. This class serves as the root of a hierarchy of controller * as the root of a hierarchy of controller classes that aim to provide functionality shared
* classes that aim to provide functionality shared between various * between various similar games. The base controller provides functionality for starting and
* similar games. The base controller provides functionality for starting * ending the game and for calculating ratings adjustements when a game ends normally. It also
* and ending the game and for calculating ratings adjustements when a * handles the basic house keeping like subscription to the game object and dispatch of commands
* game ends normally. It also handles the basic house keeping like * and distributed object events.
* subscription to the game object and dispatch of commands and
* distributed object events.
*/ */
public abstract class GameController extends PlaceController public abstract class GameController extends PlaceController
implements AttributeChangeListener implements AttributeChangeListener
{ {
/** /**
* Initializes this game controller with the game configuration that * Initializes this game controller with the game configuration that was established during the
* was established during the match making process. Derived classes * match making process. Derived classes may want to override this method to initialize
* may want to override this method to initialize themselves with * themselves with game-specific configuration parameters but they should be sure to call
* game-specific configuration parameters but they should be sure to * <code>super.init</code> in such cases.
* call <code>super.init</code> in such cases.
* *
* @param ctx the client context. * @param ctx the client context.
* @param config the configuration of the game we are intended to * @param config the configuration of the game we are intended to
@@ -65,9 +62,8 @@ public abstract class GameController extends PlaceController
*/ */
public void init (CrowdContext ctx, PlaceConfig config) public void init (CrowdContext ctx, PlaceConfig config)
{ {
// cast our references before we call super.init() so that when // cast our references before we call super.init() so that when super.init() calls
// super.init() calls createPlaceView(), we have our casted // createPlaceView(), we have our casted references already in place
// references already in place
_ctx = (ParlorContext)ctx; _ctx = (ParlorContext)ctx;
_config = (GameConfig)config; _config = (GameConfig)config;
@@ -75,9 +71,8 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Adds this controller as a listener to the game object (thus derived * Adds this controller as a listener to the game object (thus derived classes need not do so)
* classes need not do so) and lets the game manager know that we are * and lets the game manager know that we are now ready to go.
* now ready to go.
*/ */
public void willEnterPlace (PlaceObject plobj) public void willEnterPlace (PlaceObject plobj)
{ {
@@ -86,27 +81,24 @@ public abstract class GameController extends PlaceController
// obtain a casted reference // obtain a casted reference
_gobj = (GameObject)plobj; _gobj = (GameObject)plobj;
// if this place object is not our current location we'll need to // if this place object is not our current location we'll need to add it as an auxiliary
// add it as an auxiliary chat source // chat source
BodyObject bobj = (BodyObject)_ctx.getClient().getClientObject(); BodyObject bobj = (BodyObject)_ctx.getClient().getClientObject();
if (bobj.location != plobj.getOid()) { if (bobj.location != plobj.getOid()) {
_ctx.getChatDirector().addAuxiliarySource( _ctx.getChatDirector().addAuxiliarySource(_gobj, GameCodes.GAME_CHAT_TYPE);
_gobj, GameCodes.GAME_CHAT_TYPE);
} }
// and add ourselves as a listener // and add ourselves as a listener
_gobj.addListener(this); _gobj.addListener(this);
// we don't want to claim to be finished until any derived classes // we don't want to claim to be finished until any derived classes that overrode this
// that overrode this method have executed, so we'll queue up a // method have executed, so we'll queue up a runnable here that will let the game manager
// runnable here that will let the game manager know that we're // know that we're ready on the next pass through the distributed event loop
// ready on the next pass through the distributed event loop
Log.info("Entering game " + _gobj.which() + "."); Log.info("Entering game " + _gobj.which() + ".");
if (_gobj.getPlayerIndex(bobj.getVisibleName()) != -1) { if (_gobj.getPlayerIndex(bobj.getVisibleName()) != -1) {
_ctx.getClient().getRunQueue().postRunnable(new Runnable() { _ctx.getClient().getRunQueue().postRunnable(new Runnable() {
public void run () { public void run () {
// finally let the game manager know that we're ready // finally let the game manager know that we're ready to roll
// to roll
playerReady(); playerReady();
} }
}); });
@@ -114,8 +106,7 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Removes our listener registration from the game object and cleans * Removes our listener registration from the game object and cleans house.
* house.
*/ */
public void didLeavePlace (PlaceObject plobj) public void didLeavePlace (PlaceObject plobj)
{ {
@@ -131,9 +122,9 @@ public abstract class GameController extends PlaceController
/** /**
* Convenience method to determine the type of game. * Convenience method to determine the type of game.
*/ */
public byte getGameType () public int getMatchType ()
{ {
return _config.getGameType(); return _config.getMatchType();
} }
/** /**
@@ -141,15 +132,13 @@ public abstract class GameController extends PlaceController
*/ */
public boolean isGameOver () public boolean isGameOver ()
{ {
boolean gameOver = (_gobj == null) || boolean gameOver = (_gobj == null) || (_gobj.state != GameObject.IN_PLAY);
(_gobj.state != GameObject.IN_PLAY);
return (_gameOver || gameOver); return (_gameOver || gameOver);
} }
/** /**
* Sets the client game over override. This is used in situations * Sets the client game over override. This is used in situations where we determine that the
* where we determine that the game is over before the server has * game is over before the server has informed us of such.
* informed us of such.
*/ */
public void setGameOver (boolean gameOver) public void setGameOver (boolean gameOver)
{ {
@@ -157,13 +146,11 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Calls {@link #gameWillReset}, ends the current game (locally, it * Calls {@link #gameWillReset}, ends the current game (locally, it does not tell the server to
* does not tell the server to end the game), and waits to receive a * end the game), and waits to receive a reset notification (which is simply an event setting
* reset notification (which is simply an event setting the game state * the game state to <code>IN_PLAY</code> even though it's already set to <code>IN_PLAY</code>)
* to <code>IN_PLAY</code> even though it's already set to * from the server which will start up a new game. Derived classes should override {@link
* <code>IN_PLAY</code>) from the server which will start up a new * #gameWillReset} to perform any game-specific animations.
* game. Derived classes should override {@link #gameWillReset} to
* perform any game-specific animations.
*/ */
public void resetGame () public void resetGame ()
{ {
@@ -183,9 +170,8 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Handles basic game controller action events. Derived classes should * Handles basic game controller action events. Derived classes should be sure to call
* be sure to call <code>super.handleAction</code> for events they * <code>super.handleAction</code> for events they don't specifically handle.
* don't specifically handle.
*/ */
public boolean handleAction (ActionEvent action) public boolean handleAction (ActionEvent action)
{ {
@@ -197,8 +183,7 @@ public abstract class GameController extends PlaceController
*/ */
public void systemMessage (String bundle, String msg) public void systemMessage (String bundle, String msg)
{ {
_ctx.getChatDirector().displayInfo( _ctx.getChatDirector().displayInfo(bundle, msg, GameCodes.GAME_CHAT_TYPE);
bundle, msg, GameCodes.GAME_CHAT_TYPE);
} }
// documentation inherited // documentation inherited
@@ -208,16 +193,16 @@ public abstract class GameController extends PlaceController
if (event.getName().equals(GameObject.STATE)) { if (event.getName().equals(GameObject.STATE)) {
int newState = event.getIntValue(); int newState = event.getIntValue();
if (!stateDidChange(newState)) { if (!stateDidChange(newState)) {
Log.warning("Game transitioned to unknown state " + Log.warning("Game transitioned to unknown state [gobj=" + _gobj +
"[gobj=" + _gobj + ", state=" + newState + "]."); ", state=" + newState + "].");
} }
} }
} }
/** /**
* Derived classes can override this method if they add additional game * Derived classes can override this method if they add additional game states and should
* states and should handle transitions to those states, returning true to * handle transitions to those states, returning true to indicate they were handled and calling
* indicate they were handled and calling super for the normal game states. * super for the normal game states.
*/ */
protected boolean stateDidChange (int state) protected boolean stateDidChange (int state)
{ {
@@ -236,8 +221,8 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Called after we've entered the game and everything has initialized * Called after we've entered the game and everything has initialized to notify the server that
* to notify the server that we, as a player, are ready to play. * we, as a player, are ready to play.
*/ */
protected void playerReady () protected void playerReady ()
{ {
@@ -246,9 +231,8 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Called when the game transitions to the <code>IN_PLAY</code> * Called when the game transitions to the <code>IN_PLAY</code> state. This happens when all of
* state. This happens when all of the players have arrived and the * the players have arrived and the server starts the game.
* server starts the game.
*/ */
protected void gameDidStart () protected void gameDidStart ()
{ {
@@ -269,9 +253,8 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Called when the game transitions to the <code>GAME_OVER</code> * Called when the game transitions to the <code>GAME_OVER</code> state. This happens when the
* state. This happens when the game reaches some end condition by * game reaches some end condition by normal means (is not cancelled or aborted).
* normal means (is not cancelled or aborted).
*/ */
protected void gameDidEnd () protected void gameDidEnd ()
{ {
@@ -297,9 +280,8 @@ public abstract class GameController extends PlaceController
} }
/** /**
* Called to give derived classes a chance to display animations, send * Called to give derived classes a chance to display animations, send a final packet, or do
* a final packet, or do any other business they care to do when the * any other business they care to do when the game is about to reset.
* game is about to reset.
*/ */
protected void gameWillReset () protected void gameWillReset ()
{ {
@@ -317,12 +299,10 @@ public abstract class GameController extends PlaceController
/** Our game configuration information. */ /** Our game configuration information. */
protected GameConfig _config; protected GameConfig _config;
/** A reference to the game object for the game that we're /** A reference to the game object for the game that we're controlling. */
* controlling. */
protected GameObject _gobj; protected GameObject _gobj;
/** A local flag overriding the game over state for situations where /** A local flag overriding the game over state for situations where the client knows the game
* the client knows the game is over before the server has * is over before the server has transitioned the game object accordingly. */
* transitioned the game object accordingly. */
protected boolean _gameOver; protected boolean _gameOver;
} }
@@ -31,74 +31,81 @@ import com.threerings.parlor.client.TableConfigurator;
import com.threerings.parlor.game.client.GameConfigurator; import com.threerings.parlor.game.client.GameConfigurator;
/** /**
* The game config class encapsulates the configuration information for a * The game config class encapsulates the configuration information for a particular type of game.
* particular type of game. The hierarchy of game config objects mimics the * The hierarchy of game config objects mimics the hierarchy of game managers and controllers. Both
* hierarchy of game managers and controllers. Both the game manager and game * the game manager and game controller are provided with the game config object when the game is
* controller are provided with the game config object when the game is
* created. * created.
* *
* <p> The game config object is also the mechanism used to instantiate the * <p> The game config object is also the mechanism used to instantiate the appropriate game
* appropriate game manager and controller. Every game must have an associated * manager and controller. Every game must have an associated game config derived class that
* game config derived class that overrides {@link #createController} and * overrides {@link #createController} and {@link #getManagerClassName}, returning the appropriate
* {@link #getManagerClassName}, returning the appropriate game controller and * game controller and manager class for that game. Thus the entire chain of events that causes a
* manager class for that game. Thus the entire chain of events that causes a * particular game to be created is the construction of the appropriate game config instance which
* particular game to be created is the construction of the appropriate game * is provided to the server as part of an invitation or via some other matchmaking mechanism.
* config instance which is provided to the server as part of an invitation or
* via some other matchmaking mechanism.
*/ */
public abstract class GameConfig extends PlaceConfig implements Cloneable public abstract class GameConfig extends PlaceConfig implements Cloneable
{ {
/** Game type constant: a game that is started with a list of players, /** Matchmaking type constant: a game that is started with a list of players, and those are the
* and those are the only players that may play. */ * only players that may play. */
public static final byte SEATED_GAME = 0; public static final int SEATED_GAME = 0;
/** Game type constant: a game that starts immediately, but only has /** Matchmaking type constant: a game that starts immediately, but only has a certain number of
* a certain number of player slots. Users enter the game room, and * player slots. Users enter the game room, and then choose where to sit. */
* then choose where to sit. */ public static final int SEATED_CONTINUOUS = 1;
public static final byte SEATED_CONTINUOUS = 1;
/** Game type constant: a game that starts immediately, and every /** Matchmaking type constant: a game that starts immediately, and every user that enters is a
* user that enters is a player. */ * player. */
public static final byte PARTY = 2; public static final int PARTY = 2;
/** The usernames of the players involved in this game, or an empty /** Maps the matchmaking type codes into strings used in our XML configuration. */
* array if such information is not needed by this particular game. */ public static final String[] TYPE_STRINGS = { "table", "entersit", "party" };
/** The usernames of the players involved in this game, or an empty array if such information
* is not needed by this particular game. */
public Name[] players = new Name[0]; public Name[] players = new Name[0];
/** Indicates whether or not this game is rated. */ /** Indicates whether or not this game is rated. */
public boolean rated = true; public boolean rated = true;
/** Configurations for AIs to be used in this game. Slots with real /** Configurations for AIs to be used in this game. Slots with real players should be null and
* players should be null and slots with AIs should contain * slots with AIs should contain configuration for those AIs. A null array indicates no use of
* configuration for those AIs. A null array indicates no use of AIs * AIs at all. */
* at all. */
public GameAI[] ais = new GameAI[0]; public GameAI[] ais = new GameAI[0];
/** /**
* Get the type of game. * Returns a numeric identifier for this game class. This may be used to track persisent
* information on a per-game basis.
*/ */
public byte getGameType () public abstract int getGameId ();
/**
* Returns a string identifier for this game class (e.g. "spades"). This may be used to
* identify a message bundle for which to obtain translations for this game configuration and
* to look up the name of the game in said bundle.
*/
public abstract String getGameIdent ();
/**
* Returns the matchmaking type of this game: {@link #SEATED_GAME}, etc.
*/
public int getMatchType ()
{ {
return SEATED_GAME; return SEATED_GAME;
} }
/** /**
* Returns the message bundle identifier for the bundle that should be * Creates a configurator that can be used to create a user interface for configuring this
* used to translate the translatable strings used to describe the * instance prior to starting the game. If no configuration is necessary, this method should
* game config parameters. * return null.
*/ */
public abstract String getBundleName (); public GameConfigurator createConfigurator ()
{
return null;
}
/** /**
* Creates a configurator that can be used to create a user interface * Creates a table configurator for initializing 'table' properties of the game. The default
* for configuring this instance prior to starting the game. If no * implementation returns null.
* configuration is necessary, this method should return null.
*/
public abstract GameConfigurator createConfigurator ();
/**
* Creates a table configurator for initializing 'table' properties
* of the game. The default implementation returns null.
*/ */
public TableConfigurator createTableConfigurator () public TableConfigurator createTableConfigurator ()
{ {
@@ -106,27 +113,8 @@ public abstract class GameConfig extends PlaceConfig implements Cloneable
} }
/** /**
* Returns a translatable label describing this game. * Returns a List of strings that describe the configuration of this game. Default
*/ * implementation returns an empty list.
public String getGameName ()
{
// the whole getRatingTypeId(), getGameName(), getBundleName()
// business should be cleaned up. we should have getGameIdent()
// and everything should have a default implementation using that
return "m." + getBundleName();
}
/**
* Returns the game rating type, if the system uses such things.
*/
public byte getRatingTypeId ()
{
return (byte)-1;
}
/**
* Returns a List of strings that describe the configuration of this
* game. Default implementation returns an empty list.
*/ */
public List<String> getDescription () public List<String> getDescription ()
{ {
@@ -134,26 +122,21 @@ public abstract class GameConfig extends PlaceConfig implements Cloneable
} }
/** /**
* Returns true if this game config object is equal to the supplied * Returns true if this game config object is equal to the supplied object (meaning it is a
* object (meaning it is also a game config object and its * game config for the same game and its configuration settings are the same as ours).
* configuration settings are the same as ours).
*/ */
public boolean equals (Object other) public boolean equals (Object other)
{ {
// make sure they're of the same class if (!(other instanceof GameConfig)) {
if (other.getClass() == this.getClass()) {
GameConfig that = (GameConfig) other;
return this.rated == that.rated;
} else {
return false; return false;
} }
GameConfig that = (GameConfig) other;
return (this.getGameId() == that.getGameId()) && (this.rated == that.rated);
} }
/** /**
* Computes a hashcode for this game config object that supports our * Computes a hashcode for this game config object that supports our {@link #equals}
* {@link #equals} implementation. Objects that are equal should have * implementation. Objects that are equal should have the same hashcode.
* the same hashcode.
*/ */
public int hashCode () public int hashCode ()
{ {
@@ -58,20 +58,17 @@ import com.threerings.parlor.server.ParlorSender;
import com.threerings.util.MessageBundle; import com.threerings.util.MessageBundle;
/** /**
* The game manager handles the server side management of a game. It * The game manager handles the server side management of a game. It manipulates the game state in
* manipulates the game state in accordance with the logic of the game * accordance with the logic of the game flow and generally manages the whole game playing process.
* flow and generally manages the whole game playing process.
* *
* <p> The game manager extends the place manager because games are * <p> The game manager extends the place manager because games are implicitly played in a
* implicitly played in a location, the players of the game implicitly * location, the players of the game implicitly bodies in that location.
* bodies in that location.
*/ */
public class GameManager extends PlaceManager public class GameManager extends PlaceManager
implements ParlorCodes, GameCodes implements ParlorCodes, GameCodes
{ {
/** /**
* Returns the configuration object for the game being managed by this * Returns the configuration object for the game being managed by this manager.
* manager.
*/ */
public GameConfig getGameConfig () public GameConfig getGameConfig ()
{ {
@@ -81,19 +78,19 @@ public class GameManager extends PlaceManager
/** /**
* A convenience method for getting the game type. * A convenience method for getting the game type.
*/ */
public byte getGameType () public int getMatchType ()
{ {
return _gameconfig.getGameType(); return _gameconfig.getMatchType();
} }
/** /**
* Adds the given player to the game at the first available player * Adds the given player to the game at the first available player index. This should only be
* index. This should only be called before the game is started, and * called before the game is started, and is most likely to be used to add players to party
* is most likely to be used to add players to party games. * games.
* *
* @param player the username of the player to add to this game. * @param player the username of the player to add to this game.
* @return the player index at which the player was added, or * @return the player index at which the player was added, or <code>-1</code> if the player
* <code>-1</code> if the player could not be added to the game. * could not be added to the game.
*/ */
public int addPlayer (Name player) public int addPlayer (Name player)
{ {
@@ -108,10 +105,9 @@ public class GameManager extends PlaceManager
// sanity-check the player index // sanity-check the player index
if (pidx == -1) { if (pidx == -1) {
Log.warning("Couldn't find free player index for player " + Log.warning("Couldn't find free player index for player [game=" + where() +
"[game=" + where() + ", player=" + player + ", player=" + player +
", players=" + StringUtil.toString(_gameobj.players) + ", players=" + StringUtil.toString(_gameobj.players) + "].");
"].");
return -1; return -1;
} }
@@ -120,9 +116,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Adds the given player to the game at the specified player index. * Adds the given player to the game at the specified player index. This should only be called
* This should only be called before the game is started, and is most * before the game is started, and is most likely to be used to add players to party games.
* likely to be used to add players to party games.
* *
* @param player the username of the player to add to this game. * @param player the username of the player to add to this game.
* @param pidx the player index at which the player is to be added. * @param pidx the player index at which the player is to be added.
@@ -132,34 +127,31 @@ public class GameManager extends PlaceManager
{ {
// make sure the specified player index is valid // make sure the specified player index is valid
if (pidx < 0 || pidx >= getPlayerSlots()) { if (pidx < 0 || pidx >= getPlayerSlots()) {
Log.warning("Attempt to add player at an invalid index " + Log.warning("Attempt to add player at an invalid index [game=" + where() +
"[game=" + where() + ", player=" + player + ", player=" + player + ", pidx=" + pidx + "].");
", pidx=" + pidx + "].");
return false; return false;
} }
// make sure the player index is available // make sure the player index is available
if (_gameobj.players[pidx] != null) { if (_gameobj.players[pidx] != null) {
Log.warning("Attempt to add player at occupied index " + Log.warning("Attempt to add player at occupied index [game=" + where() +
"[game=" + where() + ", player=" + player + ", player=" + player + ", pidx=" + pidx + "].");
", pidx=" + pidx + "].");
return false; return false;
} }
// make sure the player isn't already somehow a part of the game // make sure the player isn't already somehow a part of the game to avoid any potential
// to avoid any potential badness that might ensue if we added // badness that might ensue if we added them more than once
// them more than once
if (_gameobj.getPlayerIndex(player) != -1) { if (_gameobj.getPlayerIndex(player) != -1) {
Log.warning("Attempt to add player to game that they're already " + Log.warning("Attempt to add player to game that they're already playing " +
"playing [game=" + where() + ", player=" + player + "]."); "[game=" + where() + ", player=" + player + "].");
return false; return false;
} }
// get the player's body object // get the player's body object
BodyObject bobj = CrowdServer.lookupBody(player); BodyObject bobj = CrowdServer.lookupBody(player);
if (bobj == null) { if (bobj == null) {
Log.warning("Unable to get body object while adding player " + Log.warning("Unable to get body object while adding player [game=" + where() +
"[game=" + where() + ", player=" + player + "]."); ", player=" + player + "].");
return false; return false;
} }
@@ -179,9 +171,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Removes the given player from the game. This is most likely to be * Removes the given player from the game. This is most likely to be used to allow players
* used to allow players involved in a party game to leave the game * involved in a party game to leave the game early-on if they realize they'd rather not play
* early-on if they realize they'd rather not play for some reason. * for some reason.
* *
* @param player the username of the player to remove from this game. * @param player the username of the player to remove from this game.
* @return true if the player was successfully removed, false if not. * @return true if the player was successfully removed, false if not.
@@ -193,10 +185,9 @@ public class GameManager extends PlaceManager
// sanity-check the player index // sanity-check the player index
if (pidx == -1) { if (pidx == -1) {
Log.warning("Attempt to remove non-player from players list " + Log.warning("Attempt to remove non-player from players list [game=" + where() +
"[game=" + where() + ", player=" + player + ", player=" + player +
", players=" + StringUtil.toString(_gameobj.players) + ", players=" + StringUtil.toString(_gameobj.players) + "].");
"].");
return false; return false;
} }
@@ -221,9 +212,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Replaces the player at the specified index and calls {@link * Replaces the player at the specified index and calls {@link #playerWasReplaced} to let
* #playerWasReplaced} to let derived classes and delegates know * derived classes and delegates know what's going on.
* what's going on.
*/ */
public void replacePlayer (final int pidx, final Name player) public void replacePlayer (final int pidx, final Name player)
{ {
@@ -236,15 +226,14 @@ public class GameManager extends PlaceManager
// notify our delegates // notify our delegates
applyToDelegates(new DelegateOp() { applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) { public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).playerWasReplaced( ((GameManagerDelegate)delegate).playerWasReplaced(pidx, oplayer, player);
pidx, oplayer, player);
} }
}); });
} }
/** /**
* Returns the user object for the player with the specified index or * Returns the user object for the player with the specified index or null if the player at
* null if the player at that index is not online. * that index is not online.
*/ */
public BodyObject getPlayer (int playerIdx) public BodyObject getPlayer (int playerIdx)
{ {
@@ -259,9 +248,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Sets the specified player as an AI with the specified * Sets the specified player as an AI with the specified configuration. It is assumed that this
* configuration. It is assumed that this will be set soon after the * will be set soon after the player names for all AIs present in the game. (It should be done
* player names for all AIs present in the game. (It should be done
* before human players start trickling into the game.) * before human players start trickling into the game.)
* *
* @param pidx the player index of the AI. * @param pidx the player index of the AI.
@@ -288,8 +276,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns the name of the player with the specified index or null if * Returns the name of the player with the specified index or null if no player exists at that
* no player exists at that index. * index.
*/ */
public Name getPlayerName (int index) public Name getPlayerName (int index)
{ {
@@ -297,8 +285,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns the player index of the given user in the game, or * Returns the player index of the given user in the game, or <code>-1</code> if the player is
* <code>-1</code> if the player is not involved in the game. * not involved in the game.
*/ */
public int getPlayerIndex (Name username) public int getPlayerIndex (Name username)
{ {
@@ -306,14 +294,12 @@ public class GameManager extends PlaceManager
} }
/** /**
* Get the player index of the specified oid, or -1 if the oid is * Get the player index of the specified oid, or -1 if the oid is not a player or is a player
* not a player or is a player that is not presently in the game. * that is not presently in the game.
*/ */
public int getPresentPlayerIndex (int bodyOid) public int getPresentPlayerIndex (int bodyOid)
{ {
return (_playerOids == null) return (_playerOids == null) ? -1 : IntListUtil.indexOf(_playerOids, bodyOid);
? -1
: IntListUtil.indexOf(_playerOids, bodyOid);
} }
/** /**
@@ -349,13 +335,11 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns whether the player at the specified player index is actively * Returns whether the player at the specified player index is actively playing the game
* playing the game
*/ */
public boolean isActivePlayer (int pidx) public boolean isActivePlayer (int pidx)
{ {
return _gameobj.isActivePlayer(pidx) && return _gameobj.isActivePlayer(pidx) && (getPlayerOid(pidx) > 0 || isAI(pidx));
(getPlayerOid(pidx) > 0 || isAI(pidx));
} }
/** /**
@@ -377,14 +361,12 @@ public class GameManager extends PlaceManager
/** /**
* Sends a system message to the players in the game room. * Sends a system message to the players in the game room.
* *
* @param waitForStart if true, the message will not be sent until the * @param waitForStart if true, the message will not be sent until the game has started.
* game has started.
*/ */
public void systemMessage ( public void systemMessage (
String msgbundle, String msg, boolean waitForStart) String msgbundle, String msg, boolean waitForStart)
{ {
if (waitForStart && if (waitForStart && ((_gameobj == null) || (_gameobj.state == GameObject.PRE_GAME))) {
((_gameobj == null) || (_gameobj.state == GameObject.PRE_GAME))) {
// queue up the message. // queue up the message.
if (_startmsgs == null) { if (_startmsgs == null) {
_startmsgs = new ArrayList<Tuple<String,String>>(); _startmsgs = new ArrayList<Tuple<String,String>>();
@@ -398,23 +380,20 @@ public class GameManager extends PlaceManager
} }
/** /**
* This is called when the game is ready to start (all players * This is called when the game is ready to start (all players involved have delivered their
* involved have delivered their "am ready" notifications). It calls * "am ready" notifications). It calls {@link #gameWillStart}, sets the necessary wheels in
* {@link #gameWillStart}, sets the necessary wheels in motion and * motion and then calls {@link #gameDidStart}. Derived classes should override one or both of
* then calls {@link #gameDidStart}. Derived classes should override * the calldown functions (rather than this function) if they need to do things before or after
* one or both of the calldown functions (rather than this function) * the game starts.
* if they need to do things before or after the game starts.
* *
* @return true if the game was started, false if it could not be * @return true if the game was started, false if it could not be started because it was
* started because it was already in play or because all players have * already in play or because all players have not yet reported in.
* not yet reported in.
*/ */
public boolean startGame () public boolean startGame ()
{ {
// complain if we're already started // complain if we're already started
if (_gameobj.state == GameObject.IN_PLAY) { if (_gameobj.state == GameObject.IN_PLAY) {
Log.warning("Requested to start an already in-play game " + Log.warning("Requested to start an already in-play game [game=" + where() + "].");
"[game=" + where() + "].");
Thread.dumpStack(); Thread.dumpStack();
return false; return false;
} }
@@ -424,27 +403,24 @@ public class GameManager extends PlaceManager
// make sure everyone has turned up // make sure everyone has turned up
if (!allPlayersReady()) { if (!allPlayersReady()) {
Log.warning("Requested to start a game that is still " + Log.warning("Requested to start a game that is still awaiting players " +
"awaiting players [game=" + where() + "[game=" + where() + ", pnames=" + StringUtil.toString(_gameobj.players) +
", pnames=" + StringUtil.toString(_gameobj.players) +
", poids=" + StringUtil.toString(_playerOids) + "]."); ", poids=" + StringUtil.toString(_playerOids) + "].");
return false; return false;
} }
// if we're still waiting for a call to endGame() to propagate, // if we're still waiting for a call to endGame() to propagate, queue up a runnable to
// queue up a runnable to start the game which will allow the // start the game which will allow the endGame() to propagate before we start things up
// endGame() to propagate before we start things up
if (_committedState == GameObject.IN_PLAY) { if (_committedState == GameObject.IN_PLAY) {
if (_postponedStart) { if (_postponedStart) {
// We've already tried postponing once, doesn't do us any // We've already tried postponing once, doesn't do us any good to throw ourselves
// good to throw ourselves into a frenzy trying again. // into a frenzy trying again.
Log.warning("Tried to postpone the start of a still-ending game " + Log.warning("Tried to postpone the start of a still-ending game multiple times " +
"multiple times [game=" + where() + "]."); "[game=" + where() + "].");
_postponedStart = false; _postponedStart = false;
return false; return false;
} }
Log.info("Postponing start of still-ending game " + Log.info("Postponing start of still-ending game [game=" + where() + "].");
"[game=" + where() + "].");
_postponedStart = true; _postponedStart = true;
// TEMP: track down weirdness // TEMP: track down weirdness
final Exception firstCall = new Exception(); final Exception firstCall = new Exception();
@@ -500,30 +476,27 @@ public class GameManager extends PlaceManager
if (shouldEndGame()) { if (shouldEndGame()) {
endGame(); endGame();
} else { } else {
// otherwise report that the player was knocked out to other // otherwise report that the player was knocked out to other people in his/her room
// people in his/her room
reportPlayerKnockedOut(pidx); reportPlayerKnockedOut(pidx);
} }
} }
/** /**
* Called when the game is known to be over. This will call some * Called when the game is known to be over. This will call some calldown functions to
* calldown functions to determine the winner of the game and then * determine the winner of the game and then transition the game to the {@link
* transition the game to the {@link GameObject#GAME_OVER} state. * GameObject#GAME_OVER} state.
*/ */
public void endGame () public void endGame ()
{ {
// TEMP: debug pending rating repeat bug // TEMP: debug pending rating repeat bug
if (_gameEndTracker.checkCall( if (_gameEndTracker.checkCall(
"Requested to end already ended game " + "Requested to end already ended game [game=" + where() + "].")) {
"[game=" + where() + "].")) {
return; return;
} }
// END TEMP // END TEMP
if (!_gameobj.isInPlay()) { if (!_gameobj.isInPlay()) {
Log.info("Refusing to end game that was not in play " + Log.info("Refusing to end game that was not in play [game=" + where() + "].");
"[game=" + where() + "].");
return; return;
} }
@@ -544,22 +517,19 @@ public class GameManager extends PlaceManager
_gameobj.commitTransaction(); _gameobj.commitTransaction();
} }
// wait until we hear the game state transition on the game object // wait until we hear the game state transition on the game object to invoke our game over
// to invoke our game over code so that we can be sure that any // code so that we can be sure that any final events dispatched on the game object prior to
// final events dispatched on the game object prior to the call to // the call to endGame() have been dispatched
// endGame() have been dispatched
} }
/** /**
* Sets the state of the game to {@link GameObject#CANCELLED}. * Sets the state of the game to {@link GameObject#CANCELLED}.
* *
* @return true if the game was cancelled, false if it was already over or * @return true if the game was cancelled, false if it was already over or cancelled.
* cancelled.
*/ */
public boolean cancelGame () public boolean cancelGame ()
{ {
if (_gameobj.state != GameObject.GAME_OVER && if (_gameobj.state != GameObject.GAME_OVER && _gameobj.state != GameObject.CANCELLED) {
_gameobj.state != GameObject.CANCELLED) {
_gameobj.setState(GameObject.CANCELLED); _gameobj.setState(GameObject.CANCELLED);
return true; return true;
} }
@@ -567,10 +537,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns whether game conclusion antics such as rating updates * Returns whether game conclusion antics such as rating updates should be performed when an
* should be performed when an in-play game is ended. Derived classes * in-play game is ended. Derived classes may wish to override this method to customize the
* may wish to override this method to customize the conditions under * conditions under which the game is concluded.
* which the game is concluded.
*/ */
public boolean shouldConcludeGame () public boolean shouldConcludeGame ()
{ {
@@ -578,13 +547,11 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when the game is to be reset to its starting state in * Called when the game is to be reset to its starting state in preparation for a new game
* preparation for a new game without actually ending the current * without actually ending the current game. It calls {@link #gameWillReset} followed by the
* game. It calls {@link #gameWillReset} followed by the standard game * standard game start processing ({@link #gameWillStart} and {@link #gameDidStart}). Derived
* start processing ({@link #gameWillStart} and {@link * classes should override these calldown functions (rather than this function) if they need to
* #gameDidStart}). Derived classes should override these calldown * do things before or after the game resets.
* functions (rather than this function) if they need to do things
* before or after the game resets.
*/ */
public void resetGame () public void resetGame ()
{ {
@@ -597,21 +564,20 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called by the client when the player is ready for the game to start. * Called by the client when the player is ready for the game to start. This method is
* This method is dispatched dynamically by {@link #messageReceived}. * dispatched dynamically by {@link #messageReceived}.
*/ */
public void playerReady (BodyObject caller) public void playerReady (BodyObject caller)
{ {
// get the user's player index // get the user's player index
int pidx = _gameobj.getPlayerIndex(caller.getVisibleName()); int pidx = _gameobj.getPlayerIndex(caller.getVisibleName());
if (pidx == -1) { if (pidx == -1) {
// only complain if this is not a party game, since it's // only complain if this is not a party game, since it's perfectly normal to receive a
// perfectly normal to receive a player ready notification // player ready notification from a user entering a party game in which they're not yet
// from a user entering a party game in which they're not yet
// a participant // a participant
if (needsNoShowTimer()) { if (needsNoShowTimer()) {
Log.warning("Received playerReady() from non-player? " + Log.warning("Received playerReady() from non-player? [game=" + where() +
"[game=" + where() + ", who=" + caller.who() + "]."); ", who=" + caller.who() + "].");
} }
return; return;
} }
@@ -626,8 +592,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns true if all (non-AI) players have delivered their {@link * Returns true if all (non-AI) players have delivered their {@link #playerReady}
* #playerReady} notifications, false if they have not. * notifications, false if they have not.
*/ */
public boolean allPlayersReady () public boolean allPlayersReady ()
{ {
@@ -640,10 +606,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns true if the player at the specified slot is ready (or if * Returns true if the player at the specified slot is ready (or if there is meant to be no
* there is meant to be no player in that slot), false if there is * player in that slot), false if there is meant to be a player in the specified slot and they
* meant to be a player in the specified slot and they have not yet * have not yet reported that they are ready.
* reported that they are ready.
*/ */
public boolean playerIsReady (int pidx) public boolean playerIsReady (int pidx)
{ {
@@ -653,20 +618,19 @@ public class GameManager extends PlaceManager
} }
/** /**
* Returns true if this game requires a no-show timer. The default * Returns true if this game requires a no-show timer. The default implementation returns true
* implementation returns true for non-party games and false for party * for non-party games and false for party games. Derived classes may wish to change or augment
* games. Derived classes may wish to change or augment this behavior. * this behavior.
*/ */
protected boolean needsNoShowTimer () protected boolean needsNoShowTimer ()
{ {
return (getGameType() == GameConfig.SEATED_GAME); return (getMatchType() == GameConfig.SEATED_GAME);
} }
/** /**
* Derived classes that need their AIs to be ticked periodically * Derived classes that need their AIs to be ticked periodically should override this method
* should override this method and return true. Many AIs can act * and return true. Many AIs can act entirely in reaction to game state changes and need no
* entirely in reaction to game state changes and need no periodic * periodic ticking which is why ticking is disabled by default.
* ticking which is why ticking is disabled by default.
* *
* @see #tickAIs * @see #tickAIs
*/ */
@@ -676,9 +640,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when a player was added to the game. Derived classes may * Called when a player was added to the game. Derived classes may override this method to
* override this method to perform any game-specific actions they * perform any game-specific actions they desire, but should be sure to call
* desire, but should be sure to call
* <code>super.playerWasAdded()</code>. * <code>super.playerWasAdded()</code>.
* *
* @param player the username of the player added to the game. * @param player the username of the player added to the game.
@@ -689,22 +652,19 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when a player was removed from the game. Derived classes * Called when a player was removed from the game. Derived classes may override this method to
* may override this method to perform any game-specific actions they * perform any game-specific actions they desire, but should be sure to call
* desire, but should be sure to call
* <code>super.playerWasRemoved()</code>. * <code>super.playerWasRemoved()</code>.
* *
* @param player the username of the player removed from the game. * @param player the username of the player removed from the game.
* @param pidx the player index of the player before they were removed * @param pidx the player index of the player before they were removed from the game.
* from the game.
*/ */
protected void playerWasRemoved (Name player, int pidx) protected void playerWasRemoved (Name player, int pidx)
{ {
} }
/** /**
* Called when a player has been replaced via a call to {@link * Called when a player has been replaced via a call to {@link #replacePlayer}.
* #replacePlayer}.
*/ */
protected void playerWasReplaced (int pidx, Name oldPlayer, Name newPlayer) protected void playerWasReplaced (int pidx, Name oldPlayer, Name newPlayer)
{ {
@@ -717,14 +677,12 @@ public class GameManager extends PlaceManager
{ {
BodyObject user = getPlayer(pidx); BodyObject user = getPlayer(pidx);
if (user == null) { if (user == null) {
// body object can be null for ai players return; // body object can be null for ai players
return;
} }
DObject place = CrowdServer.omgr.getObject(user.location); DObject place = CrowdServer.omgr.getObject(user.location);
if (place != null) { if (place != null) {
place.postMessage(PLAYER_KNOCKED_OUT, place.postMessage(PLAYER_KNOCKED_OUT, new Object[] { new int[] { user.getOid() } });
new Object[] { new int[] { user.getOid() } });
} }
} }
@@ -770,8 +728,8 @@ public class GameManager extends PlaceManager
// set up an initial player status array // set up an initial player status array
_gameobj.setPlayerStatus(new int[getPlayerSlots()]); _gameobj.setPlayerStatus(new int[getPlayerSlots()]);
// save off the number of players so that we needn't repeatedly // save off the number of players so that we needn't repeatedly iterate through the player
// iterate through the player name array server-side unnecessarily // name array server-side unnecessarily
_playerCount = _gameobj.getPlayerCount(); _playerCount = _gameobj.getPlayerCount();
// instantiate a player oid array which we'll fill in later // instantiate a player oid array which we'll fill in later
@@ -780,8 +738,8 @@ public class GameManager extends PlaceManager
// give delegates a chance to do their thing // give delegates a chance to do their thing
super.didStartup(); super.didStartup();
// let the players of this game know that we're ready to roll (if // let the players of this game know that we're ready to roll (if we have a specific set of
// we have a specific set of players) // players)
for (int ii = 0; ii < getPlayerSlots(); ii++) { for (int ii = 0; ii < getPlayerSlots(); ii++) {
// skip non-existent players and AIs // skip non-existent players and AIs
if (!_gameobj.isOccupiedPlayer(ii) || isAI(ii)) { if (!_gameobj.isOccupiedPlayer(ii) || isAI(ii)) {
@@ -790,8 +748,7 @@ public class GameManager extends PlaceManager
BodyObject bobj = CrowdServer.lookupBody(_gameobj.players[ii]); BodyObject bobj = CrowdServer.lookupBody(_gameobj.players[ii]);
if (bobj == null) { if (bobj == null) {
Log.warning("Unable to deliver game ready to non-existent " + Log.warning("Unable to deliver game ready to non-existent player [game=" + where() +
"player [game=" + where() +
", player=" + _gameobj.players[ii] + "]."); ", player=" + _gameobj.players[ii] + "].");
continue; continue;
} }
@@ -841,22 +798,21 @@ public class GameManager extends PlaceManager
endPlayerGame(pidx); endPlayerGame(pidx);
} }
// then complete the bodyLeft() processing which may result in a call // then complete the bodyLeft() processing which may result in a call to placeBecameEmpty()
// to placeBecameEmpty() which will shut the game down // which will shut the game down
super.bodyLeft(bodyOid); super.bodyLeft(bodyOid);
} }
/** /**
* When a game room becomes empty, we cancel the game if it's still in * When a game room becomes empty, we cancel the game if it's still in progress and close down
* progress and close down the game room. * the game room.
*/ */
protected void placeBecameEmpty () protected void placeBecameEmpty ()
{ {
// Log.info("Game room empty. Going away. [game=" + where() + "]."); // Log.info("Game room empty. Going away. [game=" + where() + "].");
// if we're in play then move to game over // if we're in play then move to game over
if (_gameobj.state != GameObject.PRE_GAME && if (_gameobj.state != GameObject.PRE_GAME && _gameobj.state != GameObject.GAME_OVER &&
_gameobj.state != GameObject.GAME_OVER &&
_gameobj.state != GameObject.CANCELLED) { _gameobj.state != GameObject.CANCELLED) {
_gameobj.setState(GameObject.GAME_OVER); _gameobj.setState(GameObject.GAME_OVER);
// and shutdown directly // and shutdown directly
@@ -870,23 +826,21 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when all players have arrived in the game room. By default, * Called when all players have arrived in the game room. By default, this starts up the game,
* this starts up the game, but a manager may wish to override this * but a manager may wish to override this and start the game according to different criterion.
* and start the game according to different criterion.
*/ */
protected void playersAllHere () protected void playersAllHere ()
{ {
// if we're a seated game and we haven't already started, start. // if we're a seated game and we haven't already started, start.
if ((getGameType() == GameConfig.SEATED_GAME) && (_gameobj.state == GameObject.PRE_GAME)) { if ((getMatchType() == GameConfig.SEATED_GAME) && (_gameobj.state == GameObject.PRE_GAME)) {
startGame(); startGame();
} }
} }
/** /**
* Called after the no-show delay has expired following the delivery * Called after the no-show delay has expired following the delivery of notifications to all
* of notifications to all players that the game is ready. * players that the game is ready. <em>Note:</em> this is not called for party games. Those
* <em>Note:</em> this is not called for party games. Those games have * games have a human who decides when to start the game.
* a human who decides when to start the game.
*/ */
protected void checkForNoShows () protected void checkForNoShows ()
{ {
@@ -914,14 +868,13 @@ public class GameManager extends PlaceManager
} }
/** /**
* This is called when some, but not all, players failed to show up * This is called when some, but not all, players failed to show up for a game. The default
* for a game. The default implementation simply cancels the game. * implementation simply cancels the game.
*/ */
protected void handlePartialNoShow () protected void handlePartialNoShow ()
{ {
// mark the no-show players; this will cause allPlayersReady() to // mark the no-show players; this will cause allPlayersReady() to think that everyone has
// think that everyone has arrived, but still allow us to tell who // arrived, but still allow us to tell who has not shown up in gameDidStart()
// has not shown up in gameDidStart()
int humansHere = 0; int humansHere = 0;
for (int ii = 0; ii < _playerOids.length; ii++) { for (int ii = 0; ii < _playerOids.length; ii++) {
if (_playerOids[ii] == 0) { if (_playerOids[ii] == 0) {
@@ -938,9 +891,8 @@ public class GameManager extends PlaceManager
cancelGame(); cancelGame();
} else { } else {
// go ahead and report that everyone is ready (which will start the // go ahead and report that everyone is ready (which will start the game);
// game); gameDidStart() will take care of giving the boot to // gameDidStart() will take care of giving the boot to anyone who isn't around
// anyone who isn't around
Log.info("Forcing start of partial no-show game [game=" + where() + Log.info("Forcing start of partial no-show game [game=" + where() +
", poids=" + StringUtil.toString(_playerOids) + "]."); ", poids=" + StringUtil.toString(_playerOids) + "].");
playersAllHere(); playersAllHere();
@@ -948,8 +900,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* @return true if we should start the game even without any humans. * @return true if we should start the game even without any humans. Default implementation
* Default implementation always returns false. * always returns false.
*/ */
protected boolean startWithoutHumans () protected boolean startWithoutHumans ()
{ {
@@ -957,11 +909,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when the game is about to start, but before the game start * Called when the game is about to start, but before the game start notification has been
* notification has been delivered to the players. Derived classes * delivered to the players. Derived classes should override this if they need to perform some
* should override this if they need to perform some pre-start * pre-start activities, but should be sure to call <code>super.gameWillStart()</code>.
* activities, but should be sure to call
* <code>super.gameWillStart()</code>.
*/ */
protected void gameWillStart () protected void gameWillStart ()
{ {
@@ -977,8 +927,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when the game state changes. This happens after the attribute * Called when the game state changes. This happens after the attribute change event has
* change event has propagated. * propagated.
* *
* @param state the new game state. * @param state the new game state.
* @param oldState the previous game state. * @param oldState the previous game state.
@@ -991,8 +941,8 @@ public class GameManager extends PlaceManager
break; break;
case GameObject.GAME_OVER: case GameObject.GAME_OVER:
// we do some jiggery pokery to allow derived game objects to have // we do some jiggery pokery to allow derived game objects to have different notions of
// different notions of what it means to be in play // what it means to be in play
_gameobj.state = oldState; _gameobj.state = oldState;
boolean wasInPlay = _gameobj.isInPlay(); boolean wasInPlay = _gameobj.isInPlay();
_gameobj.state = state; _gameobj.state = state;
@@ -1016,11 +966,10 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called after the game start notification was dispatched. Derived * Called after the game start notification was dispatched. Derived classes can override this
* classes can override this to put whatever wheels they might need * to put whatever wheels they might need into motion now that the game is started (if anything
* into motion now that the game is started (if anything other than * other than transitioning the game to {@link GameObject#IN_PLAY} is necessary), but should be
* transitioning the game to {@link GameObject#IN_PLAY} is necessary), * sure to call <code>super.gameDidStart()</code>.
* but should be sure to call <code>super.gameDidStart()</code>.
*/ */
protected void gameDidStart () protected void gameDidStart ()
{ {
@@ -1034,8 +983,7 @@ public class GameManager extends PlaceManager
// inform the players of any pending messages. // inform the players of any pending messages.
if (_startmsgs != null) { if (_startmsgs != null) {
for (Tuple<String,String> mtup : _startmsgs) { for (Tuple<String,String> mtup : _startmsgs) {
systemMessage(mtup.left, // bundle systemMessage(mtup.left, /* bundle */ mtup.right /* message */);
mtup.right); // message
} }
_startmsgs = null; _startmsgs = null;
} }
@@ -1045,8 +993,7 @@ public class GameManager extends PlaceManager
AIGameTicker.registerAIGame(this); AIGameTicker.registerAIGame(this);
} }
// any players who have not claimed that they are ready should now // any players who have not claimed that they are ready should now be given le boote royale
// be given le boote royale
for (int ii = 0; ii < _playerOids.length; ii++) { for (int ii = 0; ii < _playerOids.length; ii++) {
if (_playerOids[ii] == -1) { if (_playerOids[ii] == -1) {
Log.info("Booting no-show player [game=" + where() + Log.info("Booting no-show player [game=" + where() +
@@ -1058,8 +1005,7 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called by the {@link AIGameTicker} if we're registered as an AI * Called by the {@link AIGameTicker} if we're registered as an AI game.
* game.
*/ */
protected void tickAIs () protected void tickAIs ()
{ {
@@ -1097,10 +1043,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when a player has been marked as knocked out but before the * Called when a player has been marked as knocked out but before the knock-out status update
* knock-out status update has been sent to the players. Any status * has been sent to the players. Any status information that needs be updated in light of the
* information that needs be updated in light of the knocked out * knocked out player can be updated here.
* player can be updated here.
*/ */
protected void playerGameDidEnd (int pidx) protected void playerGameDidEnd (int pidx)
{ {
@@ -1109,13 +1054,11 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when a player leaves the game in order to determine whether * Called when a player leaves the game in order to determine whether the game should be ended
* the game should be ended based on its current state, which will * based on its current state, which will include updated player status for the player in
* include updated player status for the player in question. The * question. The default implementation returns true if the game is in play and there is only
* default implementation returns true if the game is in play and * one player left. Derived classes may wish to override this method in order to customize the
* there is only one player left. Derived classes may wish to * required end-game conditions.
* override this method in order to customize the required end-game
* conditions.
*/ */
protected boolean shouldEndGame () protected boolean shouldEndGame ()
{ {
@@ -1123,11 +1066,10 @@ public class GameManager extends PlaceManager
} }
/** /**
* Assigns the final winning status for each player to their respect * Assigns the final winning status for each player to their respect player index in the
* player index in the supplied array. This will be called by {@link * supplied array. This will be called by {@link #endGame} when the game is over. The default
* #endGame} when the game is over. The default implementation marks * implementation marks no players as winners. Derived classes should override this method in
* no players as winners. Derived classes should override this method * order to customize the winning conditions.
* in order to customize the winning conditions.
*/ */
protected void assignWinners (boolean[] winners) protected void assignWinners (boolean[] winners)
{ {
@@ -1135,11 +1077,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when the game is about to end, but before the game end * Called when the game is about to end, but before the game end notification has been
* notification has been delivered to the players. Derived classes * delivered to the players. Derived classes should override this if they need to perform some
* should override this if they need to perform some pre-end * pre-end activities, but should be sure to call <code>super.gameWillEnd()</code>.
* activities, but should be sure to call
* <code>super.gameWillEnd()</code>.
*/ */
protected void gameWillEnd () protected void gameWillEnd ()
{ {
@@ -1152,9 +1092,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called after the game has transitioned to the {@link * Called after the game has transitioned to the {@link GameObject#GAME_OVER} state. Derived
* GameObject#GAME_OVER} state. Derived classes should override this * classes should override this to perform any post-game activities, but should be sure to call
* to perform any post-game activities, but should be sure to call
* <code>super.gameDidEnd()</code>. * <code>super.gameDidEnd()</code>.
*/ */
protected void gameDidEnd () protected void gameDidEnd ()
@@ -1181,15 +1120,13 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called to let the manager know that the game was cancelled (and may be * Called to let the manager know that the game was cancelled (and may be about to be shutdown
* about to be shutdown if there's no one in the room). In the base * if there's no one in the room). In the base framework a game will only be canceled if no one
* framework a game will only be canceled if no one shows up, so {@link * shows up, so {@link #gameWillStart}, etc. will never have been called and thus {@link
* #gameWillStart}, etc. will never have been called and thus {@link * #gameWillEnd}, etc. will not be called. However, if a game chooses to cancel itself for
* #gameWillEnd}, etc. will not be called. However, if a game chooses to * whatever reason, no effort will be made to call {@link #endGame} and the game ending call
* cancel itself for whatever reason, no effort will be made to call {@link * backs so that game can override this method to do anything it needs. Note that {@link
* #endGame} and the game ending call backs so that game can override this * #didShutdown} will be called in every case and that's generally the best place to free
* method to do anything it needs. Note that {@link #didShutdown} will be
* called in every case and that's generally the best place to free
* resources so this method may not be needed. * resources so this method may not be needed.
*/ */
protected void gameWasCancelled () protected void gameWasCancelled ()
@@ -1198,8 +1135,7 @@ public class GameManager extends PlaceManager
} }
/** /**
* Report winner and loser oids to each room that any of the * Report winner and loser oids to each room that any of the winners/losers is in.
* winners/losers is in.
*/ */
protected void reportWinnersAndLosers () protected void reportWinnersAndLosers ()
{ {
@@ -1218,8 +1154,7 @@ public class GameManager extends PlaceManager
} }
} }
Object[] args = Object[] args = new Object[] { winners.toIntArray(), losers.toIntArray() };
new Object[] { winners.toIntArray(), losers.toIntArray() };
// now send a message event to each room // now send a message event to each room
for (int ii=0, nn = places.size(); ii < nn; ii++) { for (int ii=0, nn = places.size(); ii < nn; ii++) {
@@ -1231,10 +1166,9 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called when the game is about to reset, but before the board has * Called when the game is about to reset, but before the board has been re-initialized or any
* been re-initialized or any other clearing out of game data has * other clearing out of game data has taken place. Derived classes should override this if
* taken place. Derived classes should override this if they need to * they need to perform some pre-reset activities.
* perform some pre-reset activities.
*/ */
protected void gameWillReset () protected void gameWillReset ()
{ {
@@ -1250,8 +1184,8 @@ public class GameManager extends PlaceManager
} }
/** /**
* Gives game managers an opportunity to perform periodic processing * Gives game managers an opportunity to perform periodic processing that is not driven by
* that is not driven by events generated by the player. * events generated by the player.
*/ */
protected void tick (long tickStamp) protected void tick (long tickStamp)
{ {
@@ -1259,8 +1193,7 @@ public class GameManager extends PlaceManager
} }
/** /**
* Called periodically to call {@link #tick} on all registered game * Called periodically to call {@link #tick} on all registered game managers.
* managers.
*/ */
protected static void tickAllGames () protected static void tickAllGames ()
{ {
@@ -1271,8 +1204,7 @@ public class GameManager extends PlaceManager
try { try {
gmgr.tick(now); gmgr.tick(now);
} catch (Exception e) { } catch (Exception e) {
Log.warning( Log.warning("Game manager choked during tick [gmgr=" + gmgr + "].");
"Game manager choked during tick [gmgr=" + gmgr + "].");
Log.logStackTrace(e); Log.logStackTrace(e);
} }
} }
@@ -1298,12 +1230,11 @@ public class GameManager extends PlaceManager
} }
/** Listens for game state changes. */ /** Listens for game state changes. */
protected AttributeChangeListener _stateListener = protected AttributeChangeListener _stateListener = new AttributeChangeListener() {
new AttributeChangeListener() {
public void attributeChanged (AttributeChangedEvent event) { public void attributeChanged (AttributeChangedEvent event) {
if (event.getName().equals(GameObject.STATE)) { if (event.getName().equals(GameObject.STATE)) {
stateDidChange(_committedState = event.getIntValue(), stateDidChange(_committedState = event.getIntValue(),
((Integer)event.getOldValue()).intValue()); ((Integer)event.getOldValue()).intValue());
} }
} }
}; };
@@ -1320,19 +1251,17 @@ public class GameManager extends PlaceManager
/** The oids of our player and AI body objects. */ /** The oids of our player and AI body objects. */
protected int[] _playerOids; protected int[] _playerOids;
/** If AIs are present, contains their configuration, or null at human /** If AIs are present, contains their configuration, or null at human player indexes. */
* player indexes. */
protected GameAI[] _AIs; protected GameAI[] _AIs;
/** If non-null, contains bundles and messages that should be sent as /** If non-null, contains bundles and messages that should be sent as system messages once the
* system messages once the game has started. */ * game has started. */
protected ArrayList<Tuple<String,String>> _startmsgs; protected ArrayList<Tuple<String,String>> _startmsgs;
/** Our delegate operator to tick AIs. */ /** Our delegate operator to tick AIs. */
protected TickAIDelegateOp _tickAIOp; protected TickAIDelegateOp _tickAIOp;
/** The state of the game that has been propagated to our /** The state of the game that has been propagated to our subscribers. */
* subscribers. */
protected int _committedState; protected int _committedState;
/** TEMP: debugging the pending rating double release bug. */ /** TEMP: debugging the pending rating double release bug. */
@@ -1342,14 +1271,13 @@ public class GameManager extends PlaceManager
protected boolean _postponedStart = false; protected boolean _postponedStart = false;
/** A list of all currently active game managers. */ /** A list of all currently active game managers. */
protected static ArrayList<GameManager> _managers = protected static ArrayList<GameManager> _managers = new ArrayList<GameManager>();
new ArrayList<GameManager>();
/** The interval for the game manager tick. */ /** The interval for the game manager tick. */
protected static Interval _tickInterval; protected static Interval _tickInterval;
/** We give players 30 seconds to turn up in a game; after that, /** We give players 30 seconds to turn up in a game; after that, they're considered a no
* they're considered a no show. */ * show. */
protected static final long NOSHOW_DELAY = 30 * 1000L; protected static final long NOSHOW_DELAY = 30 * 1000L;
/** The delay in milliseconds between ticking of all game managers. */ /** The delay in milliseconds between ticking of all game managers. */
+1 -1
View File
@@ -96,7 +96,7 @@
<!-- build the java class files --> <!-- build the java class files -->
<target name="compile" depends="prepare"> <target name="compile" depends="prepare">
<javac srcdir="${src.dir}" destdir="${classes.dir}" <javac srcdir="${src.dir}" destdir="${classes.dir}"
debug="on" optimize="off" deprecation="off"> debug="on" optimize="off" deprecation="on">
<classpath refid="classpath"/> <classpath refid="classpath"/>
<exclude name="com/threerings/**/tools/xml/**" unless="build.xml"/> <exclude name="com/threerings/**/tools/xml/**" unless="build.xml"/>
</javac> </javac>
@@ -21,6 +21,7 @@
package com.threerings.parlor; package com.threerings.parlor;
import com.threerings.crowd.client.PlaceController;
import com.threerings.parlor.game.client.GameConfigurator; import com.threerings.parlor.game.client.GameConfigurator;
import com.threerings.parlor.game.data.GameConfig; import com.threerings.parlor.game.data.GameConfig;
@@ -29,19 +30,24 @@ public class TestConfig extends GameConfig
/** The foozle parameter. */ /** The foozle parameter. */
public int foozle; public int foozle;
public int getGameId ()
{
return 0;
}
public String getGameIdent ()
{
return "test";
}
public GameConfigurator createConfigurator () public GameConfigurator createConfigurator ()
{ {
return null; return null;
} }
public Class getControllerClass () public PlaceController createController ()
{ {
return TestController.class; return new TestController();
}
public String getBundleName ()
{
return "test";
} }
public String getManagerClassName () public String getManagerClassName ()
@@ -21,14 +21,15 @@
package com.threerings.whirled; package com.threerings.whirled;
import com.threerings.crowd.client.PlaceController;
import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceConfig;
import com.threerings.whirled.server.SceneManager; import com.threerings.whirled.server.SceneManager;
public class TestConfig extends PlaceConfig public class TestConfig extends PlaceConfig
{ {
public Class getControllerClass () public PlaceController createController ()
{ {
return TestController.class; return new TestController();
} }
public String getManagerClassName () public String getManagerClassName ()