This document describes canonically how to create a cockpit resource file. Hopefully it'll be easy to read, too.
This is only fully implemented in the latest unreleased development versions. There's a snapshot of this article that describes the features implemented in 0.3.0
A Cockpit—The Concept
The cockpit is where you're sitting when you play the game. There is the concept of a virtual cockpit which is implemented in the internal camera view, but we'll ignore that, for the most part. Otherwise, the cockpit is where you're sitting and what you're using.
A cockpit resource file provides the layout of some of the visual items you see while you're playing. Traditionally, these were referred to as the HUD, and the HUD is still the main information that's carried in a cockpit file.
In the resource file, gauges and other things have been abstracted to a Widget. Each Widget is capable of displaying all of the information you're used to seeing, and is likely capable of displaying the same information in different formats...thanks to the cockpit resource file.
Overview of the Cockpit file
The cockpit file consists of two sections. Technically they're just one, but you don't really have to worry about that. The two sections are pretty simple. The first just hooks it into the resource system for the whole game, using tags and stuff you may already know from the map files. The second section contains all the beautful cockpit stuff.
In the Cockpit section you'll find a series of widget tags that contain data tags to hook the widget into some game data. There's a caption, some position and size parameters, and so forth. You can also specify background and foreground colors, with gradiants, and with graphics, to provide a really custom gauge. Additionally, widgets can be associated with a camera, or made for all cameras, with the option of excluding specific cameras, offering the possibility to give a completely custom view for each camera you use. All of this gets mixed together and rendered to screen as your cockpit.
A complete gauge, in concept, isn't necessarily a single widget in the cockpit file. As an example, you might want to build a "Performance" gauge that includes ping, frames per second, cpu usage, overall system load, and bandwidth usage. That would require placing several widgets together, stacked on top of each other or on each other's sides, to build the conceptual Performance gauge you intend. Here, we'll use "gauge" to refer to a specific gauge and "widget" to refer to the larger conceptual gauge. Most widgets that you'll build will just be gauges, so it's not a big deal. (Note: At the time of writing, all of the data in the hypothetical Performance gauge isn't available to the cockpit, but the system is easily extensible and there's no reason to think it will never be available)
Within each gauge there are a series of tags that are used to define what it looks like, what data it uses, and so forth. The tags that are available vary from gauge to gauge, but the meaning of each tag never varies. Two gauges that are diametrically opposite to each other might interpret the tag differently, but it will still mean the same thing for both gauges. That's an important subtlety that you should keep in mind.
Construction of a Gauge
Each gauge is a layered affair. In technical terms, first the back of the gauge is painted. Then the middle is painted on top of the back, and finally the front is painted on top of the middle. This is intended to allow for complete configurability for each gauge. The middle layer is always the representation of the data, whatever it is. The back and front are equivalent in size and shape and are intended for window-dressing. By painting a texture with alpha-blended pixels it should be possible to reshape parts of the gauge and give it a unique appearance. Two gauges can be identical in all ways except which texture is used for the front and have very different appearances to the user.
Gauges that are text-driven are generally constructed on a table. While you don't have to use a table to build the gauge, the table allows you to organize it in rows and columns, which is the most useful way to organize data that you expect to read only in glances.
Each gauge has to specify which piece of data it displays, along with related pieces of data, and how to display them. This can vary dramatically! As an example, on the old HUD, in the rubber gauge, you saw a needle with some text to show the amount of rubber currently used. Then you had a minimum, which was 0, and a maximum, which varied from server to server. In the new rubber gauge you'll choose an appropriate widget and tell it where to get its minimum, its maximum, and its current data. Technically this is implemented with callbacks so that any data can be made available to be used by a gauge, even if it doesn't make sense to do so, and the callback name to use is what you'll give it in the cockpit file. If that doesn't make sense to you, don't worry about it. Just make sure to provide what's needed to show your gauge and be happy.
Every gauge supports an attribute called "camera". The camera attribute allows you to specify for which camera the gauge will appear. This in turn allows your cockpit to look/feel/be completely different depending on the user's camera. The primary purpose is to allow the internal camera to actually appear like you're inside the light cycle while providing minimal HUDs for the other cameras, and it's this purpose from which the name "cockpit" is derived. But you shouldn't let that block your creativity when you're creating your own nice cockpit. Possible cameras are:
follow—Following camera (the one that follows the cycle, but doesn't turn by itself)
in—Incam and the autoincam of the smartacm if enabled
server_custom—Server–defined custom camera
smart—Smartcam, default camera
mer—The new mercam
*—All cameras at once (default if you omit the attribute)
The camera attribute can contain multiple cameras separated by spaces, if it starts with a
^ the widget will appear in all cameras but the ones specified. Examples:
camera="in"—Only show in incam mode
camera="^in free"—Don't show in in– or freecam mode, but in everything else
camera="^all"—Never show, kinda pointless.
Finally all gauges support the
viewport attribute. Possible values are “top”, “cycle” and “all”, the latter being the default. “top” means that the widget will be rendered on top of the screen, “all” means it will be rendered for every player in multiplayer mode. Some data sources are only available in “all” mode, but you might not want a separate map for each player, so choose “top” then.
“cycle” is a special viewport, it means that the widget will be rendered over every cycle, just like the label (name). The orign of the coordinate system will be right above the centre of the label in this case.
General Gauge Information
Every gauge supports an interpretation of some tags that are needed for every gauge. We'll discuss those here.
The DataSet tag is a container that contains AtomicData tags. It defines all of the data that will be used to construct the gauge and may contain from 1 to 3 AtomicData tags within it. Here's an example:
<DataSet> <AtomicData field="source" source="player_speed" /> <AtomicData field="minimum" source="0" /> <AtomicData field="maximum" source="max_speed" /> </DataSet>
As you can see, each AtomicData tag contains two attributes, field and source. Field may be any of source, minimum, or maximum. Source determines where the data will be retrieved. If the contents of source match up with a known location of data, then that location will be called. If not, then the contents of source will be the data. This is easier to understand by just reading the snippet given, where "player_speed" will put the player's cycle's current speed in the field "source", but it will just put "0" in the field "minimum". This snippet is a speed gauge, and you can easily visualize a needle with 0 on the left, "max_speed" on the right, and the player's current speed marked by the needle itself as on the old HUD.
A DataSet can have an id associated with it that you can use to refer to it later for presentation. This is most important in a Label. Indeed, it's required in a Label but it doesn't really make sense in a NeedleGauge, does it?
Possible contents for data sources
Those can be whole numbers or fractions. Examples:
Any text, example:
Any config items as you would type them on the console to get their value:
These values are replaced by actual data from the game. Here's a complete list of possibilities:
|player_rubber||The rubber that's used for the player currently being watched. Ranges from 0 to CYCLE_RUBBER.|
|current_ping||The ping time in milliseconds for the client. Note, in order for this to work, it needs to be assigned to all viewports, ie: viewport="all".|
|player_speed||The speed of the cycle currently being watched.|
|max_speed||The theorethical maximum of the speed a cycle can reach according to the current settings.|
|player_brakes||The amount of brakes left. Ranges from 0 to 1.|
|enemies_alive||The number of enemies alive, from the view of the player the viewport belongs to.|
|friends_alive||The number of teammates alive, from the view of the player the viewport belongs to.|
|current_framerate||The current framerate in frames per second.|
|current_acceleration||The current cycle acceleration in m/s^2. Note, this is instantaneous acceleration, not average acceleration.|
|time_since_start||The time the game is running in seconds.|
|current_minutes||The time of the day in minutes.|
|current_hours||The time of the day in hours.|
|current_hours12h||The time of the day in hours (12- hour format).|
|current_seconds||The time of the day in seconds.|
|current_score||The score of the player the viewport belongs to.|
|top_score||The highest score.|
|top_other_score||The highest score other than the currently watched cycle.|
|current_score_team||The team score of the currently watched cycle.|
|top_score_team||The highest team score.|
|top_other_score_team||The highest team score other than the currently watched cycle.|
|fastest_speed||The speed of the fastest cycle.|
|fastest_name||The name of the player that's fastest right now.|
|fastest_speed_round||The speed of the fastest cycle during the whole round. This doesn't have to be the fastest cycle on the grid, it is just the hightest “peak”.|
|fastest_name_round||The name of the player that reached the hightest speed during the round.|
|time_to_impact_front||The approximate time it will take the cycle to reach a wall in front of it. This source is stupid, it has no prediction, so if it returns 3 seconds and someone drives in front of you in that time it will jump|
|time_to_impact_right||Same as time_to_impact_front, but on the right side (if you were to turn in that direction without loss of speed)|
|time_to_impact_left||Same as time_to_impact_front, but on the left side (if you were to turn in that direction without loss of speed)|
|current_song||The filepath to the song that's currently played. Should be altered to display the title instead.|
|current_name||The name of the player that's currently being watched|
|current_colored_name||The name of the player that's currently being watched, in the correct color|
|current_pos_x||The position of the player in grid units, x- coordinate|
|current_pos_y||The position of the player in grid units, y- coordinate|
Position and Size
Position and size are given by two tags, aptly "Position" and "Size". The point given by position represents the center of the gauge. Don't forget this! It's common for the position point of a widget to be the top left corner, but that is not the case. Here, it's the center of the gauge. An example says everything that needs to be said:
<Position x="-0.165" y="-0.9" /> <Size height="0.15" width="0.15" />
Positions and sizes are not in pixels, but in units of a special coordinate plane. The point at the bottom left is always (-1, -1) and the point at the bottom right is always (1, -1). So the x- coordinate always goes from -1 (left) to 1 (right).
The y- coordinate is chosen in a way so x and y have the same scale. (-1, 1) would refer to the point that is (width of your screen) up from the bottom edge. Of course, for most screens this means this point is off the screen.
This is an illustration of the coordinate system:
Besides determining where the data should come from for a given field it is also necessary to tell the gauge whether or not to show that field. It's important to understand the distinction between using the data to draw the gauge and displaying the data on the gauge. The canonical example is the brake gauge displayed as a bar. In this example, minimum is always 0 and maximum is always 1, and current is always something in between. In order to compute the size of the gauge for the purpose of drawing the bar, you must have AtomicData for all three fields as discussed above. But you may not want to present that data. Maybe you just want to see the bar and nothing else. In that case, you need to deal with the field flags. How to actually use them is best seen in an example:
<ShowCurrent value="true" /> <ShowMaximum value="true" /> <ShowMinimum value="true" />
Sometimes it's better to show a gauge the other way, ie the maximum on the left instead of on the right. This is how it's done:
<Reverse value="true" />
Every gauge can have a caption. The caption need not be displayed. Again, an example says everything that needs to be said:
<Caption location="bottom"> <Text value="Speed" /> </Caption>
Possible locations are top, bottom, and off. Location of "off" means the caption won't be displayed.
As previously discussed, each gauge is drawn with three layers. So far we've only talked about what is shown in the middle layer, the data layer. That is the gauge itself. In addition to the middle layer you have the background and foreground layers in which you can draw, and they are done with the aptly named Background and Foreground tags. You can put textures, solid colors, and gradients, or just leave the tag out completely if you don't need it. Here are some examples:
- A solid foreground color, red, with no alpha transparency.
<Foreground> <Solid> <Color r="1." g="0." b="0." alpha="1." /> </Solid> </Foreground>
- A lightly transparent 3-color gradient. The "at" parameter marks the borders between the colors and ranges from 0 to 1. The orientation determines how the gradient will be drawn.
<Foreground> <Gradient orientation="horizontal"> <Color r="0." g="1." b="1." alpha=".7" at="0" /> <Color r="0." g="0." b="1." alpha=".7" at=".5" /> <Color r="1." g="0." b="1." alpha=".7" at="0.8" /> </Gradient> </Foreground>
- There are three different types of gradients, indicated by the orientation attribute. Possible values are "horizontal" and "vertical" which will make horizontal or vertical gradients. A special value is "value" which will result in a solid area which changes color according to the value of the displayed data. The color with at="0" will be used at the minimum value and at="1" will be used at the maximum.
There's some basic support for textures in recent builds. A texture can be added to any
<Background /> tag, either instead or before the color or even gradient. Although you can specify a texture for all gauges that support foregrounds/backgrounds they only make sense for widgets that render more than just lines :-)
Here's an example of a working texture:
<Background> <Image scale_x="1" scale_y="1" repeat="both"> <Graphic category="" author="wrtlprnft" version="1" name="wood_512" extension="jpg" uri="http://wrtlprnft.ath.cx/wood512.jpg" /> </Image> <Solid> <Color r="1." g="1." b="1." alpha=".5" /> </Solid> </Background>
<Image /> tag supports the following attributes:
- The scale factor for the texture. If both are set to
1the texture is stretched so it covers the entire area of the widget.
- Can be one of
both. This specifies in which directions the texture will repeat itself. The area not covered by the image will be filled by the last row/column of the texture. Usually you'll want
both, so this is the default.
<Graphic /> tag contains information on where to find the texture. The attrubutes
author (default: the author of the cockpit),
category (default:the category of the cockpit),
png) specify the resource path of the texture,
<author>/<category>/<name>-<version>.<aatex>.<extension>. If the attribute
uri is given the file will be downloaded from the given URI instead of the resource repository if it's not already cached.
If there's a
<Color /> or
<Gradient /> tag after the
<Image /> tag the color at each pixel of the texture will be multiplied by the color at the corresponding point of the gradient (or the single color given). In the example this is used to make a semitransparent background image.
General advice on textures: Make sure the dimensions of every image you want are in powers of two, for example 64×64, 256×256, 512×512 or 1024×1024. Rectangular images are OK, too, so you can use 32×1024 images, too, if you really want to. Images of other dimensions will be scaled to be powers of two by OpenGL, which doesn't excactly lead to pretty results.
Here are all of the available gauges, along with which tags are supported.
A needle gauge requires all three fields to exist in the DataSet tag. It supports all general tags. Here's an example speed gauge:
<NeedleGauge camera="*"> <DataSet> <AtomicData field="source" source="player_speed" /> <AtomicData field="minimum" source="0" /> <AtomicData field="maximum" source="max_speed" /> </DataSet> <Position x="-0.165" y="-0.9" /> <Size height="0.15" width="0.15" /> <ShowCurrent value="true" /> <ShowMaximum value="true" /> <ShowMinimum value="true" />
<Text value="Speed" /> <Foreground> <Solid> <Color r="1." g="0." b="0." alpha="1." /> </Solid> </Foreground> </NeedleGauge>
A bar gauge requires all three fields to exist in the DataSet tag. It supports all general tags. Here's an example rubber gauge:
<BarGauge camera="^in"> <DataSet> <AtomicData field="source" source="player_rubber" /> <AtomicData field="minimum" source="0" /> <AtomicData field="maximum" source="cycle_rubber" /> </DataSet> <Position x="-0.55" y="-0.9" /> <Size height="0.05" width="0.15" /> <ShowCurrent value="true" /> <ShowMaximum value="true" /> <ShowMinimum value="true" />
<Text value="Rubber" /> <Background> <Gradient orientation="value"> <Color r="0." g="1." b="0." alpha=".0" at="0." /> <Color r="0." g="1." b="0." alpha=".1" at=".3" /> <Color r="1." g="1." b="1." alpha=".2" at=".4" /> <Color r="1." g="1." b="1." alpha=".2" at="1." /> </Gradient> </Background> <Foreground> <Gradient orientation="value"> <Color r="0." g="1." b="0." alpha=".7" at="0." /> <Color r="0." g="1." b="0." alpha=".7" at=".3" /> <Color r="1." g="1." b="0." alpha=".7" at=".6" /> <Color r="1." g="0." b="0." alpha=".7" at=".8" /> <Color r="1." g="0." b="0." alpha=".7" at="1." /> </Gradient> </Foreground> </BarGauge>
Identical to a BarGauge, but renders vertically instead. Labels are currently broken for it, so don't use them yet.
A label may have only one field in each DataSet tag and that field must be "source". It also requires each DataSet to have an id, which you will use to reference that data farther down. It supports any number of DataSets. In addition, you need a Face to describe how to build the Label. Here's an example for the enemies alive gauge:
<Label camera="*"> <DataSet id="enemies"> <AtomicData field="source" source="enemies_alive" /> </DataSet> <DataSet id="friends"> <AtomicData field="source" source="friends_alive" /> </DataSet> <Position x="0.06" y="-0.96" /> <Size height="0.035" width="0.017" /> <!-- Size would be the font size in this case --> <Caption location="off"> <Text value="Player Status" /> </Caption> <Face> <Table> <Row> <Cell><Text value="Enemies:" /></Cell> <Cell><GameData data="enemies" /></Cell> <Cell><Text value="Friends:" /></Cell> <Cell><GameData data="friends" /></Cell> </Row> </Table> </Face> </Label>
A table is much like a basic HTML table: It consists of rows, coulumns, and cells. Each cell can contain multiple <Text> or <GameData> nodes, they will just be concatenated. Here's an example of a table that shows the time, the framerate, and how long Armagetron Advanced is running:
<Label camera="*" viewport="top"> <DataSet id="hours"> <AtomicData source="current_hours12h" minwidth="2" fill="0" /> </DataSet> <DataSet id="minutes"> <AtomicData source="current_minutes" minwidth="2" fill="0" /> </DataSet> <DataSet id="ampm"> <Conditional field="source" lvalue="current_hours" rvalue="12" operator="le"> <IfTrue> <AtomicData source="AM" /> </IfTrue> <IfFalse> <AtomicData source="PM" /> </IfFalse> </Conditional> </DataSet> <DataSet id="fps"> <AtomicData source="current_framerate" /> </DataSet> <DataSet id="seconds"> <AtomicData source="time_since_start" /> </DataSet> <Position x="0.4" y="0.45" /> <Size height="0.04" width="0.02" /> <!-- Size would be the font size in this case --> <Caption location="top"> <Text value="Time/Fps/Timestamp" /> </Caption> <Face> <Table> <Row> <Cell><Text value="Time:" /></Cell> <Cell><GameData data="hours" /><Text value=":" /><GameData data="minutes" /><Text value=" " /><GameData data="ampm" /></Cell> </Row> <Row> <Cell><Text value="Framerate:" /></Cell> <Cell><GameData data="fps" /><Text value=" FPS" /></Cell> </Row> <Row> <Cell><Text value="Running for:" /></Cell> <Cell><GameData data="seconds" /><Text value=" Seconds" /></Cell> </Row> </Table> </Face> </Label>
The Map is a very special gauge that displays a map of the arena. As a special gauge it has very special requirements. It supports Position, and Size, nothing else. Also, it will anchor its own aspect ratio, so it might not use the whole width or height you specified. The map will be as big as possible while keeping its aspect ratio and fitting in the width and height you specify.
Here's an example:
<Map camera="*" viewport="top"> <Position x="0.73" y="-0.72" /> <Size height="0.25" width="0.25" /> </Map>
You can configure the way the minimap looks by using a special
<MapModes /> tag. It should contain one or more
<MapMode /> tags, like this:
<Map> <MapModes toggleKey="1"> <MapMode mode="full" rotation="spawn" /> <MapMode mode="closestZone" rotation="spawn" /> <MapMode mode="cycle" rotation="camera" zoomFactor="3" /> <MapMode mode="cycle" rotation="cycle" zoomFactor="3" /> <MapMode mode="cycle" rotation="spawn" zoomFactor="4" /> <MapMode mode="full" rotation="fixed" /> </MapModes> </Map>
toggleKey is a cockpit key that can be used to switch between the settings given in the
<MapMode /> tags. These are independent of each other. They can have the following attributes:
- The way the map pans. Currently the following values are implemented:
- The default; show the entire map
- Center around the cycle, like a radar
- Center around the closest zone
- This determines how much of the map you see at once. If the mode is
full, this doesn't have any effect, if it's
cycleit'll be multiplied by the cycle's speed, and if it's
closestZoneit'll be multiplied by the zone's radius
- This controls how the map is rotated. Valid values are:
- Don't rotate at all; positive y-coordinates will be at the top of the map.
- Rotate the map so the direction the cycle spawned in is facing upwards
- Rotate so the current cycle is always facing upwards
- Rotate so the direction the camera is facing is pointing upwards on the map
Templates can be used for multiple similar gauges. Say you want three gauges for Brakes, Speed and rubber, they're all the same exept for position, caption and data.
You could copy and paste the different attributes, but that leads to a larger file, and if you later decide you want your gauges smaller you'd have to edit all three of them. That's where templates come in.
This is how you define a template:
<WidgetTemplate id="MyTemplate"> <Size height="0.3" width="0.3" /> <Position y="-.7" x="0" /> </WidgetTemplate>
id attribute is used to reference your template for later use. Inside a
<WidgetTemplate /> you can have all possible tags for widgets.
This is how you use your template:
<NeedleGauge usetemplate="MyTemplate"> <Position x="-.5" y="0" /> </NeedleGauge>
This internally gets replaced by the following:
<NeedleGauge> <Size height="0.3" width="0.3" /> <Position y="-.7" x="0" /> <Position x="-.5" y="0" /> </NeedleGauge>
See how there's two
<Position /> tags now? Since there's more than one tag they'll get added to each other, resulting in the widget being positioned at (-.5, -.7).
<Size /> tags will get multiplied, so you could scale a template. All other tags just overwrite each other, so the last one "wins".
You can use multiple templates in one tag by seperating their names by spaces, like this:
<Label usetemplate="MyTemplate MyOtherTemplate">...</Label>
Are you annoyed by the HUD map being to small, but don't like a big map permanently cluttering up your screen? There's a solution, toggle keys. You can map the five available toggle keys in the global keybord configuration screen, and what they do depends entirely on the cockpit file.
Here's an example of a HUD map that gets toggled by key 1 and is visible by default (that is, until you press the key and turn it off):
<Map camera="*" viewport="top" toggle="1" toggleSticky="true" toggleDefault="on" > <Position x="0" y="-.25" /> <Size height=".75" width="0.7" /> </Map>
There are three attributes that influence toggling behavior:
- The toggle key to be used, needs to be a number from 1-5 or 0 for no toggle key (default if you omit it)
- If the value is "true" (default) pressing the key and releasing it will change the visibility. If it is false pressing the key and holding it will change the state, but it will return the default once you release it.
- "on" (default) means the widget will be visible until you press the toggle key, "off" means it will be invisible first.
For Server Admins
There are many new kinds of gauges that might be considered as cheating by some people, or at least it gives a certain advantage to people who use them.
If you want to disable certain data sources you can change the FORBID_COCKPIT_DATA setting. It contains a colon–separated list of data sources that will be disabled (it will just display 0 instead of a value):
This will prevent the client from displaying the time_to_impact data sources and the current rubber. Likewise the HUD minimap can be disabled by the following command:
Bear in mind, though, that the server has no way of knowing if the client is actually following these rules. All data sources use data that is known to the client, it wouldn't be hard to hack a client to ignore this setting. This is basically the same problem as with the FORBID_CAMERA_* settings: You have to trust on the users of your servers to play fair.
QA and release
For testing your cockpit, see Testing Resources. You can make your work available to others by uploading it to the Resource repository. If you do that, you might want to add an entry to Cockpits list to tell users about your cockpit.