natural 20 (noun) \ The result of rolling a twenty-sided die and obtaining a value of 20, before applying any modifiers, often representing an exceptional outcome. \ - Oxford Dictionary (probably)
Nat20 is a Dungeons & Dragons 5th Edition combat engine written in Rust. The engine is based on the System Reference Document v5.2.1. By combat engine, it means nat20 can execute the rules involved in throwing a Fireball at a Goblin, and it'll make him do a Dexterity Saving Throw before rolling 8d6 and (probably) burning the him to a crisp, but it is not a full game engine and it is not a complete game (no actual "gameplay", UI, or tooling beyond a developer/debug view). Think of it like the code version of a D&D rulebook!
This workspace contains two crates:
core (nat20_core): the core, data-driven rules/logic backend. This is where the magic happens!gui (nat20_gui): an ImGui-based developer/debug UI for inspecting and exercising the engine. It is intentionally a fancy debugging tool rather than a consumer-facing game UI. If you want to poke around and see what the engine can do (so far :wink:), do feel free to take it for a spin!The engine is currently very much a work-in-progress, so there is no packaged release or anything like that yet. If you want to try it out, the easiest way is to clone the repository and run the GUI crate:
cargo run -p nat20_gui <optional-log-level>
In the GUI, you can spawn some creatures and run through a combat encounter to see how the engine handles turns, actions, movement, and spellcasting.
In the future, the engine is intended to be usable as a library that can be integrated into other projects, such as a full-fledged game or a virtual tabletop application.
Here's an example of the Fireball scenario described earlier :fire::
The core of the engine is built around an ECS (Entity-Component-System) architecture using the hecs crate. This allows for flexible and efficient management of game entities and their behaviors.
core/src/entities defines entity types like characters/creatures.core/src/components defines the data components (abilities, items, effects, resources, etc.).core/src/systems implements rules and mechanics over the ECS world.The engine is intentionally data-driven, meaing pretty much all “game content” comes from JSON definitions under assets/registries and can be extended or replaced without recompiling the engine:
{
"id": "nat20_core::spell.fire_bolt",
"description": "You hurl a mote of fire at a creature or an object within range. Make a ranged spell attack against the target. On a hit, the target takes 1d10 Fire damage. A flammable object hit by this spell starts burning if it isn’t being worn or carried",
"base_level": 0,
"school": "evocation",
"flags": [
"verbal",
"somatic"
],
"kind": {
"standard": {
"condition": {
"attack_roll": "spell_attack_roll"
},
"payload": {
"damage": "(1 + (character_level + 1) / 6)d10;fire"
}
}
},
"resource_cost": {
"nat20_core::resource.action": 1
},
"targeting": {
"kind": "single",
"range": "120 feet",
"line_of_sight": "ray",
"allowed_targets": ["not_dead"]
}
}
// Script for checking if Counterspell should be triggered by an event
fn reaction_trigger(context) {
let event = context.event;
// Only care about "action-like" events (ActionRequested / ReactionRequested)
if !event.is_action_requested() {
return false;
}
let action = event.as_action_requested();
// Cannot counterspell yourself
if action.actor == context.reactor {
return false;
}
// Only react to spells (for now any spell; you can refine later)
if !action.action_context.is_spell() {
return false;
}
true
}
This makes the system inherently moddable: new spells or items can be added by dropping a JSON file (and optional script) into the registries folder.
The engine uses an event-based architecture where everything is represented as an event, from rolling a D20 to performing an action. This allows for very transparent combat-logging, so you can see exactly what happened at every step of combat, and to allow reactions that can be triggered by whatever you want (core/src/engine/event).
The payload of each event tracks every dice roll and bonus modifier that went into it, so you can see exactly how a final result was computed.
The core/src/engine module containts the overall game state (core/src/engine/game_state), which is the judge, jury, and executioner of the engine. The game state includes the ECS world, combat encounters (core/src/engine/encounter), and event/interaction state. Whenever a creature wants to do basically anything, a request is sent to the game state, which will then validate, e.g. that it's the creatures turn if they're in combat or that they have enough resources to perform an action.
parry3d, rerecast, and polyanya (engine/src/engine/geometry and engine/src/systems/geometry/movement).engine/src/scripts), used by data-defined registry entries.uom and glam help keep movement, ranges, and values consistent.Coverage is intentionally incomplete, but the following fundamentals are implemented and exercised by tests and data definitions:
fireball, magic_missile, eldritch_blast, and hex variants.If you want to poke around and see it in action fire up the GUI crate, which exists to visualize and debug the underlying systems (cargo run -p nat20_gui).
The project source code is currently licensed under the MIT License (see LICENSE). Licensing may evolve as the project matures.
This work includes material taken from the System Reference Document 5.2.1 (“SRD 5.2.1”) by Wizards of the Coast LLC and available at https://dnd.wizards.com/resources/systems-reference-document. The SRD 5.2.1 is licensed under the Creative Commons Attribution 4.0 International License available at https://creativecommons.org/licenses/by/4.0/legalcode.
$ claude mcp add nat20 \
-- python -m otcore.mcp_server <graph>