This website contains technical documentation of Digital Extinction (GitHub).
Also see Rust documentation.
Camera Movement
Move your mouse close to a screen edge to move the camera along the map surface (north, south, west, east) or use the arrow keys.
Use mouse wheel to zoom closer or further from the terrain.
Press and hold shift or the mouse wheel and then move your mouse to tilt and/or rotate the camera around its focus point on the terrain.
Minimap
Left Click
- Moves the camera to the target.
Right Click
- Sends selected units to the click position.
- Sets manufacturing delivery location to the click position.
Hotkeys
- Escape — cancel current action or display menu.
Building and Unit Selection
Left click on any of your units or buildings to select it. Press and hold Ctrl before left clicking on buildings and units to (de)select more entities.
Drag mouse to select all entities inside a rectangle. Press and hold Ctrl extend current selections instead of replacing it.
Double click on a unit to select all visible units of that type. Holding Ctrl adds to the existing selection.
Press Ctrl+A to select all entities.
Press Ctrl+Shift+A to select all visible entities.
Building Construction
You have to select a building to construct by pressing a key, place it on an empty place on the terrain by moving your mouse and then confirm the construction by left clicking the mouse.
Building Keys
- B — Base
- P — Power Hub
Unit Manufacturing
- Select a single building with a factory (for example a base).
- Clicking "manufacture unit" buttons in the bottom middle panel enqueues the units for manufacturing.
Delivery Location
Right clicking on the terrain sets manufacturing delivery location to the click position.
Commanding Units and Buildings
Right click on the terrain sends selected units to that location. Right click on an enemy building or a unit commands selected units and buildings to attack that entity.
Game Design
This documents summarizes high level design of the game. It does not capture current state but the desired state attainable in reasonable amount of time.
The document is based on conclusions from our discussions on ideas. If you have an idea, consult the discussions first.
Energy
There is only one currency / resource in the game and that is energy.
-
Many kinds of activity (for example unit movement, manufacturing & construction, combat) require energy. These are shut down by priority if there is not enough locally available energy.
-
Energy can be generated with several different kinds of power plants: solar, nuclear, and wind.
-
Energy can be stored in integrated batteries (inside buildings and units) or in dedicated battery farms with higher capacity.
-
Energy is not a global resource but has to be distributed via an electricity grid. The grid consists of Power Hubs transmitting the energy via lasers. The grid might be attacked by an opponent. It has to be constructed and defended.
-
Energy is transmitted from a source by an energy laser to a target by an energy panel. A beam from a laser to a panel is formed along a clear line of sight.
-
Buildings and units might be equipped with 0 or more energy lasers and energy panels.
-
The energy grid operates autonomously: it constructs a semi-optimal transmission network based on priorities, remaining battery capacities, and other criteria.
Buildings
Base
- Is capable of producing all unit.
- Has a battery of a very high capacity.
- Is equipped with several laser cannons for defense.
- Produces small amount of energy via integrated solar panels.
- Has several energy lasers.
- Has several energy panels.
Factory
- Is capable of producing all unit.
- Has a battery of medium capacity.
Solar Power Plant
- Has a single energy laser.
Battery Farm
- Stores large amount of energy.
- Has a single energy panel.
Power Hub
- Has several energy lasers.
- Has several energy panels.
- Has no batteries.
Units
Attacker Drone
- Has an integrated battery of medium capacity.
- Has a single energy panel.
- Is equipped with a laser cannon.
Construction Drone
- Has an integrated battery of small capacity.
- Has a single energy panel.
- Can construct any kind of building.
Battery Drone
This drone can be used to deliver energy to areas of scarcity.
- Has a battery of large capacity.
- Has a single energy panel.
- Has a single energy laser.
Map Objects
Every object which can be placed on the game map has an associated object info
file. It is an ordinary JSON file containing various information about the
object. The file name has suffix .obj.json
.
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Object Info",
"type": "object",
"description": "Description of a game object.",
"properties": {
"footprint": {
"type": "object",
"description": "A 2D footprint of the object used for path planning and collision avoidance.",
"properties": {
"convex_hull": {
"type": "array",
"description": "A convex polygon fully containing the object.",
"items": {
"type": "array",
"description": "2D coordinates in the object space.",
"items": {
"type": "number"
},
"minItems": 2,
"maxItems": 2
},
"minItems": 3
}
},
"required": [
"convex_hull"
]
},
"shape": {
"type": "object",
"description": "A tri-mesh collider of the object. The object 3D model is fully contained inside the buffer. Due to performance reasons, the number of triangles should be very small.",
"properties": {
"vertices": {
"type": "array",
"description": "Array of unique vertices of the tri-mesh.",
"items": {
"type": "array",
"description": "A 3D point in object space.",
"items": {
"type": "number"
},
"minItems": 3,
"maxItems": 3
},
"minItems": 4,
"uniqueItems": true
},
"indices": {
"type": "array",
"description": "Triangle vertex triplets. Each triplets is counter clockwise.",
"items": {
"type": "array",
"description": "An index of `vertices` array.",
"items": {
"type": "integer"
},
"minItems": 3,
"maxItems": 3,
"minimum": 0
},
"minItems": 4
}
},
"required": [
"vertices",
"indices"
]
},
"cannon": {
"type": "object",
"description": "Properties of a laser cannon.",
"properties": {
"muzzle": {
"type": "array",
"description": "3D coordinates of the cannon muzzle in object space.",
"items": {
"type": "number"
},
"minItems": 3,
"maxItems": 3
},
"range": {
"type": "number",
"description": "Maximum firing distance of the cannon.",
"exclusiveMinimum": 0
},
"damage": {
"type": "number",
"description": "Enemy damage when hit by the gun.",
"exclusiveMinimum": 0
},
"charge_time_sec": {
"type": "number",
"description": "How long it takes to fully charge the gun.",
"exclusiveMinimum": 0
},
"discharge_time_sec": {
"type": "number",
"description": "How long it takes until the gun is fully discharged if not actively charged.",
"exclusiveMinimum": 0
}
},
"required": [
"muzzle",
"range",
"damage",
"charge_time_sec",
"discharge_time_sec"
]
},
"flight": {
"type": "object",
"description": "Configuration of object flight capabilities. This property is not defined for object with no flight capability.",
"properties": {
"min_height": {
"type": "number",
"description": "Minimum flight height (vertical distance from the terrain).",
"exclusiveMinimum": 0
},
"max_height": {
"type": "number",
"description": "Maximum flight height (vertical distance from the terrain).",
"exclusiveMinimum": 0
}
},
"required": [
"min_height",
"max_height"
]
},
"factory": {
"type": "object",
"description": "Configuration of the manufacturing capability of the object.",
"properties": {
"products": {
"type": "array",
"description": "List of products which can be manufactured.",
"items": {
"type": "string",
"enum": [
"Attacker"
]
},
"minItems": 1
},
"position": {
"type": "array",
"description": "Position of the assembly line, i.e. where the products are manufactured and spawned. A 2D position in the object space.",
"items": {
"type": "number"
},
"minItems": 2,
"maxItems": 2
}
},
"required": [
"products",
"position"
]
}
},
"required": [
"footprint",
"shape"
]
}
Multiplayer
The multiplayer functionality comprises a game lobby and in-game communication.
Before a game starts, players are required to log into DE Lobby Server using their accounts. They can browse through existing games and join one, or alternatively, create a new game. This functionality and other functionality is facilitated through a REST API.
When initiating a multiplayer game, all players are interconnected via DE Connector game server. All communication is routed through this server, utilizing a custom real-time networking protocol that operates over UDP.
DE Lobby Server
Multiplayer games are managed and initiated via DE Lobby Server. The lobby server implements a simple HTTP API for user and game management.
The HTTP API is documented with Open API Specification: openapi.yaml.
Configuration
The server is configured via environment variables:
-
DE_DB_URL
(required) – SQLite database URL passed toSqliteConnectOptions
ofsqlx
Rust library. -
DE_JWT_SECRET
(required) – A Base64 encoded secret used for signing and validation of JSON Web Tokens (JWT). The secret must have between 12 and 86 characters.Make sure to invalidate all JWT by changing the secret after any changes or purges of the database.
-
DE_HTTP_PORT
(optional) – HTTP server port number. Defaults to8080
. -
RUST_LOG
(optional) – logging configuration, see env_logger documentation.
DE Connector
DE Connector interconnects players so they can exchange messages in real-time.
Communication between clients and the server relies on a low-level communication protocol, providing reliable and non-reliable means of binary data exchange.
Binary encoded messages are sent via the low-level protocol as packages to the server. Based on the package target (peers), the server either receives and interprets the data, or transmits it to other clients connected to the same game.
Game participants exchange messages through a game server. For efficiency, multiple messages may be grouped into a single package, and the messages are buffered and flushed at the end of each game update (frame). The buffering is independent for unreliable, unordered, and semi-ordered packages, with their buffers being flushed in the respective order.
The main server listens on port 8082, which is designated solely for server control messages such as game initiation requests. Upon the creation of a new game, a unique sub-server, listening on a different port, is started. It is within these sub-servers that clients exchange data among themselves.
Principles
Game networking is designed in such a way that complete game determinism is not required for certain aspects, such as entity movement. It is assumed, however, that the divergence among individual clients is negligible in the short term (on the order of several seconds). This approach simplifies movement simulation but comes at the cost of the need for regular synchronization and higher network bandwidth requirements.
All clients simulate all game activities while receiving updates to correct any possible simulation divergence. Each action has its authoritative owner, corresponding to the client that is locally controlling the entity responsible for the action, whether through a human player or AI. The owner sends a synchronization message, which is respected by the other clients. For instance, an entity damage action (a decrease in health) is owned by the client simulating the entity that causes the damage (for example, by firing a laser cannon).
Whenever possible, messages are sent in unreliable mode. This method of data exchange consumes fewer resources but does not guarantee delivery or ordering. For instance, entity movement synchronization occurs unreliably. This approach is both possible and preferable because the position of each entity, out of possibly thousands, is regularly updated, and messages that reference non-existent entities are disregarded.
Network Protocol
The protocol is based on UDP datagrams and provides means of reliable and non-reliable binary data delivery.
There are two types of datagrams:
- package – a datagram with a header and user data payload,
- protocol control – a datagram with a header and system data.
All integers are big-endian encoded.
The structure of each datagram is this:
- flags – 1 byte
- ID – 3 bytes
- payload – 504 bytes or less
Protocol control is signaled by the highest bit of the flags byte (represented
by the mask 0b1000_0000
).
Package
Each user package has an ID, encoded within the last three bytes of the datagram header. These IDs increment until they reach the maximum value that can be encoded within three bytes, after which the counter resets to 0. The ID sequence for reliable and unreliable packages are independent. Each connection / client has independent reliable package numbering.
Packages can be transmitted in several reliability modes, as listed below. The
second and third bits (represented by the mask 0b0110_0000
) of the flags byte
control the reliability.
- Unreliable (bits
00
) – these packages may be lost, delivered multiple times, or delivered out of order with respect to other packages. - Unordered (bits
01
) – non-duplicate delivery of these packages is guaranteed. However, there are no guarantees regarding the ordering of these packages with respect to other packages. - Semi-ordered (bits
10
) – non-duplicate delivery of these packages is guaranteed. Additionally, the packages are guaranteed to be delivered after all previously reliably sent packages, that is, all unordered and semi-ordered packages sent by the same client before a semi-ordered package are always delivered before the semi-ordered package.
Packages can be targeted to the server, as opposed to all other game
participants. This is indicated by the fourth highest bit of the flags byte
(represented by the mask 0b0001_0000
).
Package payload comprises the user data intended for delivery.
Examples
The datagram carrying a semi-reliable package with ID 107907,
targeted to the server and containing the payload bytes 0x12 0x34 0x56
, would
look like this: 0x50 0x01 0xA5 0x83 0x12 0x34 0x56
.
0x50
(0b0101_0000
) – flags: semi-reliable, server targeted0x01 0xA5 0x83
– package ID: 107,9070x12 0x34 0x56
– package payload
Protocol Control
Currently, the only type of control datagram is the delivery confirmation datagram. All bits in the header of these datagrams, except for the first one, are set to 0. The payload consists of IDs for all user data datagrams that have been sent reliably and delivered successfully. Each ID is encoded using 3 bytes.
Examples
A datagram confirming three packages with IDs 1238, 17, and 2443, respectively,
would look like this: 0x80 0x00 0x00 0x00 0x00 0x04 0xD6 0x00 0x00 0x11 0x00 0x09 0x8B
.
0x80
(0b1000_0000
) – protocol control flag0x00 0x00 0x00
– always zeros0x00 0x04 0xD6
– first confirmed package ID equal to 12380x00 0x00 0x11
– second confirmed package ID equal to 170x00 0x09 0x8B
– third confirmed package ID equal to 2443
Message Encoding
Individual messages, implemented as Rust enums, are encoded via Bincode version 2. Usually, multiple messages are sent in a single package. Big endian with variable bit encoding is used.
See individual message documentation.
Configuration
Game configuration is stored into and loaded from a YAML file located at
{user_conf_dir}/DigitalExtinction/conf.yaml
. {user_conf_dir}
is obtained
with dirs::config_dir()
and conforms to the following table:
Platform | Value | Example |
---|---|---|
Linux | $XDG_CONFIG_HOME or $HOME /.config | /home/alice/.config |
macOS | $HOME /Library/Application Support | /Users/Alice/Library/Application Support |
Windows | {FOLDERID_RoamingAppData} | C:\Users\Alice\AppData\Roaming |
Configuration YAML
All properties in the YAML tree are optional, default values are used instead. Missing configuration YAML file is treated equally to an empty YAML file, id est as if all properties are missing.
multiplayer
(object) – multiplayer and network configuration.lobby
(string; default:http://lobby.de-game.org
) – lobby server base URL.connector
(string; default:127.0.0.1:8082
) – DE Connector main server socket address. It must be valid IPv4 or IPv6 address.
camera
(object) – in-game camera configuration.move_margin
(f32; default:40.0
) – horizontal camera movement is initiated if mouse is withing this distance in logical pixels to a window edge. It must be a finite positive number.min_distance
(f32; default:20.0
) – minimum camera distance from the terrain. It must be a finite number larger or equal to10.0
.max_distance
(f32; default:80.0
) – maximum camera distance from the terrain. It must be a finite number larger or equal tomin_distance
and smaller or equal to300.0
.wheel_zoom_sensitivity
(f32; default:1.1
) – camera to terrain distance scale factor used during mouse wheel zooming. The distance is changed tocurrent_distance * wheel_zoom_sensitivity
(zoom out) orcurrent_distance / wheel_zoom_sensitivity
(zoom in) with each mouse wheel tick. It must be a finite number larger than1.0
.touchpad_zoom_sensitivity
(f32; default:1.01
) – camera to terrain distance scale factor used during touchpad zooming. The distance is changed tocurrent_distance * touchpad_zoom_sensitivity
(zoom out) orcurrent_distance / touchpad_zoom_sensitivity
(zoom in) with each pixel movement. It must be a finite number larger than1.0
.rotation_sensitivity
(f32; default:0.008
) – multiplicative factor used during camera tilting and rotation. Mouse drag bydelta
logical pixels leads to the change of elevation and azimuth bydelta * rotation_sensitivity
radians. It must be a positive finite number.scroll_inverted
(bool; default:false
) – iftrue
, mouse wheel and touchpad scrolling is inverted.
audio
(object) – audio configuration.master_volume
(f32; default:1.0
) – sets the master volume of all audio. It must be a finite number between0.0
and1.0
. If set to 0 no audio will play.sound_volume
(f32; default:1.0
) – sets the SFX volume. It must be a finite number between0.0
and1.0
. If set to 0 sound effects will not play.music_volume
(f32; default:1.0
) – sets the music volume. It must be a finite number between0.0
and1.0
. If set to 0 music will not play.
Example Configuration
multiplayer:
lobby: 'http://lobby.de_game.org/'
connector: '127.0.0.1:8082'
camera:
move_margin: 40.0
min_distance: 20.0
max_distance: 80.0
wheel_zoom_sensitivity: 1.1
touchpad_zoom_sensitivity: 1.1
rotation_sensitivity: 0.01
scroll_inverted: false
audio:
master_volume: 1.0
sound_volume: 1.0
music_volume: 1.0
Logging
Game logs are both sent to standard output and persistently stored in text
files located at {user_cache_dir}/DigitalExtinction/logs/
. After each game
startup, a new log file named according to the pattern
%Y-%m-%d_%H-%M-%S.jsonl
is created. The log files consist of JSON
lines.
One might use a variation of the following command for pretty printing the logs
cat 2023-05-31_17-44-25.jsonl | jq -cr '.timestamp + ": " + .fields.message'
.
{user_cache_dir}
is obtained with
dirs::cache_dir() and
conforms to the following table:
Platform | Value | Example |
---|---|---|
Linux | $XDG_CACHE_HOME or $HOME /.cache | /home/alice/.cache |
macOS | $HOME /Library/Caches | /Users/Alice/Library/Caches |
Windows | {FOLDERID_LocalAppData} | C:\Users\Alice\AppData\Local |