Metadata-Version: 2.4
Name: nwn_dg
Version: 0.7.2
Summary: Neverwinter Nights (nwn) dungeon generator
Author-email: Julien Lecomte <julien@lecomte.at>
License-Expression: MIT
Project-URL: Homepage, https://gitlab.com/cappysan/apps/nwn-dg
Project-URL: Source code, https://gitlab.com/cappysan/apps/nwn-dg
Project-URL: Bug tracker, https://gitlab.com/cappysan/apps/nwn-dg/-/issues
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: boltons>=25.0.0
Requires-Dist: connexion[flask,swagger-ui,uvicorn]
Requires-Dist: matplotlib
Requires-Dist: networkx
Requires-Dist: pycairo
Requires-Dist: torxtools
Provides-Extra: dev
Provides-Extra: api
Requires-Dist: connexion[swagger-ui,uvicorn]; extra == "api"
Dynamic: license-file

[![license](https://img.shields.io/badge/license-MIT-brightgreen)](https://spdx.org/licenses/MIT.html)
[![pipelines](https://gitlab.com/cappysan/nwn-dg/badges/master/pipeline.svg?ignore_skipped=true)](https://gitlab.com/cappysan/nwn-dg/pipelines)
[![coverage](https://gitlab.com/cappysan/nwn-dg/badges/master/coverage.svg)](/coverage/index.html)

# nwn-dg

Neverwinter Nights (nwn) dungeon generator: cli & api.

`nwn-dg` lays out rooms and corridors on a grid, carves them into a connected
maze, then maps every cell onto a real NWN tileset. The result can be written
as a playable area file, as a preview image, or as json for a running server to
consume through `SetTileJson()`.

Work in progress: in alpha/beta stage. Please refer to file POC.md for more information.


## Installation

You can install the latest version from PyPI package repository.

~~~bash
pipx install nwn-dg
~~~

Building the `pycairo` dependency requires the cairo development headers. On
Debian, install them with `apt-get install libcairo2-dev` beforehand.

Writing binary `.are` files additionally requires `nwn_gff`, from the
[neverwinter.nim](https://github.com/niv/neverwinter.nim) tools. It is only
needed for `--output-are`. On Debian 12 (Bookworm) and later, it is packaged
as `neverwinter.nim` in the unofficial [nwn.ovh](https://debian.nwn.ovh)
repository:

~~~bash
source /etc/os-release
sudo wget https://debian.nwn.ovh/sources.list.d/${VERSION_CODENAME}.sources -O /etc/apt/sources.list.d/nwn-ovh.sources
sudo wget https://debian.nwn.ovh/nwn-ovh-archive.gpg -O /usr/share/keyrings/nwn-ovh-archive.gpg
sudo apt-get update
sudo apt-get install -y neverwinter.nim
~~~


## Usage

### Command line interface

Generate a dungeon and write a preview image:

~~~bash
nwn-dg --output-tileset tdc01 mydungeon
~~~

A more complete run, writing the area, the tile json and the random seed:

~~~bash
nwn-dg --seed mydungeon.seed --output-seed \
       --output-png --output-are --output-tile-json \
       --output-tileset tdc01 \
       --png-axes-ids --png-axes-base 0 --png-tileset-idx \
       mydungeon
~~~

The positional argument is a base filepath; each output appends its own
extension to it.

Map size, room count and corridor shape are set through the `--map-*` options;
`--map-door-entrance` and `--map-door-exits` place the stairs, taking `N`, `E`,
`S`, `W` or `X` for random, one letter per door. Run `nwn-dg --help` for the
full list and current defaults.

Options documented as "may cause generation failure" constrain the generator
enough that no valid dungeon may exist for a given seed. `nwn-dg` exits with an
error rather than returning a broken map; retry with another seed or different options.

In order to test generated outputs, it's possible to create the `are` file, and to copy it
to the module folder in your toolset profile hierarchy. When area is closed and re-opened
it should have the proper new layout.


### Api

The generator is also exposed over https, so a live module can request a fresh
dungeon through `NWNX_HTTPClient` instead of shipping pre-generated files.

Start the server:

~~~bash
nwn-dg-api --port 8080
~~~

Serving over tls, which is what a module reaching an `https://` url expects:

~~~bash
nwn-dg-api --port 8080 \
           --ssl-certfile ./ssl/localhost.crt \
           --ssl-keyfile ./ssl/localhost.key
~~~

A self-signed certificate is enough when the api and the game server sit on the
same host. The docker image below ships with one already, so this step only
applies to a bare install:

~~~bash
mkdir -p ./ssl
openssl req -new -x509 -days 365 -noenc \
        -out ./ssl/localhost.crt -keyout ./ssl/localhost.key
~~~

From a checkout, the launcher is `./bin/nwn-dg-api`. Run `nwn-dg-api --help`
for the full list of options.

### Docker

The api also ships as a container image, which saves installing the cairo
headers and comes with tls certificates already in place, so there is nothing
to generate before the first run:

| Registry   | Image                                             |
|------------|---------------------------------------------------|
| GitLab     | `registry.gitlab.com/cappysan/apps/nwn-dg:latest` |
| Docker Hub | `nwnovh/nwndg:latest`                             |

The server listens on port `8000` inside the container; publish it on whatever
the game server can reach:

~~~bash
docker run -d -p 8001:8000 nwnovh/nwndg:latest
~~~

~~~bash
docker run -d -p 8001:8000 registry.gitlab.com/cappysan/apps/nwn-dg:latest
~~~

The endpoint is then at `https://localhost:8001/dungeon/tilejson`, already over
tls, with no `--ssl-certfile` or `--ssl-keyfile` to pass.

The bundled certificate is self-signed, so `curl` needs `-k` and the module
must be told not to verify the peer. That is fine for an api reachable only
from the game server; for anything exposed more widely, mount your own
certificate and point the flags at it:

~~~bash
docker run -d -p 8001:8000 \
       -v ./ssl:/ssl:ro \
       nwnovh/nwndg:latest \
       --ssl-certfile /etc/ssl/fqdn.crt --ssl-keyfile /etc/ssl/fqdn.key
~~~

You can obtain free certificates via Let's Encrypt.


### Endpoint

`POST /dungeon/tilejson` generates a dungeon and returns the same document as
`--output-tile-json`, as the response body.

  * The request body must be sent as `application/json`; the schema in
    `openapi.yml` declares no other content type and anything else is rejected.
  * Keys are the cli long options without their leading `--`, so
    `--map-width 21` becomes `"map-width": 21`.
  * An empty object generates a dungeon with every default.
  * File-writing `output-*` flags are ignored, since the endpoint returns the
    document rather than writing it; `output-tileset` is honoured and selects
    the tileset as it does on the command line.

~~~bash
curl -k -X POST https://localhost:8080/dungeon/tilejson \
     -H 'Content-Type: application/json' \
     -d '{"output-tileset": "tdm01", "map-width": 21, "map-height": 9}'
~~~

Passing `seed` reproduces an exact dungeon, the same way `--seed` does on the
command line:

~~~bash
curl -k -X POST https://localhost:8080/dungeon/tilejson \
     -H 'Content-Type: application/json' \
     -d '{"output-tileset": "tdm01", "seed": "mydungeon"}'
~~~

`-k` is only needed for a self-signed certificate, which includes the one
shipped in the docker image. On the module side, see POC `examples/nwn_dg.nss`
for the matching request and the `SetTileJson()` call it feeds.


## Outputs

| Flag                 | Extension    | Contents                                                   |
|----------------------|--------------|------------------------------------------------------------|
| `--output-png`       | `.png`       | Map preview, with optional room ids, axes and tile indexes |
| `--output-are-json`  | `.are.json`  | The area as json, ready for `nwn_gff`                      |
| `--output-are`       | `.are`       | Binary area file, converted with `nwn_gff`                 |
| `--output-tile-json` | `.tile.json` | Input for `SetTileJson()`, see below                       |
| `--output-tree`      | `.tree.png`  | Graph of connected rooms                                   |
| `--output-seed`      | `.seed`      | Random state, for reproducing the exact same dungeon       |


`--output-png` and `--output-are-json` are on by default; the rest are off.
Every flag has a `--no-` counterpart.


## Reproducibility

`--seed` accepts either a seed value or a path to a `.seed` file, whose content
restores the full random state. Combined with `--output-seed`, any dungeon can
be regenerated byte for byte. The state is also embedded, base64 encoded, in
the `seed` key of the tile json, so an area artifact is self-describing.


## Tile json

`--output-tile-json` writes what a module needs to build the area at runtime
and then populate it:

  * `tiles` — tile id and orientation per index, for `SetTileJson()`
  * `rooms` — per room: neighbouring rooms, the cells it owns, and its sills
  * `transitions` — stairs up and down, with orientation, the room they open
    onto, whether they are the dungeon entrance, and the walking distance to
    every room
  * `paths` — the longest routes through the dungeon, as room ids
  * `cells` — deadends, useful for treasure and traps

See POC `examples/nwn_dg.nss` for a proof-of-concept module script consuming it.


## Demo

A working demo lives in `./example`, showing the whole exchange: a module
requesting a dungeon from the api and building the area from the response.

  * `module` — a standalone NWN module, playable as-is. It needs NWNXEE on the
    server, since the demo goes through `NWNX_HTTPClient` to reach the api and
    `NWNX_Area_GetTileInfo` to place the doors.
  * `scripts` — the NWScript sources the module runs, kept outside the module
    so they can be read and recompiled without unpacking it.

Point the module at a running api, either a bare `nwn-dg-api` or the docker
image, and enter the area to have it regenerate itself.


## Tilesets

| Type    | Tileset           |
|---------|-------------------|
| `tdc01` | Crypt             |
| `tdm01` | Mines and Caverns |
| `tds01` | Sewers            |

See [https://nwnlexicon.com/Tileset_resref](https://nwnlexicon.com/Tileset_resref).


## Additional resources

  * NWNXEE: [https://nwnxee.github.io/unified/group__httpclient.html](https://nwnxee.github.io/unified/group__httpclient.html)
  * SetTileJson: [https://nwnlexicon.com/index.php/SetTileJson](https://nwnlexicon.com/index.php/SetTileJson)


## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.


## Locations

  * Website: [https://gitlab.com/cappysan/nwn-dg](https://gitlab.com/cappysan/nwn-dg)
  * PyPi: [https://pypi.org/project/nwn_dg](https://pypi.org/project/nwn_dg)
