aheck/dukebuilder
A Qt-based level editor for Duke 3D. Contribute to aheck/dukebuilder development by creating an account on GitHub.
0 stars · 0 forks
README
Duke Builder An experimental level editor for Duke 3D built with Qt. 2D mode:
3D mode:
Requirements
Conan 2 Meson Ninja A C++17 compiler A configured and built ../libduke checkout with its renderer enabled Desktop OpenGL 4.1
Create a Conan profile once if you do not already have one: conan profile detect Install the dependencies and generate the Meson native file: conan install . --output-folder=conan --build=missing -s build_type=Debug Build the libduke renderer (after its Conan dependency setup): meson setup ../libduke/build ../libduke \ --native-file ../libduke/build/conan_meson_native.ini \ -Drenderer=enabled meson compile -C ../libduke/build Duke Builder statically links libduke and libduke-render from ../libduke/build. Both archives are required; no libduke shared libraries are needed at runtime. On Windows, the build explicitly selects libduke.a and libduke-render.a when present, instead of assuming MSVC-style .lib filenames. Otherwise it uses the compiler's normal static-library lookup. Build both projects for the same target architecture with compatible toolchains; renaming an archive does not make incompatible object files compatible. Qt's OpenGLWidgets module hosts the renderer; no Sokol window or event loop is used. For static Qt on Linux, configuration checks whether Qt's declared dependencies can link its hashing implementation. Only if that fails and adding libb2 fixes it does the build add the workaround for incomplete Qt package metadata. Windows and shared Qt builds do not acquire an explicit b2 dependency from this workaround. Configure and build the application: meson setup build --native-file conan/conan_meson_native.ini --buildtype debug -Db_sanitize=address meson compile -C build Run it: ./build/dukebuilder Shortcut help Help → 2D Mode Shortcuts and Help → 3D Mode Shortcuts open resizable, scrollable rich-text references. F1 opens the reference for the current mode. The windows can stay open while you edit; reopening one brings the same window forward. Opening help releases the mouse in 3D. Click the viewport to resume mouse navigation afterward. Checking a map Use Tools → Check Map (F4) to run the same validation used by Save, without writing a file. It checks exported geometry, references, field ranges, format limits, sprite placement, and the player start. Finish or cancel any unfinished drawing before checking. Checking stops at the first blocking error. Choose Show in Map to select and center the affected sector, line side, sprite, or player start in 2D with its properties visible. Fix the problem and press F4 again. Map-wide errors have no object to select. A successful check confirms that save validation passes; it does not simulate gameplay or validate the behavior of tagged effects. Checking does not change map data or undo history. 3D mode uses less restrictive preview validation: it retains field-range and reference checks but permits intersecting walls and imported effect geometry. Existing sprite sector membership is preserved for rendering. This lets maps such as the original E1L1 enter 3D mode even when strict geometry checks fail. Save and Check Map still apply the full validation rules. Autosave and recovery Duke Builder writes a recovery snapshot every minute while there are unsaved changes. These snapshots are separate from your .map files and do not change the saved/unsaved state. They preserve unfinished drawings, untextured sprites, and other work that cannot yet be exported as a valid Build map. Active drags are skipped until the next interval. Both 2D and 3D edits are included; the 3D preview's temporary player position is never stored. After an interrupted session, startup offers Recover, Discard, or Later. Recovery opens the work as an unsaved map and requires Save As; it never overwrites the original automatically. Later leaves the snapshot for a future launch. If multiple interrupted sessions exist, the newest is offered first; recovering one leaves the others available on a subsequent launch. Undo history and camera/selection state are not recovered. Successful saves, successful New/Open operations, and an accepted normal close remove the current session's recovery snapshot. Cancelling a close or failing a save keeps it. Each running instance has its own locked snapshot, replaced atomically. Invalid or unsupported snapshots are reported and left on disk. Autosave write errors appear in the status bar. Snapshots are stored in the recovery subdirectory of Qt's application-local data directory (normally $XDG_DATA_HOME/Duke Builder/Duke Builder on Linux, falling back to ~/.local/share/Duke Builder/Duke Builder, and beneath %LOCALAPPDATA% on Windows). No game artwork is copied into recovery files. Undo and redo Use Ctrl+Z to undo and Ctrl+Y or Ctrl+Shift+Z to redo in either 2D or 3D. The Edit menu shows the operation that will be undone or redone. Drawing a completed shape, dragging a selection, deleting geometry, joining sectors, and changing properties or textures are undoable. Repeated 3D wheel or arrow edits are grouped while the target, operation and modifiers stay the same and successive edits are less than half a second apart. Undo restores map data and selection in the current editing mode without moving the camera or changing zoom. An active unfinished drawing is cancelled first. Opening or creating a map clears history; saving retains it and marks that state as saved. New edits after undo discard the redo branch. History retains at most 100 operations, dropping older snapshots above an estimated 128 MiB budget (always keeping the latest operation). History is kept only for this session. 2D editor controls
Left click: place connected wall vertices. Lines can be drawn at any angle. Click the first vertex: close the current shape and create a sector. Click an existing wall: connect to it, splitting it at the connection if needed. The drawing finishes automatically when the chain and existing walls form a sector. Right click or Enter: finish the current line chain. It is kept only if its walls, together with existing walls, create a sector. Backspace: remove the last point from the active drawing. Escape: cancel the active drawing. Middle-mouse drag: pan the map. Mouse wheel: zoom around the cursor. Zoom: the default 100% is a level-design working scale (1024 map units span about 82 pixels). Use the status-bar zoom selector to return to 100%. [ / ]: decrease or increase the snapping grid size, also selectable in the status bar. Sizes range from 1 to 4096 map units; the default is 256 (about 20 pixels at 100% zoom). The drawn grid uses this same spacing at every zoom; every eighth line is brighter for orientation. G or the Grid toolbar button: show or hide the grid. F11 / View → Reorient Grid to Line: with one line selected, rotate the nearest grid axis parallel to it. Drawing and vertex-insertion snapping follow the rotated grid; its origin and spacing stay unchanged. F12 / View → Reset Grid Orientation: restore the default grid axes. T: enter Sprites mode. Lines mode: select one or more lines and press Delete to remove them. Connected selected lines collapse onto their lowest-numbered endpoint, keeping the remaining sector boundaries closed. Surviving sectors retain their properties and relative order. Sectors collapsed below three edges disappear. Vertices mode: hover over a line to preview a new vertex; double-click to split the line there. The preview snaps to grid crossings along the line; hold Alt for a free position on the line. Shared walls split on both sides. Right-drag selected vertices to move them: the grabbed vertex snaps to the active grid, including its rotation. Hold Alt to move freely. Multiple selected vertices move together without changing their relative positions. Sprites mode: right-click empty space to add a sprite; left-click or rubber-band to select; right-drag to move; right-click a sprite to choose its texture; Delete removes selected sprites. Player start: the permanent arrow can be selected and right-dragged only in Sprites mode; it has no selectable texture and is omitted from selections containing ordinary sprites. Alt: temporarily disable snapping while placing or previewing a point.
Drawing snaps to existing vertices first, then to nearby walls, and otherwise to the grid. The wall preview shows where the new connection vertex will be placed; click to start or finish the sector there. Sector fills are shown when a chain is closed with three or more walls. The Editor toolbar's Floor textures and Ceiling textures buttons tile the corresponding images inside sectors. Plain fill restores the original untextured appearance (the default). Missing textures use the plain fill. These are 2D previews; selection and hover highlighting remain visible. The Texture Browser has a category sidebar, including All, Used in this map, and Others for unmapped tiles. Search accepts tile numbers, original tile names, and aliases such as "pistol" or "pig cop"; multiple words narrow the results together with the selected category. Double-click a tile to select it. Used textures refresh whenever the browser opens and include both wall sides, active overlays, floors, ceilings, and sprites. The catalog uses the original Atomic Edition tile definitions, with categories and animation-family names added by the editor. Surface categories also include tile IDs used on walls, floors, and ceilings in the 41 shipped Atomic Edition maps, covering unnamed building materials. This metadata is bundled; no game archive is needed to build the catalog. Original tiles without a known name or family remain searchable by number in Others. Custom GRPs may replace the artwork at those IDs, so names describe the original game. To add a neighboring sector, start on an existing wall, draw the new boundary, and click back on the existing boundary. The sector finishes automatically; there is no need to retrace the shared wall. Connection points split existing walls as needed. Boundaries shared by two sectors automatically become two-sided walls and appear red; outer boundaries remain light gray. Shared vertices move both sides together. Hover and selection highlighting still apply to either kind of wall. When a drawn line splits an existing sector, both pieces keep that sector's floor and ceiling heights and textures. This also applies to raised inner sectors. New rooms attached to existing sector vertices inherit that sector's floor and ceiling heights and textures. If several sectors qualify, the first attachment point in drawing order chooses the source, with the lowest sector number breaking ties. Other properties are not copied, and existing rooms keep their own heights and textures. Drawing a closed sector entirely inside another sector creates an inner loop in the surrounding room and two-sided walls connecting the sectors. Raise the inner floor (use a smaller Z value) to make a box or platform. Maps saved by older versions with disconnected overlapping sectors need their topology repaired; opening them does not automatically join intentional overlaps. Maps containing overlapping rooms still allow drawing in non-overlapping areas: create rooms, extend ordinary rooms, or split them between existing vertices. Only the sectors touched by the drawing are reconstructed; unrelated rooms, including stacked rooms, keep their geometry and properties. Drawings that affect an overlapping sector or unsupported loops are rejected when completed, with a status message and no change to the map. Insert vertices before drawing across existing wall edges. Splits that would change the first wall used by a slope or relative texture alignment are also rejected. Editing directly within stacked rooms does not yet have a floor-selection context. In Lines mode, select one wall to edit its texture, overlay texture, shade, palette, texture repeat and panning, flags (cstat), hitag, and lotag. For a two-sided wall, use the Properties panel's Side dropdown to choose Front or Back, labeled with its sector number. The tick on the selected line points toward the active side. Each side retains its own values; a one-sided wall shows only its available side. Sprite lotag suggestions follow the selected tile: Sector Effectors show effects, standard enemies and supported pickups show minimum difficulty, and player sprites show multiplayer start types. Other recognized special sprites explain channels, sounds, waypoints, lighting phase, or speed in the lotag tooltip. Unknown tiles keep a numeric editor. All types accept custom numeric tags; these suggestions assume standard Duke 3D / Atomic tile definitions, which mods can change. Changing a sprite texture refreshes suggestions without altering tags. Sanitizers are disabled by default. The debug example above explicitly enables AddressSanitizer; omit that flag if your development toolchain does not support it. For a release build, use Release for Conan's build_type and a separate build directory, explicitly disabling sanitizers: conan install . --output-folder=conan-release --build=missing -s build_type=Release meson setup build-release --native-file=conan-release/conan_meson_native.ini --buildtype=release -Db_sanitize=none meson compile -C build-release Existing build directories retain their configured options. Before using an existing directory for a release, set meson configure build-release --buildtype=release -Db_sanitize=none and rebuild. Switching only --buildtype does not clear an explicitly enabled sanitizer. Opening and saving maps Use File → Open Map (Ctrl+O) to open a version-7 or version-8 Build .map file. The view centers on the player start, and Save uses the opened filename. An unsuccessful open leaves the current map intact. New, Open, and closing the application prompt to save unsaved changes first; choose Discard to proceed without saving or Cancel to keep editing. A canceled or failed save stops the operation, including quitting. File → Recent Files lists the ten most recently opened or saved maps, newest first, and remembers them between sessions. Select a path to reopen it, or use Clear Recent Files to clear the list. Reopening a recent map uses the same unsaved-changes prompt as Open. Imported maps retain sector loops, independent portal-side properties, sprites, and player start information. Connected inner sectors remain editable after reopening. Overlapping sectors can be edited using an explicit drawing scope; independent rooms are not connected merely because their coordinates coincide. Empty inner loops remain editable without filling their interiors. Ambiguous and unsupported effect-geometry operations are rejected. Saving applies the validation rules below; some original effect geometry may be opened but cannot yet be saved. Editing overlapping rooms Overlapping object clicks offer a target chooser. Starting a drawing at an ambiguous location asks which sector to attach to and grays out the others. Alternatively, use Tools → Isolate selected sectors or Edit height range… to set an optional editing scope. Grayed-out geometry is excluded from picking, snapping, and drawing. The status bar shows the scope; Show all sectors resets it. Height filtering uses Build Z (smaller values are higher), including slope bounds. A filter containing ambiguous overlapping rooms still does not authorize merging them: the drawing is rejected until its target is resolved. Draw independent sector starts a separate room without attaching to existing geometry, even at coincident coordinates. Scoped drawing supports splitting rooms, inserting inner loops, and attaching rooms. Intersections with editable boundaries are split transactionally; real portal counterparts are updated even when the neighbor is outside the filter. Unrelated overlapping rooms are retained. Drawing and moving geometry are undoable; undo/redo also restores the editing scope. Invalid changed boundaries are rolled back instead of silently deleting sectors. Topology operations other than drawing may reset the scope when sector identities change. Unsupported splits that would change a slope or relative texture alignment's first wall, and ambiguous effect loops, remain blocked with an explanation. Sprite creation in overlapping rooms also resolves the target sector and uses its local floor height. Use File → Save (Ctrl+S) or Save As (Ctrl+Shift+S) to write a Build .map file. Saving automatically uses version 7 for maps with at most 1,024 sectors, 8,192 wall sides, and 4,096 sprites; exceeding any of those limits selects version 8 (up to 4,096 sectors, 16,384 wall sides, and 16,384 sprites). Shared lines count as two wall sides. This also means a version-8 map that fits the version-7 limits is saved as version 7. Save remembers the filename until you start a new map. Existing files are replaced only after validation and writing succeed. Finish or cancel any active drawing before saving. The player start must lie inside a closed sector, between its ceiling and floor. Sprites must have a texture and lie inside a sector. Errors identify invalid placement, geometry, field values, or format limits so they can be corrected in the editor. Export preserves numeric X/Y/Z coordinates, rounds fractional coordinates to integers, and converts degree angles to Build's 0–2047 angle range. Sector numbers and first-wall selections are preserved; shared lines produce two linked wall records with independent properties. Sprite and player sector numbers are determined from the exported geometry, retaining imported sector membership when it still matches the position. Ambiguous placement inside overlapping sectors without a matching imported membership is rejected. The classic limits are 1,024 sectors, 8,192 wall sides, 4,096 sprites, and texture numbers 0–6,143, matching the original Duke 3D definitions. Textures are referenced by tile number; their artwork is supplied by the game. Configure the executable in Settings → EDuke32, then use Testing → Run in eDuke32 (F9) to test the current map. New or modified maps are saved first; canceling or failing to save stops the launch. The executable receives -usecwd -nosetup -j <map directory> -map <map filename> -s#, mapping Piece of Cake through Damn I'm Good to EDuke32 skills 1 through 4. Enemies: Off adds EDuke32's -m option before the skill option, so the no-monsters flag and selected difficulty both take effect. On Windows, select eduke32.exe; the launcher uses its absolute path and passes arguments directly through Qt, supporting spaces in both the executable and map paths without a command shell. Run geometry and map open/save regression tests with meson test -C build. GRP File Manager Select a file to preview CON/text contents, MAP information, or paged ART thumbnails. ART previews use the archive's PALETTE.DAT when available, otherwise a labeled grayscale palette. Text previews show up to 256 KiB. Previews include unsaved archive edits. Double-click a MAP to open an unsaved copy in DukeBuilder; saving that map asks for a separate filename and does not modify the archive. A compact toolbar provides the common archive actions; hover an icon for its label. The File and Edit menus contain all commands. Right-click the file list for extraction, replacement, deletion, append, and undo/redo commands. Right-clicking within a multi-selection keeps that selection. The status bar shows file count, total contents size, selected count, and pending changes. Shortcuts: Ctrl+N New, Ctrl+O Open, Ctrl+S Save, Ctrl+Shift+S Save As, Ctrl+I Append, Ctrl+E Extract Selected, Ctrl+Shift+E Extract All, Ctrl+A Select All, and Delete to delete selected entries. Standard shortcuts follow platform conventions. The Edit menu provides undo/redo for appending, replacing, and deleting files (Ctrl+Z / Ctrl+Y). A multi-file operation is one undo step. The Change column marks added and replaced entries; the status bar also counts pending deletions. Saving resets the change indicators w

