Skip to content

Commit 2849def

Browse files
committed
A version of doomgeneric adapted to run in modern terminal emulators.
0 parents  commit 2849def

File tree

301 files changed

+161293
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

301 files changed

+161293
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.zig-cache
2+
zig-out
3+
.DS_Store
4+
src/.savegame
5+
output.mp4

README.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Terminal Doom
2+
Terminal Doom enables Doom-based games to play smoothly in modern terminals using the Kitty graphics protocol. Also works
3+
over ssh on fast connections. The engine is based on the amazing [doomgeneric project](https://github.com/ozkl/doomgeneric), while
4+
sound is handled by miniaudio.
5+
6+
`doom1.wad` is included in the repository; other wad files are available on various online sites.
7+
8+
Check it out (feel free to crank up sound):
9+
10+
[![Demo]](https://github.com/user-attachments/assets/8ca127d7-23f6-45cd-82e9-49c51c4cdc42)
11+
12+
## Building
13+
Build with Zig v.0.13 (which includes a C compiler for the doomgeneric parts).
14+
15+
There are no system dependencies, just clone and build:
16+
17+
`zig build -Doptimize=ReleaseFast`
18+
19+
Run with `zig-out/bin/terminal-doom`
20+
21+
Terminal Doom uses the [libvaxis Zig library](https://github.com/rockorager/libvaxis) to render and handle keyboard and mouse events.
22+
If you ever want to make a TUI app, I highly recommend this library.
23+
24+
### Sound support
25+
Add the `-Dsound=true` flag if you want compile with sound support.
26+
27+
All sound effects are included, and a few music tracks. You can download
28+
and add additional music tracks (mp3) yourself. Terminal Doom will automatically pick them up from the `sound` directory. See the sound section for naming.
29+
30+
### Where does it run?
31+
Tested on macOS and Linux. Compiles on Windows as well, but no terminal there seems to run it (WezTerm gets close in ssh local mode)
32+
33+
Currently works best in Ghostty and Kitty as these have solid implementations of the required specs. WezTerm should
34+
work if you use 'f' instead of ctrl keys for firing the gun.
35+
36+
## Playing
37+
You can play keyboard-only or in combination with a mouse. You can disable/enable mouse at any time by pressing `m` (useful if playing with keyboard on a laptop to avoid spurious input from the trackpad)
38+
39+
When using a mouse, make sure you adjust sensitivity in the Options menu if it's too fast or slow. Also try adjusting sensitivity on your mouse if it has buttons for this. Once sensitivity is right, playing with a mouse/keyboard combo is pretty efficient.
40+
That said, mouse reporting in terminals do have limitations.
41+
42+
| Action | Keys/Mouse Actions |
43+
|---------------------------|-------------------------------------|
44+
| Walk / rotate | Arrow keys or mouse. `j`, `l` also rotates.|
45+
| Walk / stride | `wasd` |
46+
| Fire | `f`, `i`, control keys, mouse click |
47+
| Use/open | Spacebar, right mouse click |
48+
| Stride left/right | Alt+arrow keys, `a`, `d` |
49+
| Quit | `Ctrl+c` |
50+
| Disable/enable mouse | `m` |
51+
| Disable/enable scaling | `u` |
52+
53+
54+
Most other Doom keys should work as well, such as Tab for map and F5 for adjusting detail level.
55+
56+
## How it works
57+
58+
## Rendering
59+
While the Kitty graphics protocol is primarily intended to display images, modern terminals and
60+
computers are *fast*, with high memory bandwidth, SIMD support, and discrete GPUs. There's plenty
61+
of juice available to run this classic game smoothly over a text protocol.
62+
63+
Here's how it works: on every frame, doomgeneric calls `DG_DrawFrame`. Our job is now to turn
64+
the pixel data into a base64 encoded payload. This payload is then split into 4K chunks,
65+
each chunk wrapped by the Kitty protocol envelope. Actually, some terminals work without
66+
chunking, which is even faster, but that's not spec compliant and e.g. Kitty itself fails
67+
without chunking (thanks to rockorager for pointing this out)
68+
69+
With the encoded message ready, we now:
70+
71+
1. Set synchronized output (mode 2026)
72+
2. Clear the screen
73+
3. Display the frame by sending the Kitty graphics message
74+
4. Reset synchronized output to flush updates to screen
75+
5. Handle any keyboard input
76+
77+
With the latest version, all of this is outsourced to libvaxis.
78+
79+
This sequence repeats for every frame. While this is enough to run Doom smoothly in a modern terminal, there are many optimizations that can be done, including SIMDifying pixel encoding.
80+
81+
## Sound
82+
The history of Doom has many interesting facets, and its sound library is no different. You can read about it [here](https://doomwiki.org/wiki/Origins_of_Doom_sounds)
83+
84+
Terminal Doom's sound support originally worked by calling out SDL2, but that had a couple of problems. First of all, the implementation
85+
from doomgeneric was complicated and large. Second, depending on SDL2 makes building harder.
86+
87+
The solution I can up was this:
88+
89+
1. Ditch all the complex midi sequencing and mixing logic.
90+
2. Make the wav files part of the project
91+
3. Call out to miniaudio to play the wav file whose name matches the sfx name.
92+
93+
While large, miniaudio is a single header file, it's portable, and has a very easy to use API.
94+
95+
### Adding addition music tracks
96+
Terminal Doom ships with a few tracks, such as for the intro and the first level.
97+
You can add additional mp3's to the `sound` directory. For Terminal Doom to pick these up, they must be named
98+
according to the Doom convention:
99+
100+
```
101+
d_e1m1.mp3
102+
d_e1m2.mp3
103+
d_e1m3.mp3
104+
d_e1m4.mp3
105+
d_e1m5.mp3
106+
d_e1m6.mp3
107+
d_e1m7.mp3
108+
d_e1m8.mp3
109+
d_e1m9.mp3
110+
d_inter.mp3
111+
d_intro.mp3
112+
d_victor.mp3
113+
```
114+
115+
## Supported games
116+
These should all work:
117+
118+
```
119+
doom2.wad
120+
plutonia.wad
121+
tnt.wad
122+
doom.wad
123+
doom1.wad
124+
chex.wad
125+
hacx.wad
126+
freedm.wad
127+
freedoom2.wad
128+
freedoom1.wad
129+
```
130+
## LICENSE
131+
As Terminal Doom is based on the doomgeneric project, the project as a whole is licensed under GPL2.
132+
133+
The Zig-based renderer/handler, build file, and MiniAudio sound integration is licensed under MIT.

build.zig

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//! SPDX-License-Identifier: GPL-2-0 MIT
2+
const std = @import("std");
3+
const builtin = @import("builtin");
4+
5+
pub fn build(b: *std.Build) void {
6+
const target = b.standardTargetOptions(.{});
7+
const optimize = b.standardOptimizeOption(.{});
8+
9+
const vaxis = b.dependency("vaxis", .{
10+
.target = target,
11+
.optimize = optimize,
12+
});
13+
14+
const doom = b.addExecutable(.{
15+
.name = "terminal-doom",
16+
.root_source_file = b.path("src/main.zig"),
17+
.target = target,
18+
.optimize = optimize,
19+
});
20+
doom.root_module.addImport("vaxis", vaxis.module("vaxis"));
21+
22+
const sound = b.option(bool, "sound", "If set to true, compile sound support using miniaudio. Default is false.") orelse false;
23+
24+
// const music = b.option(bool, "music", "If set to true, a music library for Doom will be fetched. Default is false.") orelse false;
25+
// const music_library = b.lazyDependency("music_library", .{
26+
// .target = target,
27+
// .optimize = optimize,
28+
// });
29+
30+
const cflags_sound = if (sound) [_][]const u8{ "-DFEATURE_SOUND", "-Isrc/doomgeneric" } else [_][]const u8{ "", "" };
31+
const cflags = [_][]const u8{ "-D_THREADSAFE", "-fno-sanitize=undefined" } ++ cflags_sound;
32+
33+
const sourcefiles = [_][]const u8{
34+
"src/doomgeneric/dummy.c",
35+
"src/doomgeneric/am_map.c",
36+
"src/doomgeneric/doomdef.c",
37+
"src/doomgeneric/doomstat.c",
38+
"src/doomgeneric/dstrings.c",
39+
"src/doomgeneric/d_event.c",
40+
"src/doomgeneric/d_items.c",
41+
"src/doomgeneric/d_iwad.c",
42+
"src/doomgeneric/d_loop.c",
43+
"src/doomgeneric/d_main.c",
44+
"src/doomgeneric/d_mode.c",
45+
"src/doomgeneric/d_net.c",
46+
"src/doomgeneric/f_finale.c",
47+
"src/doomgeneric/f_wipe.c",
48+
"src/doomgeneric/g_game.c",
49+
"src/doomgeneric/hu_lib.c",
50+
"src/doomgeneric/hu_stuff.c",
51+
"src/doomgeneric/info.c",
52+
"src/doomgeneric/i_cdmus.c",
53+
"src/doomgeneric/i_endoom.c",
54+
"src/doomgeneric/i_joystick.c",
55+
"src/doomgeneric/i_scale.c",
56+
"src/doomgeneric/i_sound.c",
57+
"src/doomgeneric/i_system.c",
58+
"src/doomgeneric/i_timer.c",
59+
"src/doomgeneric/memio.c",
60+
"src/doomgeneric/m_argv.c",
61+
"src/doomgeneric/m_bbox.c",
62+
"src/doomgeneric/m_cheat.c",
63+
"src/doomgeneric/m_config.c",
64+
"src/doomgeneric/m_controls.c",
65+
"src/doomgeneric/m_fixed.c",
66+
"src/doomgeneric/m_menu.c",
67+
"src/doomgeneric/m_misc.c",
68+
"src/doomgeneric/m_random.c",
69+
"src/doomgeneric/p_ceilng.c",
70+
"src/doomgeneric/p_doors.c",
71+
"src/doomgeneric/p_enemy.c",
72+
"src/doomgeneric/p_floor.c",
73+
"src/doomgeneric/p_inter.c",
74+
"src/doomgeneric/p_lights.c",
75+
"src/doomgeneric/p_map.c",
76+
"src/doomgeneric/p_maputl.c",
77+
"src/doomgeneric/p_mobj.c",
78+
"src/doomgeneric/p_plats.c",
79+
"src/doomgeneric/p_pspr.c",
80+
"src/doomgeneric/p_saveg.c",
81+
"src/doomgeneric/p_setup.c",
82+
"src/doomgeneric/p_sight.c",
83+
"src/doomgeneric/p_spec.c",
84+
"src/doomgeneric/p_switch.c",
85+
"src/doomgeneric/p_telept.c",
86+
"src/doomgeneric/p_tick.c",
87+
"src/doomgeneric/p_user.c",
88+
"src/doomgeneric/r_bsp.c",
89+
"src/doomgeneric/r_data.c",
90+
"src/doomgeneric/r_draw.c",
91+
"src/doomgeneric/r_main.c",
92+
"src/doomgeneric/r_plane.c",
93+
"src/doomgeneric/r_segs.c",
94+
"src/doomgeneric/r_sky.c",
95+
"src/doomgeneric/r_things.c",
96+
"src/doomgeneric/sha1.c",
97+
"src/doomgeneric/sounds.c",
98+
"src/doomgeneric/statdump.c",
99+
"src/doomgeneric/st_lib.c",
100+
"src/doomgeneric/st_stuff.c",
101+
"src/doomgeneric/s_sound.c",
102+
"src/doomgeneric/tables.c",
103+
"src/doomgeneric/v_video.c",
104+
"src/doomgeneric/wi_stuff.c",
105+
"src/doomgeneric/w_checksum.c",
106+
"src/doomgeneric/w_file.c",
107+
"src/doomgeneric/w_main.c",
108+
"src/doomgeneric/w_wad.c",
109+
"src/doomgeneric/z_zone.c",
110+
"src/doomgeneric/w_file_stdc.c",
111+
"src/doomgeneric/i_input.c",
112+
"src/doomgeneric/i_video.c",
113+
"src/doomgeneric/doomgeneric.c",
114+
};
115+
116+
const sourcefiles_sound = [_][]const u8{"src/miniaudio/doom_miniaudio_sound_bridge.c"};
117+
118+
inline for (sourcefiles) |src| {
119+
doom.addCSourceFile(.{ .file = b.path(src), .flags = &cflags });
120+
}
121+
122+
if (sound) {
123+
inline for (sourcefiles_sound) |src| {
124+
doom.addCSourceFile(.{ .file = b.path(src), .flags = &cflags });
125+
}
126+
}
127+
128+
doom.linkLibC();
129+
b.installArtifact(doom);
130+
}

build.zig.zon

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.{
2+
.name = "terminal-doom",
3+
.version = "0.1.0",
4+
.dependencies = .{
5+
.vaxis = .{
6+
.url = "git+https://github.com/rockorager/libvaxis#f0eaa3c831f6376e7fa0519c275cdcb764580e12",
7+
.hash = "1220523a3a301cbfbd83a374e2a69073fa14b211f2f6aaa4fc9497c936feaa67f738",
8+
},
9+
},
10+
.paths = .{
11+
"build.zig",
12+
"build.zig.zon",
13+
"README.md",
14+
"src",
15+
},
16+
}

doom1.wad

4 MB
Binary file not shown.

0 commit comments

Comments
 (0)