Client-server design
Server side
The server provides a number of remote interfaces that can be used externally
to access the White Orb world. Here is a summary of these interfaces
and how they are used.
Connecting to the server
Let's start from the beginning - there is a class called Server.
An instance of Server is registered in the RMI registry of the host, which
means any java program on the net can access this server at rmi://www.whiteorb.com/server
(or wherever it will be running). The server is an interface with
the following methods:
AccountAgent login(String name, String password);
AccountAgent loginAsGuest(String name);
AccountAgent createAccount(String name, String password);
So the server simply provides different ways of accessing an AccountAgent.
What is an AccountAgent?
This is a remote interface that represents a White Orb account. It
includes the following methods:
public void setClient(AccountClient client) throws RemoteException;
public ItemHeader[] getStartingLocations() throws RemoteException;
public TypeInfo[] getRaces() throws RemoteException;
public AccountRights getRights() throws RemoteException;
public void setRights(AccountRights rights) throws RemoteException;
public ItemHeader[] getSouls() throws RemoteException;
public MainAgent getBuilderAgent() throws RemoteException;
public MainAgent getPlayerAgent(ItemHeader soul) throws RemoteException;
public ItemHeader createCharacter(NewCharacterDef newChar) throws RemoteException;
public void deleteCharacter(ItemHeader character) throws RemoteException;
So the AccountAgent allows you to do stuff with your account - create new
characters, delete characters, etc. The server knows what rights
you have (AccountRights) so it will of course not allow you to create new
characters if you are only allowed one character, etc. When you decide
to use your character Fred you call the getPlayerAgent method and receive
a MainAgent.
What is a MainAgent?
This is the main remote interface, ie the one that allows you to interact
with the world (run around with characters, build items, pretty much anything.
It's quite large, but I'll list the methods anyway:
public void setClient(MainClient client)
public AllowedActions getAllowedActions(ItemHeader item)
public ItemHeader getWorld()
public long getFreeMemory()
public long getTotalMemory()
public void performGarbageCollection()
public PropertyAgent getPropertyAgent()
public EventAgent getEventAgent()
public MapAgent getMapAgent()
public AreaAgent getAreaAgent()
public void doAction(int id, Action action)
public ItemHeader createItem(ItemHeader location, Class itemType)
public ItemHeader createArea(String name, Dimension size, ItemHeader
background)
public void deleteItem(ItemHeader item)
Whether you are logged in as an admin, a builder, or a player you will
be using the MainAgent. As you see it is possible to get even more
"agents" from this interface, for example getEventAgent.
What are all these agents anyway?
An "agent" in White Orb is a remote interface that provides ways for external
objects (ie not located on the host) to see and manipulate the White Orb
world. Most agents have a specific purpose:
-
PropertyAgent: allows you to view the properties of a specific object (for
example a sword).
-
EventAgent allows you to monitor events received by a specific object
-
MapAgent allows you to monitor the "field of view" of an item (for example
yourself), ie all items currently visible
-
AreaAgent allows you to monitor the contents of complete Area
How does an agent "talk back" to a client?
The agent interfaces provide methods for use by clients. But how
does the agent tell something to the client?
Most agent interfaces have a setClient(...) method, for example EventAgent
provides the method setClient(EventClient client). The EventClient
interface has only method:
public void receiveEvent(GameEvent evt)
This is what the EventAgent will use to tell the EventClient what is happening.
Here is another example - the AccountClient interface (connected to an
AccountAgent):
public void soulAdded(ItemHeader soul) throws RemoteException;
public void soulRemoved(ItemHeader soul) throws RemoteException;
public void rightsChanged(AccountRights newRights) throws RemoteException;
The AccountAgent will use this to keep the AccountClient updated when changes
occur.
What is an ItemHeader?
If you look closely at the methods of the remote interfaces presented above,
you'll see that I use something called ItemHeader when referring to items.
This a very important class.
The ItemHeader is a remote representation of an item in the White Orb
world. It contains only the minimal information necessary: a name
to describe the item, an image, a class name (the class of the item that
this header represents), and most importantly an ID number that the server
can use to find the "real" item that this header represents.
So ItemHeaders are actual objects sent back and forth over the net,
representing a "slimmed version" of the real item. If the client
wants more info about a particular ItemHeader, it can use the MainAgent
interface to retreive a property - or even retreive a PropertyAgent that
will also monitor any changes to the item in question.
Why are ItemHeaders sent back to the agents? Isn't the ID enough?
Yeah, OK, I admit, you got me there. I was lazy. Soon I will
change the agent interfaces so that the agents only require an ID when
referring to an item. The agent is of course not interested in the
name and image and stuff like that, since it already knows all that - it
can find the original item as long as it gets the ID.
So in the near future all ItemHeader input parameters in the agent methods
will be changed to simple integer IDs. The output parameters will
remain unchanged, however (the client is not satisfied with only the ID).
Client side
Now you know how the server provides access to the world. Note that
the server doesn't really know anything at all about which type of client
is logged on - it could be a text terminal or pretty much anything for
all the server knows. That's good - it means we can create whatever
crazy clients we want in the future, without having to touch the server
code. Now I will describe my particular client design.
JavaBeans for agent communication
In order to make it easy to build and modify client GUIs I've created a
set of JavaBean GUI components that provide a higher level interface to
the Agents mentioned above. Here is a few of them:
-
ActionPanel - an input text-area that allows you to type actions.
It will parse this and use the MainAgent to execute the actions.
You simply select who the source of the actions should be.
-
AreaPanel - a panel that displays a complete Area as a tile grid.
Will connect itself to an AreaAgent.
-
EventPanel - a panel that displays events received by an item, using
text. You just specify which item is the listener, and the EventPanel
track events received using an EventAgent (what else...).
-
MemoryWatcher - an adminstration component that displays the memory
status of the host computer, and allows you to initiate garbage collection.
The MemoryWatcher simply uses the MainAgent methods.
-
PlayerViewPanel - an advanced component that displays the world
as seen by a certain item (usually a player character). If the player
is in a normal square, a tile-grid representing the player's field of view
will be displayed - it will scroll as the player walks around. If
the player is put in a sack or something like that, the GUI will change
itself to display the other items in the same location. If the player
dies a skull on a red background is displayed...
-
PropertyPanel - a very useful component that displays the properties
of any item. You can set which item should be displayed, and also
how it should be displayed (right now two styles are supported: tall and
wide).
Here is an example of a PropertyPanel and an EventPanel:
MainClients - putting the components together
A MainClient is a client GUI that puts together other components to provide
a single, coherent user interface. There are different clients for
different purposes. The PlayerClient is used to control a player:
And the BuilderClient is used to modify the world:
There are other clients as well, such as the AccountClient that gives
you access to your accounts (create new characters, open new clients, etc).
Or the AdminClient that gives you server usage statistics and lets you
see who is logged on, etc.
Tiles - the higher level representation of ItemHeaders
A Tile is simply an encapsulation of an ItemHeader that also keeps track
of whether or not it is selected (among other things). It will also
report changes to the image or name of the ItemHeader, and stuff like that.
There are currently two GUI components available for viewing a Tile: TileLabel
and TilePanel. TileLabel is simply a label showing the name of the
Tile. It will highlight itself when the Tile is selected, and will
also allow mouse click operations upon the tile (popup menu and stuff like
that). TilePanel is the same thing, but it display the icon image
instead of the name.
TilePanels and TileLabels are in other words the GUI representation
of an item in the White Orb world. Here is an example of a TilePanel
and a TileLabel:
Client context - maintaining shared resources
The client context provides the contextual "glue" that keeps all individual
components logically synchronized. Almost every client component
mentioned above will need a reference to the client context. Here
is a list of the most important functions:
-
Provide a global dictionary of Tiles. This is to avoid duplication
of tiles - even if TilePanels representing one specific sword are currently
shown in two different GUI components in the client, the TilePanels are
referring to the same Tile object. This means if you click on one
TilePanel to select the Tile, then all other TilePanels representing the
same item will also be highlighted. Makes things nice and consistent.
-
Provides ways of finding a Tile by name, or by partial name. Internally
it simply searches the Tile dictionary. This is used for example
when the ActionPanel is parsing your typed input and trying to figure out
which Tile you are actually referring to.
-
Provides a link to the MainAgent
-
Provides access to global GUI components such as the status bar, item info
bar, menu bar, and things like that.
-
Keep track of which Tile is currently selected, and makes sure only one
Tile is selected at a time (so if you select one tile the others are deselected).
-
Maintain a global image cache so that once an image has been drawn it stays
loaded in memory for next time it should be drawn.
Handling mouse clicks and popup menus - TileActionHandler
When the user clicks on a TileLabel or TilePanel or any other GUI representation
of a Tile, something will usually happen. The typical behavior is
that when you left-click the tile is selected, and when you right-click
a context sensitive popup menu is opened. Drag 'n drop will heavily
incorporated as soon JDK 1.2 is released.
Each component that displays Tiles will also include a TileActionHandler
that can be set from the outside. TileActionHandler is an interface
that looks like this:
public void tileClicked(MouseEvent event, Tile tile);
public void tileEntered(MouseEvent event, Tile tile);
public void tileExited(MouseEvent event, Tile tile);
public void tilePressed(MouseEvent event, Tile tile);
public void tileReleased(MouseEvent event, Tile tile);
As you see, it mimics the MouseListener interface. Each component
that displays tiles will provide a default TileActionHandler with the behaviour
I described above, but in some cases a new handler might want to be used.
In a builder client, for example, left-click might mean "create new item"
under certain circumstances. Some components may also want to modify
the popup menu and add options of it's own, since the popup menu by default
only contains server-side actions.
Note that the concept of "tile" is much broader than just labels and
icon images. The EventPanel, for example, will use tiles as well
(although it doesn't yet) - so if it says "Joe says hi" then you can click
on the word "Joe" just as if it was a TilePanel or TileLabel. The
EventPanel will keep track of which Tiles are represented by which words.
Image and graphics handling - now and in the future
The ClientContext provides methods for retreiving the actual Image object
for any given image filename. On the whole the image handling is
very simple right now and will be improved in the future.
First of all, images will be referred to by ID instead of filename.
The server will contain a global ResourcePool or something like that which
will map the IDs to the actual images. The client will get access
to this resource pool, probably by maintaining a copy of it's own that
is updated by the server. In the long run the resource pool will
also provide access to things like sounds, animations, and other fun stuff.
Right now each item is associated with a single .gif file containing
it's icon. In the future this will be expanded so that the .gif file
can contain many different icon representations of the same item in different
situations (chest open, chest closed, Orc sleeping, Orc dead, etc).
All in all the whole concept of how an item is graphically represented
will be encapsulated in higher-level display managers. No filenames
will be directly referenced, each file will be provided via the ResourcePool
and the contents of each file will also be indexed using some kind of FileInformation
object - so that .gif files can contain more than one image without complicating
the game.
This means the client will be able to dynamically update the graphical
representation of an item, and will also perform animations when certain
events happen. As usual, the client itself will be pretty "dumb".
Instead, the display managers provided by the items on the server will
listen for events and request animations to be performed when necessary.
Henrik
Kniberg
Last updated: