Introduction
Here's the documentation for Spaceships v1.3.0. Right now this documentation is pretty incomplete.
If you have any questions, ask them on Lemmy or Matrix, whichever you prefer. More information about Lemmy and Matrix is available here:
- Lemmy: https://join-lemmy.org/
- Matrix: https://matrix.org/
Screenshots
Here are some screenshots of the game (from an older version):
Links
- Website: https://spaceships.me
- Download: https://spaceships.me/download
- Documentation: https://spaceships.me/docs
- Source code: https://codeberg.org/rustydev/spaceships
- Lemmy: https://lemmy.spaceships.me
- Development blog: https://lemmy.spaceships.me/c/blog
- Help and support: https://lemmy.spaceships.me/c/help
- Matrix: #spaceships:matrix.spaceships.me
- Chat: #chat:matrix.spaceships.me
- Help and support: #help:matrix.spaceships.me
How to play
Joining a game
To start a game, you first need to start a server or join an already existing server. See the "Hosting a server" section for more information about starting a server.
If the game server is hosted on the local network, joining the game is done by clicking "Play" and then "Local" under. Then click "Search LAN" and if everything works well, the game server will pop up and you can then click "Play".
If the game server is hosted on the internet (or on the local network but the above instructions don't work for some reason), instead click "Enter Address" and enter the IP address or domain name in the "Host" section. The "Game ID" section can be left blank unless the server administrator tells you to set it to a particular value.
You can also play Spaceships in a singleplayer mode where you can immediately join a local game (where nobody else can join) to test things out. Playing Spaceships only makes sense with other people, it won't be fun at all doing it by yourself (this might change in the future if I create bot players).
Playing the game
Gameplay in Spaceships involves the player controlling a spaceship that they move around and kill other spaceships by firing bullets at them to diminish their health and eventually destroy them.
Controls
To move around, use WASD or the arrow keys. To change the direction you're looking in and where bullets will be fired from, move the mouse. Bullets are shot by pressing the left mouse button or spacebar. You can hold down these buttons to easily fire multiple bullets at a time. These controls can be changed by pressing the "Settings" button while in the title screen.
You can zoom the camera in and out by scrolling (or by pressing 'J' and 'K'), and you can reset to the default zoom by pressing the middle mouse button.
Health
Your health is displayed in the health bar at the bottom of your screen, with the number of bullets also displayed above that. The health bars of other players are displayed right above them. After getting shot, players slowly regenerate their health.
Ammo
You have a finite amount of bullets (unless the server is configured to provide you infinite), which are reset when you die. Periodically during the game, ammo crates are spawned which supply the player collecting them with a random amount of bullets.
Powerups
Additionally, powerups are periodically spawned which give the player collecting them a temporary effect. These effects either last for 30 seconds or happen immediately (teleportation) and are listed as follows:
- Speed. This is the green powerup which increases your movement speed.
- Reload. This is the yellow powerup which increases your reload speed, making you shoot faster.
- Health. This is the pink powerup which increases your health and makes you regenerate health faster. The increase of health that this powerup provides can increase your health above the usual limit and make your health bar blue.
- Damage. This is the red powerup which increases the damage of your bullets.
- Forcefield. This is the blue powerup which provides a forcefield that deflects bullets fired by other players.
- Teleportation. This is the purple powerup which teleports you to a random place on the map. Unlike the others, this can be disadvantageous depending where you teleport...
Here are these six powerups:
Player customisation
The name and colour of the play can be changed by going into the settings.
Teams
Some Spaceships servers can have teams enabled. When you join the game, you will be assigned to a specific team. To change teams, first list the available teams by running the command (keep reading for information about commands) teams
. This should return a list of teams on the server. Then run team NAME
where NAME
is the name of the team you want to change to.
When in a team, your bullets cannot hit other members of your team (unless the server has friendly fire enabled).
Winning the game
The way the game is actually won, if there is a way, is highly customisable. On the top-left corner of the screen is a list of scores that the server keeps track of. These scores can increase or decrease when certain events happen, such as when a player gets a kill or kills someone else.
Here is a list of a few examples of win conditions that are possible:
- First to N kills.
- Most kills within a certain amount of time.
- Last player remaining where players are eliminated if they lose too many lives.
Chat
Spaceships has two types of in-game chat that you can use to talk to other players. The first type is team chat which allows you to talk to your team members. To open team chat, press 'T' (you can bind this to something different), start typing and press enter to send your message. Only other players on your team will receive your message, unless teams are disabled in which all players will receive the message.
The second type of chat is global chat which lets you talk to all players. Opening global chat is done by pressing 'G'.
Commands
Spaceships also supports sending commands to the server using the chat window. To send a command, first press '/' (again, can be rebinded), type your command and then press enter to send it. Run the help
command to get an exact list of commands the server supports.
Note that opening chat and typing a message beginning with '/' won't execute a command and instead send a regular message.
Underpowering
If you're an experienced player playing with a friend who is new to the game, you can choose to make yourself weaker to make the game fairer. This is called underpowering and is done by executing commands.
For an example, to decrease your speed to 75% of what it usually is, run underpower speed 75%
. Note that you can use 0.75 instead of 75%, and the alias up
can be used. Another example is to completely disable regeneration, you can run up regen 0
.
The general syntax for this specific subcommand is underpower/up EFFECT VALUE
, where EFFECT
is speed
, reload
, regen
(or regeneration
) and damage
, and value is a number or a percentage.
Security
If you're playing this game online, you might be concerned about whether encryption is used, especially when it comes to chat messages which might be sensitive. Spaceships uses QUIC, a transport-layer protocol over UDP that always uses encryption. However, that doesn't mean that all communications are secure.
First note that the security I'm referring to here is about whether a malicious device on the path between the client and the server can eavesdrop communications. All chat messages are logged for the server owner to read, so if you don't trust the server owner, don't send any sensitive chat messages.
While playing the game, the connection's security is displayed in the bottom-left corner under the chat. This information is one of the three security levels, "Secure", "Local" and "Insecure".
Secure
If a connection is secure, as indicated by the green text saying "Secure connection", it is very unlikely that communication is being eavesdropped.
This level of security is reached when the server is hosted on the same device, or a trusted TLS certificate is used. This level of security is the same as HTTPS on the web.
Local
If a connection is local, as indicated by the blue text saying "Local connection", it means that the connection isn't secure, but can only be eavesdropped by an attacker on the local network. If you're connecting to a server on the local network and you trust that network, communications are secure.
Insecure
If a connection is insecure, as indicated by the red text saying "INSECURE connection", it means that the connection isn't secure and can be eavesdropped by an attacker on the internet that controls a node on the path. This attacker can be an ISP or a government agency.
For most users, it is unlikely that there actually is an attacker listening into communications, but it is still possible. If you live in an authoritarian regime such as Russia, Saudi Arabia, Myanmar, the United States, Israel, China or Iran, you shouldn't send any chat messages that the government would disapprove of if using an insecure connection. It's better to be safe than sorry.
Note on eavesdropping
I mentioned earlier that encryption is always enabled in Spaceships. That is true but it is still possible to eavesdrop by performing a man-in-the-middle attack in which an attacker impersonates the server and can decrypt communications.
This encryption is still better than nothing as it fully prevents against passive eavesdropping attacks, where the attacker can only listen in but can't modify the messages.
Spectator mode
If you want to watch a game play out but not be an active player, you can join as a spectator. To do this, click the checkbox before starting the game or execute the spectate
command while playing the game.
While in spectator mode, you can move the camera around using WASD or the arrow keys and you can also drag the camera using the mouse. You can also press 'F' to toggle between freely moving around and following another player in the world.
Some game modes can eliminate the player before the game ends, in which those players will be forcefully put into spectator mode.
Settings
In the title screen, you can click "Settings" to change some settings related to input and graphics.
Player
Here you can change your name and colour.
Input
The second part of the settings screen provides a way to change the keyboard and mouse button bindings to certain actions. These actions are organised into many categories available as headers, such as "UI" and "Player". Click on these to open up a list of buttons for each action in that category.
You can then hover over a button to get a list of inputs bound to that action. For example, hovering over the "Open Chat" button shows "Bound to: Key T".
To change these bindings, first click on one of these buttons after which a window will pop up showing the bound inputs and three buttons. To add a binding, first click "Select" and then press any key or click any mouse button. Now you can click "Bind" to bind the recently performed input to that action. You can do the same for unbinding.
Graphics
The graphics part of the settings screen provides the ability to change the density of decorative stars (to which you can completely disable) as well as which particle effects are enabled.
Hosting a server
Right now hosting a server is only possible using the command line.
There are many different worlds that you can create, a few of them are provided in the configs/
directory. These configs are also available online here.
Tip: If you're using the Flatpak distribution, configuration files are located in /app/configs/
inside the sandbox. Whenever you see the configs/
directory discussed in the rest of the documentation, substitute it for /app/configs/
if using the Flatpak distribution.
To start a server, simply run ./spaceships server configs/CONFIG.ron
, replacing configs/CONFIG.ron
with the exact configuration to use. On Windows, you would run ./spaceships.exe server ...
.
The server program outputs logs to the terminal (in particular, stderr), which include information about players joining and leaving the game. The server also provides a simple REPL interface which allows more advanced configuration.
If you're running an internet server with an associated domain name, if users want to connect to the server by entering the domain name you will need a valid TLS (also called SSL) certificate. To run the server to use this certificate, run ./spaceships ... --cert-path PATH --key-path PATH
, replacing PATH
with the paths of the certificate and private key. These paths are to files in the PEM format.
Requesting server info
This game is licensed under the GNU AGPLv3, which requires that any user interacting with the game over the network can get a copy of the exact version of the game running on the server. If you want to know the exact version of a server and if it's a fork, run ./spaceships info HOST
, where HOST
is the domain name or IP address of the server.
Client information
The information about your version of Spaceships can be obtained by running ./spaceships info
or clicking the "About" button in the title screen.
Server configuration
The long-term goal of Spaceships is to support configuration of everything in the game, including the physics, weapons, and other objects, similar to how games like Luanti currently are.
Right now Spaceships isn't there yet, but game servers are pretty configurable.
If you find any errors in this documentation, which are likely if I forget to update this in future versions, make an issue here.
Get started
The documentation here is incomplete. I recommend first taking a look at files in the configs/
directory attached with your distribution of the game, or at https://codeberg.org/rustydev/spaceships/src/branch/main/configs for examples of what a config file looks like. These can be used as a template for creating your own server config.
These configuration files are in the RON (Rusty Object Notation) format. See the link for more information.
The complete schema of the config files is available inside src/net/server/config.rs
, with some (but not enough) code comments explaining things.
Example
Here's an example of a very simple configuration file that's an infinite world with no blocks in it. This is available at configs/empty.ron
.
Config(
world: World(
size: (inf, inf),
),
lan_discovery: LanDiscovery(
name: "Empty",
desc: "An empty map.",
),
blocks: Blocks(
block_size: 1,
grid_size: (0, 0),
ops: [ Constant(0) ],
),
)
Now here's an explanation of each of these fields:
world.size
: This specifies the width and height of the world. Players cannot go outside this space. In this example, both the width and height are set toinf
, which configures an infinite world.lan_discovery
: The first chapter of this documentation briefly goes over LAN discovery as a way to easily join game servers hosted on the local network. Here,lan_discovery.name
andlan_discovery.desc
set the displayed name and description of the game server respectively.blocks
: This consists of three fields, each for configuring the physical blocks in the world that constrain the players' movement.blocks.block_size
: This is an integer that sets how large each block is in the game.blocks.grid_size
: This sets the width and height of the grid which contains these blocks. The number of blocks in this grid cannot exceed 220.blocks.ops
: This specifies a list of operations that are performed on block maps stored on a stack to produce a final block map.
Spawning, ammo crates and powerups
To configure these, see the fields with the events
prefix. See the full schema for more information.
Special areas
The server has the ability to send an arbitrary list of rectangles of arbitrary position, size and colour to the clients, which the clients should render these in the world. These are designed for highlighting regions of the world where custom server-side configuration can perform certain actions. Right now the two special areas implemented in this game are portals and effect zones, though more special areas can be added in the future and those special areas can still be compatible with older clients.
Here's an example of how to add special areas that are just decorative:
Config(
...
special_areas: SpecialAreas(
areas: [
( pos: Vec2(15, 15), size: Vec2(4, 4), colour: (127, 255, 0, 63) )
],
),
)
Portals
Portals allow players (but not bullets) to teleport from one location to another. Portals can either be sinks (you enter these), sources (you leave these), or bidirectional (you can go both ways). Portals are expressed in the config as a directed graph, with nodes being the portals and edges being the possible transitions between portals. If a node has more than one outgoing edge, players entering that portal will teleport to a random outgoing neighbour of that portal.
It is a desirable feature to allow the velocity of players to change after they leave the portal, like if they entered a horizontal portal and leave a vertical portal. To do this, special_areas.portals.out.rotation
can be set to either D0
(default for no rotation), D90
, D180
or D270
.
When specifying portals, make sure that the player isn't inside a block or outside the world border if they are at the portal's centre. If you don't ensure this, it might be possible for players to teleport inside a block.
Example
Config(
...
special_areas: SpecialAreas(
portals: [
Portal(
p0: Vec2(-4.5, -3.0), p1: Vec2(1.5, 0.5),
out: [PortalLink(
index: 1,
rotation: D90,
), PortalLink(
index: 2,
)],
),
Portal( p0: Vec2(12.5, -9.5), p1: Vec2(14.5, -1.5) ),
Portal(
p0: Vec2(22, 22), p1: Vec2(24, 23),
out: [PortalLink(
index: 0,
)],
),
],
),
)
Effect zones
An effect zone is a region where players get effects applied to them (the same effects that powerups give) while there. These effects can persist for an arbitrary amount of time when the player leaves the effect zone.
Example
Config(
...
special_areas: SpecialAreas(
effect_zones: [
EffectZone(
p0: Vec2(15, 15),
p1: Vec2(19, 19),
effect: Speed,
time: 5,
power: Some(4), // Very fast!
),
]
),
)
Teams
By default teams are disabled. To enable them, you need to specifically add them to the config.
Example
Here is an example of how to create a red and blue team.
Config(
...
teams: [
Team(
name: "Red",
colour: "red",
aliases: ["r", "red"],
),
Team(
name: "Blue",
colour: "blue",
aliases: ["b", "blue"],
),
],
)
Custom spawn points
It is possible to have all players in a team spawn at a fixed location in the world by adding spawn_point: Some((X, Y))
for some X
and Y
. This doesn't affect player spawning when in the lobby. See configs/occupation.ron
for an example of this.
Scoring
The scoring part of the config schema consists of two parts: the controller and the conditions for winning and elimination. The score controller specifies how the player and team scores should change in response to events, like a player getting a kill, dying or occupying a part of the world (see the occupation game). The win and elimination conditions accept as input the scores of either the player or the teams, as well as the timer. These decide respectively when the game should end and who (either in terms of players or teams) should win the game, or who should be removed from the game until the game finishes.
By default there is no win or elimination condition and the score of each player is their kill count.
Examples
Here are a few examples of how the scoring system can be configured.
First to 10 kills
To win the game in the following config, you need to be the first player to get at least 10 kills.
It should be noted that multiple players can win the game if they both get to at least 10 kills at the exact same time, though this is very unlikely to occur.
Config(
...
scoring: Scoring(
win: Player(FirstTo((GreaterEqual, 10))),
),
)
Survival
Here's another config where each player starts off with five lives, and the winning player is the last player remaining. Players get eliminated when they lose all their lives, as shown in the elimination condition.
The full config is in configs/survival.ron
.
Config(
...
scoring: Scoring(
controller: ScoringController(
events: [
(
listener: Death,
score: -1,
),
],
player: (
init: 5,
),
),
win: Player(LastWith((Greater, 0))),
elimination: Player(FirstTo((LessEqual, 0))),
can_join_midgame: false,
),
)
Occupation
Here is a server config that provides a unique game mode that isn't around killing the other players or staying alive. Instead, the game consists of two teams, red and blue, that spawn at fixed locations in their base. When you occupy the base of the other team, your team's score increases. The winner is the team with the highest score after five minutes. If after five minutes the scores are tied, the first team to then increase their score wins. (Also when occupying the other team's base, you can teleport back to your own base with the team-only portal.)
This config also involves the timer which is displayed on the top-right corner of the screen and is used in the win condition.
The full config is in configs/occupation.ron
.
Config(
...
scoring: Scoring(
controller: ScoringController(
events: [
(
listener: Occupation((
owner: Some(TeamId(0)),
p0: (-10, -30),
p1: (10, -25.5),
capture_time: 1,
period: 0.2,
)),
score: 1,
output: Team,
),
(
listener: Occupation((
owner: Some(TeamId(1)),
p0: (-10, 25.5),
p1: (10, 30),
capture_time: 1,
period: 0.2,
)),
score: 1,
output: Team,
),
],
),
win: Team(And([
Extremum( type: Maximum, exclusive: true ),
Timer((LessEqual, 0)),
])),
),
...
timer: Some(Timer(
now: 300,
limit: 0,
counting: Down,
)),
)
Notes
When making your own scoring system and conditions for winning and elimination, here are a few important notes to consider:
- Avoid
Equal
as a comparison operator. If having a goal of the winning player being the first one to 20 kills, you probably don't want to usegoal: FirstTo((Equal, 20))
. This is because it's possible that a player has 19 kills, and then fires two bullets which kill two players at the exact same time and boost their score to 21. In that case, that player lost their chance of winning the game. - Prevent players from joining mid-game. If your scoring system involves the elimination of players, you should add
can_join_midgame: false
to the config to prevent players from joining after being eliminated. You should also do this if players joining the game later on have an advantage (like having much fewer deaths).
Lobby
By default as soon as players join, the game starts. However, you can put players in a "lobby" where players can shoot and kill each other but scoring is disabled before the game starts. This can be done to give players a break after playing the game.
To enable the lobby and have it last for, say 60 seconds, add this to your config:
Config(
...
lobby: Lobby(
time: 60,
),
)
Players can start the game prematurely if they all are ready by running the ready
command (or its alias of r
). If you want to decrease the proportion of players that need to be ready before the game starts, change ready_threshold
to a value in [0, 1].
Timer
A timer can be added while playing the game which can provide useful information to players, such as when the game will end. This timer can be used in the scoring system for the win and elimination conditions.
Here is an example of a timer that starts at zero and counts up forever. This timer is configured to display the raw seconds and use millisecond precision (which is probably overkill).
By default there is no timer.
Config(
...
timer: Some(Timer(
now: 0,
limit: inf,
counting: Up,
precision: Milli,
format: S,
)),
)
Stability
I make no guarantees about the stability of this schema between versions. I will try to ensure backwards compatibility between changes in the patch version (third number of the version), and as of writing I have ensured this (maybe, there might've been breaking changes I made and didn't think much about).
Advanced configuration
Everything that can be configured right now through the server config is just a small sample of what you can actually configure. If you want to add something like an effect zone but for giving players ammo, it's possible to modify the server source code without needing all players to update their game to your specific fork.
Here's a list of ideas that can be implemented for custom server-side configuration that unmodified clients can play with:
- Recoil: When players fire bullets, the server can decrease the player's velocity by a small amount in the opposite direction of the fired bullet.
- Stealing ammo: When a player kills another player, an ammo crate might spawn containing all ammo that player had.
- Player-player collision: Right now the game's physics don't support collision between players. However, this functionality can be hacked on by the server arbitrarily controlling the players' positions and velocities.
Even more advanced configuration would require forking the game, such as if you want to add a new type of weapon.
Tips
Here are some tips to be more efficient when customising a game server:
- Right-click to print position. When in the world, you can right-click (or whatever you choose to bind it to) and the position your cursor's pointing at will be printed to stderr. This can be very helpful when placing special effects.
reload
command. In the server console, you can runreload default
to quickly restart the server, which you might do a lot.
Server console
When starting the server, you have access to a console that allows addition and removal of game servers, without restarting the entire process. Complete documentation for these particular commands isn't provided here but instead available by running help
in this console. Instead what is documented here is game ids and basic usage of running multiple game servers on the same process.
When using this console, I recommend redirecting stderr to a file (or /dev/null) to avoid the logs from distracting you as you write your commands.
Game ids
A spaceships server can run multiple different game servers on the same UDP port. When a player joins the game, the client includes a game id in the request it makes to the server to play the game. The server then takes this id and checks its "routing table" to find a game server the player can join. Game ids are either entered manually when manually entering the address, or automatically with LAN discovery.
The exact mapping of game ids to game servers is up to the server. It is possible to have multiple game ids mapped to the same server.
One game server can be configured to be the default, if it has game id "default". Here, if the user entered a game id that doesn't match any explicit rule in the routing table, the player will join this default server. When you start a server without specifying a game id, it will be configured to be the default server.
Private servers
Game ids can be used to have the server accessible over the internet but restrict who can join the game by requiring users enter a password. Here, the game id is the password. This works because over the internet, it is not possible for clients to gain access to this routing table.
However if the client is on the local network, by default the game id is exposed locally and anyone on the local network can find it out by using LAN discovery. If this is a problem, you can completely disable LAN discovery by adding --lan-discovery disabled
through command line arguments.
Examples
Here are some examples. Lines beginning with $
indicate a shell, while lines beginning with >
indicate the server console.
Multiple servers
Here is an example of how to host multiple game servers on the same process.
$ ./spaceships server maze:configs/maze.ron
> add cave:configs/cave.ron
> add white_noise:configs/white_noise.ron
> route add white_noise noise
> add configs/empty.ron
After running all of this, the game ids maze
and cave
are mapped to the servers with configs configs/maze.ron
and configs/cave.ron
respectively. Game ids white_noise
and noise
are mapped to a single server with config configs/white_noise.ron
. Finally, all other game ids are mapped to configs/empty.ron
by default.
Hosting a private server
Here is how to run just a single server only accessible through a hard to guess game id.
$ ./spaceships server I-L0v3_sP4c35h1p5:configs/private.ron
Make sure that no colons are present in the password or the game will instead be accessible at multiple, possibly easier to guess, game ids. To ensure that everything is working fine after starting the server, run status
and if it prints something like as follows:
> status
Showing status for all game servers:
0: {"I-L0v3_sP4c35h1p5"}
path = configs/private.ron
player count = 0/8
The important part is that only one game id is listed on the third line.