
The community-driven multiplayer shooter.
Challenge your friend to an intense duel, or gather two clans to fight a spectacular war.
Written in modern C++, without a game engine!
Forever free and open-source β€οΈ
Comes with an in-game map Editor!
|
|
|
|
---|---|---|---|
Steam | Windows | Linux | macOS |
Only 29 MB!
The game will start with an interactive Tutorial!
Run the Server Docker
Or download Server AppImage
All archives are digitally signed. You can verify signatures.
![]() |
![]() |
||
---|---|---|---|
Liam @ GamingOnLinux | GitHub Blog | Linux Magazine, Issue 275 | Hacker News Discussion |
π° Are you a journalist? Click here!
- Introduction
- Tech highlights
- Game guide
- How to build
- Contributing
- Credits π΅π±
- The story behind the code
Hypersomnia has been online and playable since 2017. It brings together:
- the tactics of Counter-Strike,
- the dynamics of Hotline Miami,
- the pixel art nostalgia of oldschool RPGs...
- ...and an in-game map editor lets you instantly playtest a work-in-progress map with your friends, even behind a router!
Today, we have 24 unique firearms, 10 community maps, and two game modes: Bomb defusal and Gun game.
We aim to become the ultimate open-source shooter - a grand community project extensible without limit.
Declare allegiance to one of the three factions whose apple of discord is a disparity between prevailing notions of moral excellence:
Metropolis. Atlantis. Resistance.
Will you take revenge for the unethical simulation of an inferior universe? Will you support the cruel experiments to win total control over metempsychosis? Or will you join the underground civilization that awaits the end of war in this dangerous afterlife reality?
-
rectpack2D, written for packing textures, became famous and was used in Assassin's Creed: Valhalla.
- Used also by a drone manufacturing company and in 2 scientific papers.
-
My Entity-Component-System idea from 2013 describes techniques similar to Unity Engine patent from 2018.
-
Networking is based on cross-platform simulation determinism. It is 100% deterministic even across browser and native clients (Windows, Linux, MacOS) and that includes native ARM builds (aarch64)!
- This technique is traditionally used by RTS games with hundreds of continuously moving soldier units.
- It is impractical to continuously update every single one of them through the network.
- Instead, only the player inputs are transmitted ("I moved mouse here", "I pressed this button") - the clients simulate everything else locally, on their own. Think playing chess with your friend over the phone. You won't ever say aloud the entire state of the chessboard, only the movements ("Queen to H5").
- But Hypersomnia is not an RTS - since it's physics-based, it uses floats heavily, not just integers.
- When floating point calculations are involved, simulation determinism becomes extremely hard.
- See why in this excellent article by Glenn Fiedler.
- To achieve it in Hypersomnia, I had to:
- Use the same compiler -
clang
- on all OSes. It's very nice of LLVM to be ieee754-compliant by default. - Pass
/fp:strict
for Windows builds. - Pass
-ffp-model=strict
for the ARM builds. - Replace all math functions like
std::sin
,std::sqrt
by these from STREFLOP. - ..after which
streflop::sqrt
became a huge bottleneck - thankfully, I found another efficient ieee754-compliant implementation.
- Use the same compiler -
- When floating point calculations are involved, simulation determinism becomes extremely hard.
- Apart from worrying about floats..
- I had to watch out even when iterating
std::unordered_map
- often replacing them with deterministically orderedstd::map
. - I had to use a portable RNG (xorshift), ditching the entire
<random>
header (its implementation differs across OSes).
- I had to watch out even when iterating
- Another magical trick was needed to ensure the physics itself is fully deterministic:
- Problem: when a new client connects, it receives the initial physics state - but the packets contain just the body positions, rotations and velocities.
- In particular, it doesn't contain the "hot" physics state such as the already tracked contacts, or the already built/balanced quadtree for fast collision detection.
- What if there are already some players on the server, and several bodies overlap the moment a new player joins?
- The new client will recreate the entire internal physics state on its own, using just the pos/vel/rotation data, but the contacts might be created in a different order than that of the existing clients! The fresh quadtree might end up completely different too from the one that has already seen many insertions/deletions during a long gaming session - so it might later report collisions in different order.
- My solution was one of the greatest stroke of epiphany I ever experienced:
- When a new client connects, force already connected clients to completely rebuild the physics state from the positions, rotations and velocities as if they have just connected themselves. drops the mic
- As long as building the internal physics data from pos/vel/rotation data is deterministic, the in-game clients will have the exact same state as the newly connected client.
- I apply the same principle in many other areas of networking. Throughout my codebase, I call this process cache reinference.
- All this would be very hard to pull off in a commercial engine, or straight impossible in a closed source one.
- Yet in Hypersomnia, you can have thousands of dynamic crates or bullets on the map - as long as there are e.g. two player-controlled characters, the traffic will be around
40 kbit/s (= 5 KB/s)
under a tickrate of 60 Hz. Only player-controlled characters contribute to traffic.- Even the silliest details like bullet shells are fully synchronized without paying for it with network traffic.
- As a bonus, lag is hidden exceptionally well as the game doesn't just mindlessly "extrapolate" frames visually - rather, it simulates the entire game world forward offscreen to accurately predict the future.
- For the very curious - here's an article from my old abandoned blog, showing how I achieved this as early as in 2016.
- This technique is traditionally used by RTS games with hundreds of continuously moving soldier units.
-
Browser clients and native clients can play on the same servers, thanks to libdatachannel and datachannel-wasm.
- You can host a server in the browser and connect to it with a native client...
- ...and vice versa: host a server in the native client and send anyone the link to join from the browser!
-
In the browser version, you can sign in with Discord and play ranked matches to compete on the global Leaderboards!
- You can also associate your Discord account with Steam account so that the Steam version and web version share the same rating!
-
You can host a working game server from the main menu - the game is able to forward ports out of the box!
- The algorithm requires a third party server - in this case, the masterserver (server list keeper) facilitates the traversal.
- Very simple in principle. Using Google STUN servers, we first detect the NAT type for both the client and the server (conic, symmetric or no NAT), as well as the difference in ports between packets outgoing to two different addresses ("port delta").
- Both parties send packets to the masterserver which in turn relays what was the last external port
P
of the other party. - Both parties then spam e.g. 10-20 UDP packets at ports
P + delta * n
. - Even a pair of symmetric NATs will form a connection if they have a deterministic port delta - although after a while. The connection will fail if either the client or the server has a symmetric NAT with randomized port selection. In practice however, most routers are conic which makes punchthrough work instantly.
-
Cute fish and insect AI with flocking behaviors. Full source: movement_path_system.cpp.
- These too are synchronized through the network!
- Other players see fish and insects in the same positions, even though they do not contribute to network traffic.
- Fish and insects react to shots and explosions!
- And once they're scared, they keep closer to members of the same species.
- These too are synchronized through the network!
-
Anyone can host the entire Hypersomnia server infrastructure.
- The Editor, the game server and the masterserver (server list keeper) are all embedded in the same game executable, on every OS.
- You could run a separate server list for your own community around a completely modded version of Hypersomnia!
-
Memory pool implementation with:
- Contiguous storage.
- All game objects in existence are kept linearly in memory (in a simple
std::vector
).- It means blazing-fast iteration of all game objects of the same type, as well as trivial pool serialization.
- All game objects in existence are kept linearly in memory (in a simple
- O(1) allocation (mostly a glorified
push_back
). - O(1) free (via
std::swap
with the last element andpop_back
- a very known idiom). - O(1) dereference.
- The only caveat being: a dereference involves four reads from memory (instead of just one with a direct pointer and two with a pointer + array index):
- It has to first read the
indirection_array
pointer (1).- The indirection array contains the current index of the requested object within the vector of all allocated objects.
- This index is located at
indirection_array[identifier]
(2).
- Then it reads the actual
objects
vector pointer (3). - Finally, it reads the object itself at
objects[indirection_array[identifier]]
(4). - (Note that the
objects
andindirection_array
will usually be cached already so subsequent dereferences will be reduced to two fetches from RAM at most). - This works because
indirection_array
is kept up-to-date whenever objects are allocated or removed.
- Fully resizable. You don't have to pass a maximum pool size. It will expand on its own.
- Fully deterministic. Given the same sequence of allocations and frees starting from a given initial pool state, the allocations will produce the same integer identifiers and objects will have exactly the same order in the linear memory.
- When a client connects and the server sends it the initial world state, it includes the entire internal state of the game object pools.
- It means the pools themselves are a part of the game simulation - I don't have to send any "creation" notification events through the network whenever entities are created as the clients deterministically simulate allocations on their own resulting in identical object identifiers as well as their orders in memory.
- When a client connects and the server sends it the initial world state, it includes the entire internal state of the game object pools.
- Perfectly undoable allocations and frees.
- The Editor requires all commands to implement a deterministic undo/redo, especially ones that create or delete a resource.
- Suppose you create a custom Material in the editor and it now has an integer identifier
I
.- You later set this Material to some walls on the scene. The walls now refer to identifier
I
. - Now you want to undo all the way back to before you even created the Material.
- The Material gets deleted from the pool.
- Now you actually want to redo all of these commands back.
- You first redo the Material allocation command.
- But if the pool uses a non-undoable allocation scheme, the material could now be allocated with the id
I+1
. - This means that the subsequent commands (that alter the walls material) will set an identifier that is now invalid -
I
. - To solve this problem, my memory pool implementation provides
undo_last_allocate
apart from the standardfree
. It's used by the commands that have to undo creating a resource (which means deleting it). It will free the object in a way that the next allocation will result in an identical integer id and internal pool state (with allocated objects order) as before freeing the resource. It is an optional feature for the sake of editor commands that is never used during actual gameplay. Analogously, there is anundo_last_free
.
- You later set this Material to some walls on the scene. The walls now refer to identifier
- Contiguous storage.
-
Built-in self-updater: the game will download and apply updates automatically.
- Not only that, it will verify that the update came from the hardcoded developer public key, with a call to
ssh-keygen
.- If the build hosting were hacked and a malicious game version uploaded, the existing game clients will refuse to apply the updates.
- Not only that, it will verify that the update came from the hardcoded developer public key, with a call to
-
..and I'm signing builds offline with a Trezor hardware wallet. This is how every update looks like on my end:
przecietnylinuksiarz.mp4
-
Discord and Telegram notifications when:
-
Beautifully simple JSON format for the game maps. This short json:
{ "settings": { "ambient_light_color": [53, 25, 102, 255] }, "nodes": [ { "id": "floor", "type": "dev_floor_128", "pos": [640, 0], "size": [1536, 1536] }, { "id": "light", "type": "point_light", "color": [255, 165, 0, 255], "pos": [100, 100], "radius": 700 }, { "id": "wall", "type": "dev_wall_128", "pos": [-384, 0], "size": [512, 1536] }, { "id": "crate", "type": "crate", "pos": [64, 448] }, { "id": "wood1", "type": "hard_wooden_wall", "pos": [192, -192] }, { "id": "wood2", "type": "hard_wooden_wall", "pos": [561.819, 206.436], "size": [384, 128], "rotation": -15 }, { "id": "aquarium", "type": "aquarium", "pos": [1408, 0], "rotation": 90 }, { "id": "baka1", "type": "baka47", "pos": [698, -8] }, { "id": "baka2", "type": "baka47", "pos": [454, -264] } ] }
Instantly produces:
-
And speaking of the Editor..
-
It's created with the excellent ImGui.
-
You're working directly on the game world. 100% WYSIWYG.
-
Supports custom resources. It's enough to paste folders with PNGs, WAVs, OGGs to the map directory..
- ..alt-tab back to the game and it will automatically pick it up!
-
Supports GIFs as well! Just drag&drop them on the scene and they will be animated in-game, right out of the box!
-
It's possible to playtest a work-in-progress map with a single click:
-
After which you instantly appear in the game's server browser:
-
You will enter the game as the host.
-
The connecting clients will automatically download the map in its current version with all its custom resources (like in CS 1.6).
- And they can later create their own remake - maps are saved in JSON, after all!
-
ESC will let you stop the session and go back to the Editor exactly as you left it, enabling ultra-efficient iteration cycles.
-
This is possible because the server, the game and the editor are all within the same executable.
-
The official Discord will also be notified that you're playtesting a map, so others can join in on the fun!
-
-
The Editor took well over a year to implement.
-
It paid off big time - there is now a neat catalogue of community maps - every one of them downloadable.
-
(Main article: GUIDE.md)
(Main article: BUILDING.md)
(Also check out the AGENTS.md file - while a guide for the machines, humans should respect it just as well!)
Pull requests are welcomed, should they even be typo fixes or missing const guarantees. If you plan to add a completely new feature, create a relevant issue so that everybody knows about it, because the project is continuously in a very, very active development and may undergo a revolution when it is the least expected.
A WIP documentation can be found at wiki.
Make sure to check out TeamHypersomnia for other repositories that are useful when setting up your own custom servers.
If you have questions or you fail to build Hypersomnia, reach out on Discord. Or if you just can't wait to utter some brilliant ideas with regard to the game, please do so, too!
Hypersomnia has been in development since 2013 (as seen in the commit history).
It didn't take 10 years of uninterrupted coding, though - in the meantime, I worked commercially to cover my costs of living. I saved money to be able to work less and focus on Hypersomnia. My financial decisions now let me develop the game full-time.
I use a lot of 3rdparty libraries like Box2D
(physics) or yojimbo
(transport layer) - everything not on this list, however, is written pretty much from scratch, in pure C++.
Many believe that writing games without an engine is no more than reinventing the wheel, or put more bluntly, a complete waste of time.
I hope this project serves as a great testament to the opposite.
Had I never embarked on this journey, I would have never made some of the interesting discoveries detailed in Tech highlights section. Video game internals are just so vast and interdisciplinary that they have limitless potential for creative breakthroughs, and it is a waste to never even entertain the idea that some widely used solutions can be replaced by something absolutely ingenious.
To reach me on Matrix, you can hop onto FOSSGralnia chat (I'm friends with them) and ping @pythagoras
.