Download

← All updates

Update

quark 1.18.19

Everything that changed in this version, and why.

Data-driven weapons and the weapons editor, the fourth editor in the series after the bot brains (1.18.11), the physics editor and the gamemode editor (1.18.17). The owner wants to create and modify weapons, test them against targets and bots, write custom weapon logic in code / blocks / a graph, and compare damage and DPS with other games. For an edited weapon to change the real game, every weapon number is now a field of the per-thread tuned rules, and a set's optional hit / kill logic runs on the server. Design: tools/weapon_editor/DESIGN.md. No network protocol, map or demo format change: the set travels in the existing EV_RULES text. Owner's call (2026-10-24): a "new" weapon is a variant of one of the 11 (its own name, numbers and logic, in that weapon's slot with its model and sounds); extra weapon slots are a possible follow-up.

Weapon sets (src/game/weaponset.{h,cpp}, new)

  • Fields: 280 in all: every WeaponDef value per weapon (refire, damage, range, projectile speed, splash radius, spread, scoped spread, zoom, pellets, gravity, lifetime, semi-auto; the kind, projectile / beam / sticky, is read-only) plus the numbers that were loose constants: head damage, knockback, falloff (start, end, minimum, or a points / steps curve per weapon), direct-hit damage, self damage, wallbang, and every alt-fire constant (rifle scoped refire; rocket lob speed and gravity; knife throw speed, gravity, life, away time and swing reach; shotgun bounces and bounce damage; laser pull scale and damage; the bubble stream's seven numbers; syringe throw and heal; sticky max, arm time and inherit). Ranges are clamped like CustomRules::fromText clamps (nearest bound; nan = the default).
  • Why absolute values: the mode editor's weapon= multipliers already scale fire rate, damage and projectile speed. A weapon set says what a weapon IS; the multipliers still apply on top, so the two editors never overwrite each other.
  • Format: .qweapons, the bot brain grammar (tools/bots/GRAMMAR.md) with a weapons "<name>" header, one value per param (no skill levels), label <weapon> "<NAME>", and slots hit, hit <weapon>, kill, kill <weapon> (a weapon's own slot runs instead of the general one). Reusing the grammar means the bot editor's tokenizer, printer, Blocks and Graph code carry over to the editor. Canonical printer: print(parse(text)) == text; errors with line:col.
  • Logic: hit outputs damage (starts at what the game worked out after falloff, head, wallbang and splash), knock (x1) and heal (to the shooter, only when the hit hurt); kill outputs heal and refund (reset the shooter's cooldown). Sensors: weapon, alt, dist, head, direct, splash, self, airborne, both players' health and speed, time, streak (hits on this victim less than 1 s apart), kills this life. A tree-walk interpreter that allocates nothing per call: 25-60 ns a call, which matters because the laser hits up to 1000 times a second per player. Logic runs on the server only (damage is server-side).
  • Cost when unused: none per shot. The tuned table is rebuilt only when the rules change (rebuild() in customrules.cpp compiles the set once), and the per-shot lookups are hoisted out of the pellet loop. A server without a set takes the same path as before.
  • Bit-identical default: with no set, and with the full standard set text loaded, every tuned value equals the old constants (test), so standard play is unchanged.

Custom modes and the network (src/game/customrules.{h,cpp}, server.cpp)

  • weaponset=<name> in a .qmode names custom_weapons/<name>.qweapons; loadCustomMode reads and validates it. The file form keeps only the name (the set has its own file); the network form (toNetText, EV_RULES) carries the text as wsline= lines so every client compiles the same set and predicts refire, spread, projectile speed and drop and the scope with the server's numbers.
  • Size limit: rules text over 24,000 bytes (100 chunks of 240) is refused for the set, and the server sends the rules without it, logging why. Why 100 and not the 255 the chunk counter allows: every chunk is queued at once on join next to up to 62 welcome events, and kMaxUnackedEvents is 256, so a 255-chunk set would get the joining client dropped. The full standard set is 44 chunks; a set's text is capped at 20,000 bytes.
  • The gamemode and physics editors keep weaponset= verbatim like any line they don't own (agreed with those threads).
  • Ranked and time trials never run custom modes, so they always play the standard weapons.

Weapon code reads the tuned values (gamedefs.h, server.cpp, bots.cpp, client/game.cpp)

  • WeaponExtra (per weapon) and WeaponAlt (alt-fire numbers), read through weaponExtra(w) / weaponAlt(); weaponFalloff(). headDamage, projectileGravity, bubbleStreamCount, stickyInherit and shotRefire read them; the old constants remain as the standard values.
  • server.cpp: a weaponHit() wrapper sends a hit straight to damagePlayer when no set has logic, else runs the hit slot, then damage, heal and the kill slot. damagePlayer / killPlayer themselves are untouched (they carry the mode logic hooks). Projectiles remember whether they were alt fire; explode() takes it.
  • The knife's reach now comes from the tuned table (kKnifeRange was computed once at startup and ignored custom rules); same 3.5 m by default.
  • Falloff for projectiles and blasts is measured from the shooter's eye to the hit (not distance travelled).
  • bots.cpp: the knife-throw lead uses the tuned throw speed and gravity. client/game.cpp: the throw away-time, shotgun bounces and sticky max shown in the HUD use the tuned values.

Tools for the editor

  • quark_tests exportweapons [out.json] (the catalog: fields, weapons, slots, sensors, builtins, the standard text), checkweapons <file> (errors as JSON, exit 1), weaponsast <file>, printweapons <file>.
  • quark_maptool (tools/weapon_editor/native): wpnparams, wpncheck, wpnstats (per weapon and fire mode: damage, shots and time to kill, DPS, body and head, travel time and falloff at distances; the same rows the page computes, checked against it), wpnrange (the game's real Server: a shooter with perfect, head, body or bot aim against still or moving dummy targets on a built-in range or any map, deterministic per seed; 10 s of rifle at 1000 Hz runs in about 0.09 s). sim / simbatch (bot editor) take weapons, qmode and weaponsAllowed, echo weaponSet, and report per-weapon kills, damage, accuracy and time to kill.

The weapons editor page (tools/weapon_editor, 127.0.0.1:8773)

  • Tabs: Weapon (every field with slider, standard value and difference, numbers card, damage-vs-distance), Values (sortable table of every weapon and fire mode, body vs head, standard vs edited, DPS / TTK bars, falloff curves), Other games, Code / Blocks / Graph (one AST, "try a hit" runs a slot and marks the lines it ran), Range (top-down and 3D replay), Fights (live and batch bot matches). Saves custom_weapons/<name>.qweapons; "Write wpn-<set>.qmode" makes a custom mode that plays it (keeping every other line of an existing one). MOCK fallback when the helper is missing. Dense layout for a 2560x1440 screen.
  • Other games: 19 games, 224 weapons with sources and patch / date (web/data/games.json, SOURCES.md, rebuilt by tools/weapon_editor/build_games.py): Doom, Quake 1 / 2 / 3 / Live / Champions, UT99 / 2004 / 3, Xonotic, Warfork, TF2, Overwatch 2, VALORANT, CS2, Apex Legends, THE FINALS, Deadlock, Titanfall 2. Compared by shots and time to kill against each game's own health pool (or a hero's), with a per-game damage scale (UT99 Hardcore is x1.5) and "closest weapons" for each Quark weapon. Not found with citable numbers: Halo Infinite, Splitgate, Diabotical, Doom Eternal, Siege, Marvel Rivals, Fortnite, CoD, Battlefield.
  • Quark Project app: Weapons Editor tab under the Editor Manager (port 8773), auto-installed by installs.json; the Editor Manager tracks its files.

Tests

  • tests/weapon_tests.h (weapons group; alone: quark_tests weaponsets): the standard text round-trips and gives bit-identical tuned values (no rules, mode multipliers, standard text under multipliers); 16 error cases with exact line:col; interpreter semantics with C++ f32 rounding; network / file forms (missing, broken, too big); through the real Server: sniper 30 body / 90 head with a set, an airborne hit slot doubling to 60, a rocket direct hit 70, rifle falloff x0.5 at 20 m, a kill slot healing 25; the EV_RULES client copy compiles to the same values. Fuzzed with ASan/UBSan (200k rules texts, 360k mutated sets): no crashes, one printer bug fixed (-(0) keeps its parentheses).
  • tools/weapon_editor/native/test_wpn.py (58 checks), web/test/run.mjs (73), and the bot and physics editors' helper tests still pass.