Table creation refactor: Rather than have the game config specify

table configuration parameters that are unneeded once the actual game starts,
and requiring each GameConfigurator to duplicate the functionality
of standard table setup, I separated TableConfig from GameConfig.
If a game wants to be usable in a table matchmaking service, it
implements TableableGameConfig, and returns the TableConfigurator to use.
There is a default TableConfigurator implementation that just does
everything needed. TableConfigurator returns a happily customized
TableConfig, which is sent to the server with the GameConfig in order to
create the Table.
More refactoring needs to happen to get the Party stuff in line, but
right now that's all a mess since we're still supporting the old-style
party interface for the old drinking puzzle in yohoho.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3525 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2005-04-26 02:38:42 +00:00
parent 2351316259
commit 052cf8e0e2
16 changed files with 265 additions and 190 deletions
@@ -1,69 +0,0 @@
//
// $Id: TableGameConfigurator.java,v 1.3 2004/08/27 02:12:51 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.micasa.lobby.table;
import com.samskivert.swing.SimpleSlider;
import com.samskivert.swing.VGroupLayout;
import com.threerings.parlor.client.GameConfigurator;
import com.threerings.parlor.data.TableConfig;
/**
* Extends the basic game configurator with elements to support the
* configuration of standard table game configurations.
*/
public class TableGameConfigurator extends GameConfigurator
{
// documentation inherited
protected void createConfigInterface ()
{
// create a slider and associated label for configuring the number
// of people at the table
add(_pslide = new SimpleSlider("Seats:", 0, 10, 0), VGroupLayout.FIXED);
// TODO: get a message bundle and translate "Seats"
}
// documentation inherited
protected void gotGameConfig ()
{
TableConfig tconfig = (TableConfig)_config;
// configure our slider
_pslide.setMinimum(tconfig.getMinimumPlayers());
_pslide.setMaximum(tconfig.getMaximumPlayers());
_pslide.setValue(tconfig.getDesiredPlayers());
// if the min == the max, hide the slider because it's pointless
_pslide.setVisible(tconfig.getMinimumPlayers() !=
tconfig.getMaximumPlayers());
}
// documentation inherited
protected void flushGameConfig ()
{
TableConfig tconfig = (TableConfig)_config;
tconfig.setDesiredPlayers(_pslide.getValue());
}
/** Our number of players slider. */
protected SimpleSlider _pslide;
}
@@ -1,5 +1,5 @@
//
// $Id: TableItem.java,v 1.5 2004/08/27 02:12:51 mdb Exp $
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -41,7 +41,6 @@ import com.threerings.crowd.data.BodyObject;
import com.threerings.parlor.client.TableDirector;
import com.threerings.parlor.client.SeatednessObserver;
import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.micasa.Log;
import com.threerings.micasa.util.MiCasaContext;
@@ -73,9 +72,6 @@ public class TableItem
// figure out who we are
_self = ((BodyObject)ctx.getClient().getClientObject()).username;
// grab the table config reference
_tconfig = (TableConfig)table.config;
// now create our user interface
setBorder(BorderFactory.createLineBorder(Color.black));
setLayout(new GridBagLayout());
@@ -88,10 +84,7 @@ public class TableItem
add(tlabel, gbc);
// we have one button for every "seat" at the table
int bcount = _tconfig.getDesiredPlayers();
if (bcount == -1) {
bcount = _tconfig.getMaximumPlayers();
}
int bcount = table.occupants.length;
// create blank buttons for now and then we'll update everything
// with the current state when we're done
@@ -243,9 +236,6 @@ public class TableItem
/** A reference to our table director. */
protected TableDirector _tdtr;
/** A casted reference to our table config object. */
protected TableConfig _tconfig;
/** We have a button for each "seat" at the table. */
protected JButton[] _seats;
@@ -21,6 +21,7 @@
package com.threerings.micasa.lobby.table;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
@@ -44,7 +45,9 @@ import com.threerings.parlor.client.SeatednessObserver;
import com.threerings.parlor.client.TableDirector;
import com.threerings.parlor.client.TableObserver;
import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfigurator;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.game.data.TableableGameConfig;
import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
@@ -97,6 +100,10 @@ public class TableListView extends JPanel
GameConfig gconfig = null;
try {
gconfig = config.getGameConfig();
_tableFigger =
((TableableGameConfig) gconfig).createTableConfigurator(_ctx);
panel.add((Component) _tableFigger, VGroupLayout.FIXED);
_figger = gconfig.createConfigurator();
if (_figger != null) {
_figger.init(_ctx);
@@ -222,7 +229,8 @@ public class TableListView extends JPanel
{
// the create table button was clicked. use the game config as
// configured by the configurator to create a table
_tdtr.createTable(_figger.getGameConfig());
_tdtr.createTable(_tableFigger.getTableConfig(),
_figger.getGameConfig());
}
// documentation inherited
@@ -276,7 +284,10 @@ public class TableListView extends JPanel
/** The list of tables that are in play. */
protected JPanel _playList;
/** The interface used to configure a table before creating it. */
/** The interface used to configure the table for a game. */
protected TableConfigurator _tableFigger;
/** The interface used to configure a game before creating it. */
protected GameConfigurator _figger;
/** Our create table button. */
@@ -0,0 +1,107 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.parlor.client;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import com.samskivert.swing.SimpleSlider;
import com.samskivert.swing.VGroupLayout;
import com.threerings.parlor.data.TableConfigurator;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.util.ParlorContext;
/**
* Provides a default implementation of a TableConfigurator for
* a Swing interface.
*/
public class DefaultSwingTableConfigurator extends JPanel
implements TableConfigurator
{
/**
* Create a TableConfigurator that allows only the specified number
* of players and lets the configuring user enable private games
* only if the number of players is greater than 2.
*/
public DefaultSwingTableConfigurator (ParlorContext ctx, int players)
{
this(ctx, players, (players > 2));
}
/**
* Create a TableConfigurator that allows only the specified number
* of players and lets the user configure a private table, or not.
*/
public DefaultSwingTableConfigurator (ParlorContext ctx, int players,
boolean allowPrivate)
{
this(ctx, players, players, players, allowPrivate);
}
/**
* Create a TableConfigurator that allows for the specified configuration
* parameters.
*/
public DefaultSwingTableConfigurator (ParlorContext ctx, int minPlayers,
int desiredPlayers, int maxPlayers, boolean allowPrivate)
{
super(new VGroupLayout()); // TODO: layout improvement?
// TODO: translations
_playerSlider = new SimpleSlider(
"Seats:", minPlayers, maxPlayers, desiredPlayers);
_privateCheck = new JCheckBox("Private?:");
// figure out what to actually show
if (minPlayers != maxPlayers) {
add(_playerSlider);
}
if (allowPrivate) {
add(_privateCheck);
}
}
// documentation inherited from interface TableConfigurator
public boolean isEmpty ()
{
return (getComponentCount() == 0);
}
// documentation inherited from interface TableConfigurator
public TableConfig getTableConfig ()
{
TableConfig tconfig = new TableConfig();
tconfig.desiredPlayerCount = _playerSlider.getValue();
tconfig.privateTable = _privateCheck.isSelected();
return tconfig;
}
/** A slider for configuring the number of players at the table. */
protected SimpleSlider _playerSlider;
/** A checkbox to allow the table creator to specify if the table is
* private. */
protected JCheckBox _privateCheck;
}
@@ -26,6 +26,8 @@ import com.threerings.util.Name;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.game.data.GameConfig;
/**
@@ -115,12 +117,13 @@ public interface ParlorService extends InvocationService
* @param client a connected, operational client instance.
* @param lobbyOid the oid of the lobby that will contain the newly
* created table.
* @param tableConfig the table configuration parameters.
* @param config the game config for the game to be matchmade by the
* table.
* @param listener will receive and process the response.
*/
public void createTable (Client client, int lobbyOid, GameConfig config,
TableListener listener);
public void createTable (Client client, int lobbyOid,
TableConfig tableConfig, GameConfig config, TableListener listener);
/**
* You probably don't want to call this directly, but want to call
@@ -36,6 +36,7 @@ import com.threerings.crowd.data.PlaceObject;
import com.threerings.parlor.Log;
import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.data.TableLobbyObject;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.util.ParlorContext;
@@ -146,7 +147,7 @@ public class TableDirector extends BasicDirector
* will be added to the first position in the table. The response will
* be communicated via the {@link TableObserver} interface.
*/
public void createTable (GameConfig config)
public void createTable (TableConfig tableConfig, GameConfig config)
{
// if we're already in a table, refuse the request
if (_ourTable != null) {
@@ -163,7 +164,8 @@ public class TableDirector extends BasicDirector
}
// go ahead and issue the create request
_pservice.createTable(_ctx.getClient(), _lobby.getOid(), config, this);
_pservice.createTable(_ctx.getClient(), _lobby.getOid(), tableConfig,
config, this);
}
/**
@@ -22,6 +22,7 @@
package com.threerings.parlor.data;
import com.threerings.parlor.client.ParlorService;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
@@ -118,12 +119,12 @@ public class ParlorMarshaller extends InvocationMarshaller
public static final int CREATE_TABLE = 2;
// documentation inherited from interface
public void createTable (Client arg1, int arg2, GameConfig arg3, ParlorService.TableListener arg4)
public void createTable (Client arg1, int arg2, TableConfig arg3, GameConfig arg4, ParlorService.TableListener arg5)
{
ParlorMarshaller.TableMarshaller listener4 = new ParlorMarshaller.TableMarshaller();
listener4.listener = arg4;
ParlorMarshaller.TableMarshaller listener5 = new ParlorMarshaller.TableMarshaller();
listener5.listener = arg5;
sendRequest(arg1, CREATE_TABLE, new Object[] {
new Integer(arg2), arg3, listener4
new Integer(arg2), arg3, arg4, listener5
});
}
+28 -46
View File
@@ -21,10 +21,6 @@
package com.threerings.parlor.data;
import java.io.IOException;
import com.threerings.io.ObjectInputStream;
import com.samskivert.util.StringUtil;
import com.threerings.util.Name;
@@ -52,17 +48,19 @@ public class Table
public int gameOid = -1;
/** An array of the usernames of the occupants of this table (some
* slots may not be filled). */
* slots may not be filled), or null if a party game. */
public Name[] occupants;
/** The body oids of the occupants of this table. (This is not
* propagated to remote instances.) */
/** The body oids of the occupants of this table, or null if a party game.
* (This is not propagated to remote instances.) */
public transient int[] bodyOids;
/** The game config for the game that is being matchmade. This config
* instance will also implement {@link TableConfig}. */
/** The game config for the game that is being matchmade. */
public GameConfig config;
/** The table configuration object. */
public TableConfig tconfig;
/**
* Creates a new table instance, and assigns it the next monotonically
* increasing table id. The supplied config instance must implement
@@ -74,7 +72,7 @@ public class Table
* @param config the configuration of the game being matchmade by this
* table.
*/
public Table (int lobbyOid, GameConfig config)
public Table (int lobbyOid, TableConfig tconfig, GameConfig config)
{
// assign a unique table id
tableId = new Integer(++_tableIdCounter);
@@ -83,18 +81,20 @@ public class Table
this.lobbyOid = lobbyOid;
// keep a casted reference around
_tconfig = (TableConfig)config;
this.tconfig = tconfig;
this.config = config;
// make room for the maximum number of players
occupants = new Name[_tconfig.getMaximumPlayers()];
bodyOids = new int[occupants.length];
if (tconfig.desiredPlayerCount != -1) {
occupants = new Name[tconfig.desiredPlayerCount];
bodyOids = new int[occupants.length];
// fill in information on the AIs
int acount = (config.ais == null) ? 0 : config.ais.length;
for (int ii = 0; ii < acount; ii++) {
// TODO: handle this naming business better
occupants[ii] = new Name("AI " + (ii+1));
// fill in information on the AIs
int acount = (config.ais == null) ? 0 : config.ais.length;
for (int ii = 0; ii < acount; ii++) {
// TODO: handle this naming business better
occupants[ii] = new Name("AI " + (ii+1));
}
}
}
@@ -104,18 +104,6 @@ public class Table
public Table ()
{
}
/**
* Extends default behavior to initialize transient members.
*/
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
// initialize casted reference
_tconfig = (TableConfig)config;
}
/**
* A convenience function for accessing the table id as an int.
@@ -134,6 +122,10 @@ public class Table
*/
public Name[] getPlayers ()
{
if (occupants == null) {
return null;
}
// count up the players
int pcount = 0;
for (int i = 0; i < occupants.length; i++) {
@@ -168,15 +160,8 @@ public class Table
*/
public String setOccupant (int position, Name username, int bodyOid)
{
// find out how many positions we have for occupation
int maxpos = _tconfig.getDesiredPlayers();
// if there is no desired number of players, use the max
if (maxpos == -1) {
maxpos = _tconfig.getMaximumPlayers();
}
// make sure the requested position is a valid one
if (position >= maxpos || position < 0) {
if (position >= tconfig.desiredPlayerCount || position < 0) {
return INVALID_TABLE_POSITION;
}
@@ -245,8 +230,11 @@ public class Table
*/
public boolean mayBeStarted ()
{
// TODO: this becomes more meaningful if we implement games
// that can optionally be started with less than the desired number
// make sure at least the minimum number of players are here
int want = _tconfig.getMinimumPlayers(), have = 0;
int want = tconfig.desiredPlayerCount, have = 0;
for (int i = 0; i < occupants.length; i++) {
if (occupants[i] != null) {
if (++have == want) {
@@ -263,10 +251,7 @@ public class Table
*/
public boolean shouldBeStarted ()
{
int need = _tconfig.getDesiredPlayers();
if (need == -1) {
need = _tconfig.getMaximumPlayers();
}
int need = tconfig.desiredPlayerCount;
for (int i = 0; i < occupants.length; i++) {
if (occupants[i] != null && --need == 0) {
return true;
@@ -329,9 +314,6 @@ public class Table
", config=" + config + "]";
}
/** A casted reference of our game config object. */
protected transient TableConfig _tconfig;
/** A counter for assigning table ids. */
protected static int _tableIdCounter = 0;
}
@@ -21,43 +21,18 @@
package com.threerings.parlor.data;
import com.threerings.io.SimpleStreamableObject;
/**
* A game config object that is to be matchmade using the table services
* should implement this interface so that the table services can extract
* the necessary table-generic information.
* Table configuration parameters for a game that is to be matchmade
* using the table services.
*/
public interface TableConfig
public class TableConfig extends SimpleStreamableObject
{
/**
* Returns the minimum number of players needed to start the game.
*/
public int getMinimumPlayers ();
/** The number of players that are desired for the table, or -1 for a
* party game. */
public int desiredPlayerCount;
/**
* Returns the maximum number of players that can play in the game.
*/
public int getMaximumPlayers ();
/**
* Returns the number of players that when reached, will cause the
* game to automatically be started. If this value is -1 the game will
* not automatically be started until the maximum number of players is
* reached.
*/
public int getDesiredPlayers ();
/**
* Sets the desired number of players to the specified value.
*/
public void setDesiredPlayers (int desiredPlayers);
/**
* Checks whether or not this is a configuration for a private table.
*/
public boolean isPrivateTable ();
/**
* Sets whether nor not this is a configuration for a private table.
*/
public void setPrivateTable (boolean privateTable);
/** Whether the table is "private". */
public boolean privateTable;
}
@@ -0,0 +1,42 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.parlor.data;
/**
* This should be implemented some user-interface element that allows
* the user to configure whichever TableConfig options are relevant.
*/
public interface TableConfigurator
{
/**
* If true, the tableConfigurator is empty, useful if the lobby code
* can skip the step of letting the table creator configure things
* if the game configurator is also empty.
*/
public boolean isEmpty ();
/**
* Return the fully configured table config when the user is ready
* to create their table.
*/
public TableConfig getTableConfig ();
}
@@ -21,12 +21,10 @@
package com.threerings.parlor.game.data;
import com.threerings.parlor.data.TableConfig;
/**
* Provides additional information for party games.
*/
public interface PartyGameConfig extends TableConfig
public interface PartyGameConfig
{
/**
* Returns true if this party game is being played in party game mode,
@@ -0,0 +1,38 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.parlor.game.data;
import com.threerings.parlor.data.TableConfigurator;
import com.threerings.parlor.util.ParlorContext;
/**
* An interface to be implemented by GameConfigs if they want to be
* able to use Table matchmaking services.
*/
public interface TableableGameConfig
{
/**
* Called when this GameConfig is to be used for Table matchmaking,
* returns the TableConfigurator to be used to configure the Table.
*/
public TableConfigurator createTableConfigurator (ParlorContext ctx);
}
@@ -26,7 +26,7 @@ import com.threerings.parlor.data.TableConfig;
/**
* Provides additional information for games with teams.
*/
public interface TeamGameConfig extends TableConfig
public interface TeamGameConfig
{
/**
* Returns the members of each team. For instance, a game with three
@@ -23,6 +23,7 @@ package com.threerings.parlor.server;
import com.threerings.parlor.client.ParlorService;
import com.threerings.parlor.data.ParlorMarshaller;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
@@ -68,7 +69,7 @@ public class ParlorDispatcher extends InvocationDispatcher
case ParlorMarshaller.CREATE_TABLE:
((ParlorProvider)provider).createTable(
source,
((Integer)args[0]).intValue(), (GameConfig)args[1], (ParlorService.TableListener)args[2]
((Integer)args[0]).intValue(), (TableConfig)args[1], (GameConfig)args[2], (ParlorService.TableListener)args[3]
);
return;
@@ -37,6 +37,7 @@ import com.threerings.parlor.Log;
import com.threerings.parlor.client.ParlorService.InviteListener;
import com.threerings.parlor.client.ParlorService.TableListener;
import com.threerings.parlor.data.ParlorCodes;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.game.server.GameManager;
@@ -113,7 +114,7 @@ public class ParlorProvider
* Processes a request from the client to create a new table.
*/
public void createTable (ClientObject caller, int lobbyOid,
GameConfig config, TableListener listener)
TableConfig tableConfig, GameConfig config, TableListener listener)
throws InvocationException
{
Log.info("Handling create table request [caller=" + caller.who() +
@@ -121,7 +122,7 @@ public class ParlorProvider
// pass the creation request on to the table manager
TableManager tmgr = getTableManager(lobbyOid);
int tableId = tmgr.createTable((BodyObject)caller, config);
int tableId = tmgr.createTable((BodyObject)caller, tableConfig, config);
listener.tableCreated(tableId);
}
@@ -84,6 +84,7 @@ public class TableManager
* will be created.
*
* @param creator the body object that will own the new table.
* @param tableConfig the configuration parameters for the table.
* @param config the configuration of the game to be created.
*
* @return the id of the newly created table.
@@ -92,18 +93,10 @@ public class TableManager
* not able processed for some reason. The explanation will be
* provided in the message data of the exception.
*/
public int createTable (BodyObject creator, GameConfig config)
public int createTable (BodyObject creator, TableConfig tableConfig,
GameConfig config)
throws InvocationException
{
// make sure the game config implements TableConfig
if (!(config instanceof TableConfig)) {
Log.warning("Requested to matchmake a non-table game " +
"using the table services [creator=" + creator +
", lobby=" + _plobj.getOid() +
", config=" + config + "].");
throw new InvocationException(INTERNAL_ERROR);
}
// make sure the creator is an occupant of the lobby in which
// they are requesting to create a table
if (!_plobj.occupants.contains(creator.getOid())) {
@@ -114,7 +107,7 @@ public class TableManager
}
// create a brand spanking new table
Table table = new Table(_plobj.getOid(), config);
Table table = new Table(_plobj.getOid(), tableConfig, config);
// stick the creator into the first non-AI position
int cpos = (config.ais == null) ? 0 : config.ais.length;