init
This commit is contained in:
commit
e45f121fb9
89 changed files with 336069 additions and 0 deletions
330
godot/README.md
Normal file
330
godot/README.md
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
# brogue_gen — Godot GDExtension API Reference
|
||||
|
||||
Brogue-style dungeon generator for Godot 4.3+. Pure C99 generator under the hood; Godot-native Dictionary interface on top.
|
||||
|
||||
**Scope.** Map generation only. One class, one method. Call `BrogueGen.generate(seed, depth)` and get back everything you need to spawn a level: terrain grid, liquids, markers, rooms, machines, chokepoints, stairs. Gameplay is yours to write.
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Project layout](#project-layout)
|
||||
- [Class `BrogueGen`](#class-broguegen)
|
||||
- [Returned dictionary schema](#returned-dictionary-schema)
|
||||
- [Data structures](#data-structures)
|
||||
- [Enums](#enums)
|
||||
- [Usage pattern](#usage-pattern)
|
||||
- [FOV reference (`BrogueFOV`)](#fov-reference)
|
||||
- [Determinism guarantees](#determinism-guarantees)
|
||||
- [Build and rebuild](#build-and-rebuild)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Drop the `addons/brogue_gen/` directory into any Godot 4.3+ project:
|
||||
|
||||
```
|
||||
your_project/
|
||||
└── addons/
|
||||
└── brogue_gen/
|
||||
├── brogue_gen.gdextension ← registered by Godot scan
|
||||
└── bin/
|
||||
└── libbrogue_gen.linux.template_debug.x86_64.so
|
||||
```
|
||||
|
||||
Re-open the project once so Godot scans and registers the extension. `BrogueGen` then appears in **Create New Node** and `class_name` auto-complete.
|
||||
|
||||
**Compatibility:** The `.gdextension` manifest declares `compatibility_minimum = "4.3"`. The extension is built against `godot-cpp` 4.5 and loads in any Godot 4.3+, including 4.6.
|
||||
|
||||
**Linux x86_64 only for now.** Windows / macOS require building additional targets and adding paths to the `[libraries]` section of the `.gdextension` manifest.
|
||||
|
||||
---
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
brogue-genesis/
|
||||
├── src/ pure C99 generator (no Godot, no SDL)
|
||||
│ └── gen/ room carving, lakes, machines, chokepoints, ...
|
||||
├── godot/ GDExtension wrapper
|
||||
│ ├── godot-cpp/ submodule, branch 4.5
|
||||
│ ├── src/
|
||||
│ │ ├── register_types.* module init
|
||||
│ │ ├── brogue_gen.* BrogueGen class
|
||||
│ │ └── grid_to_dict.* shared grid → Dictionary serializer
|
||||
│ ├── SConstruct build script
|
||||
│ └── brogue_gen.gdextension source-of-truth manifest
|
||||
└── demo/ reference Godot 4.6 project
|
||||
├── addons/brogue_gen/ ← build output
|
||||
│ ├── brogue_gen.gdextension
|
||||
│ └── bin/libbrogue_gen...so
|
||||
├── project.godot
|
||||
├── scenes/
|
||||
└── scripts/
|
||||
```
|
||||
|
||||
Build with `make godot` from the repo root.
|
||||
|
||||
---
|
||||
|
||||
## Class `BrogueGen`
|
||||
|
||||
`extends Node`
|
||||
|
||||
One-shot blocking dungeon generator. Instantiate, call `generate()`, free.
|
||||
|
||||
Typical generation time on the reference machine: **under 20 ms per level** including machines. Safe to call synchronously in `_ready()`.
|
||||
|
||||
### `generate(seed: int, depth: int) -> Dictionary`
|
||||
|
||||
Runs the full pipeline (accretion → loops → lakes → wreaths → bridges → walls → stairs → chokepoints → machines) and returns a Dictionary describing the final dungeon.
|
||||
|
||||
Parameters:
|
||||
|
||||
| name | type | range | notes |
|
||||
|---|---|---|---|
|
||||
| `seed` | int | any 64-bit | same seed + depth always yields identical output |
|
||||
| `depth` | int | 1–26 | influences liquid type weights |
|
||||
|
||||
```gdscript
|
||||
var gen := BrogueGen.new()
|
||||
var grid: Dictionary = gen.generate(2026, 1)
|
||||
gen.free()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Returned dictionary schema
|
||||
|
||||
| key | type | notes |
|
||||
|---|---|---|
|
||||
| `seed` | `int` | echoed back |
|
||||
| `depth` | `int` | 1..26 |
|
||||
| `width` | `int` | always `79` |
|
||||
| `height` | `int` | always `29` |
|
||||
| `terrain` | `PackedByteArray` | length `width * height`, row-major. Index = `y * width + x`. Values in [terrain enum](#terrain-enum-terrain-byte). |
|
||||
| `liquid` | `PackedByteArray` | same layout. Values in [liquid enum](#liquid-enum). Meaningful only when `terrain[i] == T_LIQUID`. |
|
||||
| `surface` | `PackedByteArray` | semantic marker byte. Values in [marker enum](#marker-enum). `MK_NONE` for un-marked cells. |
|
||||
| `flags` | `PackedInt32Array` | bitmask of [cell flags](#cell-flags). |
|
||||
| `room_id` | `PackedByteArray` | 1–255 inside a room, `0` elsewhere. |
|
||||
| `machine_id` | `PackedByteArray` | 1–255 inside a machine, `0` elsewhere. |
|
||||
| `stairs_up` | `Vector2i` | always valid grid coords; `(-1, -1)` only on degenerate seeds. |
|
||||
| `stairs_down` | `Vector2i` | farthest reachable floor from `stairs_up` (BFS). |
|
||||
| `rooms` | `Array[Dictionary]` | see [room dictionary](#room-dictionary). |
|
||||
| `machines` | `Array[Dictionary]` | see [machine dictionary](#machine-dictionary). |
|
||||
| `chokepoints` | `Array[Vector2i]` | every articulation point in the map graph. |
|
||||
| `gate_sites` | `Array[Vector2i]` | the "best" chokepoint per pocket — ideal vestibule candidates. Subset of `chokepoints`. |
|
||||
|
||||
---
|
||||
|
||||
## Data structures
|
||||
|
||||
### Room dictionary
|
||||
|
||||
Each entry in `grid["rooms"]`:
|
||||
|
||||
```gdscript
|
||||
{
|
||||
"id": int, # matches room_id in the cell arrays
|
||||
"cells": Array[Vector2i], # every cell that belongs to this room
|
||||
}
|
||||
```
|
||||
|
||||
### Machine dictionary
|
||||
|
||||
Each entry in `grid["machines"]`:
|
||||
|
||||
```gdscript
|
||||
{
|
||||
"id": int, # matches machine_id in the cell arrays
|
||||
"interior_cells": Array[Vector2i],
|
||||
"gate": Vector2i, # the one F_MACHINE_GATE cell; (-1,-1) if unflagged
|
||||
"markers": Dictionary, # marker_id (int) -> Array[Vector2i]
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```gdscript
|
||||
var m: Dictionary = grid["machines"][0]
|
||||
for marker_id in m["markers"]:
|
||||
var positions: Array = m["markers"][marker_id]
|
||||
match marker_id:
|
||||
4: # MK_PEDESTAL
|
||||
for p in positions:
|
||||
spawn_pedestal(p)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enums
|
||||
|
||||
### Terrain enum (`terrain` byte)
|
||||
|
||||
| id | constant | meaning |
|
||||
|---|---|---|
|
||||
| 0 | `T_NOTHING` | outside the dungeon — not walkable |
|
||||
| 1 | `T_FLOOR` | room floor |
|
||||
| 2 | `T_WALL` | wall |
|
||||
| 3 | `T_DOOR` | door in a room wall |
|
||||
| 4 | `T_CORRIDOR` | corridor between rooms |
|
||||
| 5 | `T_LIQUID` | water / lava / chasm / brimstone — see `liquid` byte |
|
||||
| 6 | `T_BRIDGE` | walkable span across water/chasm |
|
||||
| 7 | `T_STAIRS_UP` | up-stair |
|
||||
| 8 | `T_STAIRS_DOWN` | down-stair |
|
||||
|
||||
`T_FLOOR`, `T_CORRIDOR`, `T_DOOR`, `T_BRIDGE`, `T_STAIRS_UP`, `T_STAIRS_DOWN` are **passable**.
|
||||
|
||||
### Liquid enum (`liquid` byte)
|
||||
|
||||
| id | constant | bridges allowed | |
|
||||
|---|---|---|---|
|
||||
| 0 | `L_NONE` | — | |
|
||||
| 1 | `L_WATER` | yes | shallow levels |
|
||||
| 2 | `L_LAVA` | no | damages walker |
|
||||
| 3 | `L_CHASM` | yes | gameplay-defined fall behavior |
|
||||
| 4 | `L_BRIMSTONE` | no | lowest depths |
|
||||
|
||||
### Marker enum (`surface` byte)
|
||||
|
||||
Semantic placement markers. The generator stamps these; gameplay interprets them.
|
||||
|
||||
| id | constant | typical use |
|
||||
|---|---|---|
|
||||
| 0 | `MK_NONE` | no marker |
|
||||
| 1 | `MK_CARPET` | decorative floor tint (paint pass) |
|
||||
| 2 | `MK_ALTAR` | item pedestal on an altar |
|
||||
| 3 | `MK_CAGE` | item behind a lock |
|
||||
| 4 | `MK_PEDESTAL` | item on pedestal |
|
||||
| 5 | `MK_STATUE` | decorative statue |
|
||||
| 6 | `MK_TORCH` | point light source |
|
||||
| 7 | `MK_BRAZIER` | point light source (stronger) |
|
||||
| 8 | `MK_CHEST` | item container |
|
||||
| 9 | `MK_SPAWN_MONSTER` | spawn an enemy here |
|
||||
| 10 | `MK_SPAWN_BOSS` | spawn a boss here |
|
||||
| 11 | `MK_KEY_ITEM` | the key that unlocks this machine's parent |
|
||||
|
||||
### Cell flags
|
||||
|
||||
Bitmask in `flags[]`. Test with `(flags[idx] & F_X) != 0`.
|
||||
|
||||
| bit | value | constant | meaning |
|
||||
|---|---|---|---|
|
||||
| 0 | `0x0001` | `F_IN_ROOM` | cell is room floor |
|
||||
| 1 | `0x0002` | `F_IN_LOOP` | cell was carved by the loop pass |
|
||||
| 2 | `0x0004` | `F_CHOKEPT` | reserved |
|
||||
| 3 | `0x0008` | `F_BRIDGE` | cell is a bridge (redundant with `T_BRIDGE`) |
|
||||
| 4 | `0x0010` | `F_WREATH` | shoreline cell (adjacent to liquid) |
|
||||
| 6 | `0x0040` | `F_IN_MACHINE` | cell belongs to a machine |
|
||||
| 7 | `0x0080` | `F_MACHINE_INTERIOR` | interior (not boundary) |
|
||||
| 8 | `0x0100` | `F_MACHINE_GATE` | the vestibule / entry |
|
||||
| 9 | `0x0200` | `F_MACHINE_IMPREGNABLE` | cell is protected from later modification (reserved) |
|
||||
| 10 | `0x0400` | `F_MACHINE_KEY` | key goes here (gameplay hint) |
|
||||
|
||||
---
|
||||
|
||||
## Usage pattern
|
||||
|
||||
Generate a level, instantiate everything up front, hand off to gameplay.
|
||||
|
||||
```gdscript
|
||||
func build_level(seed: int, depth: int) -> void:
|
||||
var gen := BrogueGen.new()
|
||||
var grid: Dictionary = gen.generate(seed, depth)
|
||||
gen.free()
|
||||
|
||||
# Player at the up-stair.
|
||||
$Player.position = _grid_to_world(grid["stairs_up"])
|
||||
|
||||
# Goal marker at the down-stair.
|
||||
$GoalMarker.position = _grid_to_world(grid["stairs_down"])
|
||||
|
||||
# One reward per machine.
|
||||
for m in grid["machines"]:
|
||||
for marker_id in m["markers"]:
|
||||
for cell in m["markers"][marker_id]:
|
||||
spawn_for_marker(marker_id, cell)
|
||||
|
||||
# Monsters proportional to room size.
|
||||
for room in grid["rooms"]:
|
||||
var size: int = (room["cells"] as Array).size()
|
||||
if size > 40 and randf() < 0.6:
|
||||
spawn_monster(_random_cell(room["cells"]))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FOV reference
|
||||
|
||||
`demo/scripts/fov.gd` ships a reference `BrogueFOV` class. It is **not** part of the extension — it's pure GDScript you can copy into your project and modify.
|
||||
|
||||
```gdscript
|
||||
# demo/scripts/fov.gd
|
||||
class_name BrogueFOV extends RefCounted
|
||||
|
||||
static func compute(grid: Dictionary, origin: Vector2i, radius: int) -> PackedByteArray
|
||||
```
|
||||
|
||||
Symmetric recursive shadowcasting, tile-accurate, O(visible cells). Opaque cells = walls, nothing, and non-water liquids (lava / chasm / brimstone block sight; water does not).
|
||||
|
||||
Example:
|
||||
|
||||
```gdscript
|
||||
var vis := BrogueFOV.compute(grid, player_pos, 8)
|
||||
for y in grid["height"]:
|
||||
for x in grid["width"]:
|
||||
if vis[y * grid["width"] + x] == 1:
|
||||
mark_visible(x, y)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Determinism guarantees
|
||||
|
||||
- Same `(seed, depth)` input always produces identical output, across runs, platforms, and future minor versions of the plugin.
|
||||
- The C generator uses a PCG32 RNG seeded from the `seed` argument only. No platform calls (`time()`, `rand()`) are read.
|
||||
|
||||
---
|
||||
|
||||
## Build and rebuild
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
make godot # builds the extension
|
||||
make godot-clean # removes build artifacts
|
||||
```
|
||||
|
||||
This runs `scons -j$(nproc) platform=linux target=template_debug` in `godot/`. The resulting `.so` and manifest land together in `demo/addons/brogue_gen/` per Godot's plugin convention.
|
||||
|
||||
For release builds:
|
||||
|
||||
```bash
|
||||
cd godot && scons -j$(nproc) platform=linux target=template_release
|
||||
```
|
||||
|
||||
To add a new platform (Windows/macOS), add the SCons target + append a new line to `brogue_gen.gdextension`:
|
||||
|
||||
```
|
||||
windows.debug.x86_64 = "res://bin/libbrogue_gen.windows.template_debug.x86_64.dll"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Could not find type BrogueGen" / "Cannot get class 'BrogueGen'".**
|
||||
Godot has not registered the extension. Check:
|
||||
1. `addons/brogue_gen/brogue_gen.gdextension` exists in the project.
|
||||
2. `addons/brogue_gen/bin/libbrogue_gen...so` exists and matches the path in the manifest.
|
||||
3. The project has been opened in the editor at least once (this triggers the extension scan).
|
||||
4. Delete the `.godot/` cache directory and re-open if in doubt.
|
||||
|
||||
**Godot 4.6 crashes on startup (SIGABRT in `WaylandThread`).**
|
||||
This is a Godot 4.6.1 Wayland bug, not the extension. Launch with X11 instead:
|
||||
```bash
|
||||
godot --display-driver x11 --path /path/to/your/project
|
||||
```
|
||||
66
godot/SConstruct
Normal file
66
godot/SConstruct
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
SConstruct for the brogue_gen GDExtension.
|
||||
|
||||
Build: cd godot && scons target=template_debug
|
||||
(or target=template_release for optimized)
|
||||
|
||||
Output: ../demo/bin/libbrogue_gen.<platform>.<target>.<arch>.so
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
env = SConscript("godot-cpp/SConstruct")
|
||||
|
||||
# Where to find our C generator library headers.
|
||||
env.Append(CPPPATH=["src/", "../src/"])
|
||||
|
||||
# Compile all of our C generator sources directly into the extension — the
|
||||
# generator is a pure C99 library with no Godot deps. This avoids needing a
|
||||
# separate libbroguegen.a.
|
||||
c_sources = [
|
||||
"../src/gen/rng.c",
|
||||
"../src/gen/grid.c",
|
||||
"../src/gen/events.c",
|
||||
"../src/gen/room_types.c",
|
||||
"../src/gen/accretion.c",
|
||||
"../src/gen/dijkstra.c",
|
||||
"../src/gen/loops.c",
|
||||
"../src/gen/ca.c",
|
||||
"../src/gen/lakes.c",
|
||||
"../src/gen/walls.c",
|
||||
"../src/gen/stairs.c",
|
||||
"../src/gen/chokepoints.c",
|
||||
"../src/gen/machines.c",
|
||||
"../src/gen/blueprints_data.c",
|
||||
]
|
||||
|
||||
# Ensure C files compile with C99; don't inherit godot-cpp's C++ flags verbatim.
|
||||
c_env = env.Clone()
|
||||
c_env.Replace(CFLAGS=["-std=c99", "-Wall", "-Wpedantic", "-Werror=implicit",
|
||||
"-O2", "-g", "-fPIC"])
|
||||
c_objects = [c_env.SharedObject(target="build/" + os.path.basename(s).replace(".c", ""),
|
||||
source=s) for s in c_sources]
|
||||
|
||||
cpp_sources = Glob("src/*.cpp")
|
||||
|
||||
library_name = "libbrogue_gen{}{}".format(env["suffix"], env["SHLIBSUFFIX"])
|
||||
|
||||
# Godot plugin layout convention: addons/<plugin_name>/
|
||||
# brogue_gen.gdextension — manifest, references the .so path relative to res://
|
||||
# bin/<name>.so — binaries per platform/target
|
||||
import os as _os
|
||||
import shutil as _shutil
|
||||
_ADDON_DIR = "../demo/addons/brogue_gen"
|
||||
_os.makedirs(_ADDON_DIR + "/bin", exist_ok=True)
|
||||
|
||||
library = env.SharedLibrary(
|
||||
_ADDON_DIR + "/bin/" + library_name,
|
||||
source=cpp_sources + c_objects,
|
||||
)
|
||||
|
||||
_shutil.copyfile("brogue_gen.gdextension",
|
||||
_ADDON_DIR + "/brogue_gen.gdextension")
|
||||
|
||||
Default(library)
|
||||
11
godot/brogue_gen.gdextension
Normal file
11
godot/brogue_gen.gdextension
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[configuration]
|
||||
|
||||
entry_symbol = "brogue_gen_library_init"
|
||||
compatibility_minimum = "4.3"
|
||||
|
||||
[libraries]
|
||||
|
||||
linux.debug.x86_64 = "res://addons/brogue_gen/bin/libbrogue_gen.linux.template_debug.x86_64.so"
|
||||
linux.release.x86_64 = "res://addons/brogue_gen/bin/libbrogue_gen.linux.template_release.x86_64.so"
|
||||
|
||||
[dependencies]
|
||||
1
godot/godot-cpp
Submodule
1
godot/godot-cpp
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 60b5a4196de8442b43b32ba68ebe1e79cfcb762f
|
||||
68
godot/src/brogue_gen.cpp
Normal file
68
godot/src/brogue_gen.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "brogue_gen.h"
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/variant/packed_byte_array.hpp>
|
||||
#include <godot_cpp/variant/packed_int32_array.hpp>
|
||||
#include <godot_cpp/variant/vector2i.hpp>
|
||||
#include <godot_cpp/variant/typed_array.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include "gen/grid.h"
|
||||
#include "gen/rng.h"
|
||||
#include "gen/events.h"
|
||||
#include "gen/accretion.h"
|
||||
#include "gen/loops.h"
|
||||
#include "gen/lakes.h"
|
||||
#include "gen/walls.h"
|
||||
#include "gen/stairs.h"
|
||||
#include "gen/chokepoints.h"
|
||||
#include "gen/machines.h"
|
||||
}
|
||||
|
||||
#include "grid_to_dict.h"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
BrogueGen::BrogueGen() = default;
|
||||
BrogueGen::~BrogueGen() = default;
|
||||
|
||||
void BrogueGen::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("generate", "seed", "depth"), &BrogueGen::generate);
|
||||
}
|
||||
|
||||
Dictionary BrogueGen::generate(int seed, int depth) {
|
||||
grid_t g;
|
||||
event_sink_t sink = {nullptr, nullptr, 0};
|
||||
rng_t rng;
|
||||
rng_seed(&rng, (uint64_t)seed);
|
||||
grid_fill(g, T_NOTHING);
|
||||
|
||||
event_emit(&sink, EV_GEN_BEGIN, NULL, 0);
|
||||
accrete_rooms(g, &rng, &sink);
|
||||
add_loops(g, &sink);
|
||||
place_lakes(g, &rng, &sink, depth);
|
||||
apply_wreaths(g, &sink);
|
||||
place_bridges(g, &sink);
|
||||
finish_walls(g, &sink);
|
||||
place_stairs(g, &sink);
|
||||
|
||||
static choke_map_t choke;
|
||||
static choke_flag_t is_choke, is_gate;
|
||||
compute_chokepoints(g, &sink, choke, is_choke, is_gate);
|
||||
place_machines(g, &rng, depth, &sink, choke, is_gate);
|
||||
event_emit(&sink, EV_GEN_END, NULL, 0);
|
||||
|
||||
Dictionary d = grid_to_dictionary(g, seed, depth, /*add_metadata=*/true);
|
||||
|
||||
TypedArray<Vector2i> chokes, gates;
|
||||
for (int y = 0; y < DROWS; y++) {
|
||||
for (int x = 0; x < DCOLS; x++) {
|
||||
if (is_choke[y][x]) chokes.push_back(Vector2i(x, y));
|
||||
if (is_gate[y][x]) gates.push_back(Vector2i(x, y));
|
||||
}
|
||||
}
|
||||
d["chokepoints"] = chokes;
|
||||
d["gate_sites"] = gates;
|
||||
return d;
|
||||
}
|
||||
25
godot/src/brogue_gen.h
Normal file
25
godot/src/brogue_gen.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
|
||||
namespace godot {
|
||||
|
||||
/* One-shot facade over the pure C generator. Call generate() with a seed and
|
||||
depth; receive a Dictionary describing the final dungeon. See the plugin
|
||||
README for the full dict shape (terrain / liquid / surface / flags arrays,
|
||||
rooms[], machines[], chokepoints[], stairs). */
|
||||
class BrogueGen : public Node {
|
||||
GDCLASS(BrogueGen, Node)
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
BrogueGen();
|
||||
~BrogueGen();
|
||||
|
||||
Dictionary generate(int seed, int depth);
|
||||
};
|
||||
|
||||
} // namespace godot
|
||||
132
godot/src/grid_to_dict.cpp
Normal file
132
godot/src/grid_to_dict.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#include "grid_to_dict.h"
|
||||
|
||||
#include <godot_cpp/variant/packed_byte_array.hpp>
|
||||
#include <godot_cpp/variant/packed_int32_array.hpp>
|
||||
#include <godot_cpp/variant/array.hpp>
|
||||
#include <godot_cpp/variant/typed_array.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
static void build_rooms_and_machines(grid_t g, Array &rooms, Array &machines) {
|
||||
/* Bucket cells by room_id and machine_id. room_id and machine_id are 1..255. */
|
||||
struct Bucket { TypedArray<Vector2i> cells; Vector2i gate = Vector2i(-1, -1); };
|
||||
Bucket rbuckets[256];
|
||||
Bucket mbuckets[256];
|
||||
|
||||
/* Also collect marker-per-cell for each machine. */
|
||||
Dictionary mmarkers[256];
|
||||
|
||||
for (int y = 0; y < DROWS; y++) {
|
||||
for (int x = 0; x < DCOLS; x++) {
|
||||
const cell_t *c = &g[y][x];
|
||||
Vector2i p(x, y);
|
||||
if (c->room_id > 0) {
|
||||
rbuckets[c->room_id].cells.push_back(p);
|
||||
}
|
||||
if (c->machine_id > 0) {
|
||||
mbuckets[c->machine_id].cells.push_back(p);
|
||||
if (c->flags & F_MACHINE_GATE) {
|
||||
mbuckets[c->machine_id].gate = p;
|
||||
}
|
||||
if (c->surface != MK_NONE) {
|
||||
Dictionary &mk = mmarkers[c->machine_id];
|
||||
int key = (int)c->surface;
|
||||
TypedArray<Vector2i> list;
|
||||
if (mk.has(key)) {
|
||||
list = (TypedArray<Vector2i>)(Variant)mk[key];
|
||||
}
|
||||
list.push_back(p);
|
||||
mk[key] = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < 256; i++) {
|
||||
if (rbuckets[i].cells.size() == 0) continue;
|
||||
Dictionary d;
|
||||
d["id"] = i;
|
||||
d["cells"] = rbuckets[i].cells;
|
||||
rooms.push_back(d);
|
||||
}
|
||||
for (int i = 1; i < 256; i++) {
|
||||
if (mbuckets[i].cells.size() == 0) continue;
|
||||
Dictionary d;
|
||||
d["id"] = i;
|
||||
d["interior_cells"] = mbuckets[i].cells;
|
||||
d["gate"] = mbuckets[i].gate;
|
||||
d["markers"] = mmarkers[i];
|
||||
machines.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary godot::grid_to_dictionary(grid_t g, int seed, int depth, bool add_metadata) {
|
||||
const int N = DCOLS * DROWS;
|
||||
PackedByteArray terrain; terrain.resize(N);
|
||||
PackedByteArray liquid; liquid.resize(N);
|
||||
PackedByteArray room_id; room_id.resize(N);
|
||||
PackedByteArray machine_id; machine_id.resize(N);
|
||||
PackedByteArray surface; surface.resize(N);
|
||||
PackedInt32Array flags; flags.resize(N);
|
||||
|
||||
uint8_t *t = terrain.ptrw();
|
||||
uint8_t *lq = liquid.ptrw();
|
||||
uint8_t *ri = room_id.ptrw();
|
||||
uint8_t *mi = machine_id.ptrw();
|
||||
uint8_t *sf = surface.ptrw();
|
||||
int32_t *fl = flags.ptrw();
|
||||
|
||||
Vector2i stairs_up(-1, -1), stairs_down(-1, -1);
|
||||
int i = 0;
|
||||
for (int y = 0; y < DROWS; y++) {
|
||||
for (int x = 0; x < DCOLS; x++, i++) {
|
||||
const cell_t *c = &g[y][x];
|
||||
t[i] = c->terrain;
|
||||
lq[i] = c->liquid;
|
||||
ri[i] = c->room_id;
|
||||
mi[i] = c->machine_id;
|
||||
sf[i] = c->surface;
|
||||
fl[i] = (int32_t)c->flags;
|
||||
if (c->terrain == T_STAIRS_UP) stairs_up = Vector2i(x, y);
|
||||
if (c->terrain == T_STAIRS_DOWN) stairs_down = Vector2i(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary d;
|
||||
d["seed"] = seed;
|
||||
d["depth"] = depth;
|
||||
d["width"] = DCOLS;
|
||||
d["height"] = DROWS;
|
||||
d["terrain"] = terrain;
|
||||
d["liquid"] = liquid;
|
||||
d["flags"] = flags;
|
||||
d["room_id"] = room_id;
|
||||
d["machine_id"] = machine_id;
|
||||
d["surface"] = surface;
|
||||
d["stairs_up"] = stairs_up;
|
||||
d["stairs_down"] = stairs_down;
|
||||
|
||||
if (add_metadata) {
|
||||
Array rooms;
|
||||
Array machines;
|
||||
build_rooms_and_machines(g, rooms, machines);
|
||||
d["rooms"] = rooms;
|
||||
d["machines"] = machines;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
Dictionary godot::cell_to_dictionary(grid_t g, int x, int y) {
|
||||
Dictionary d;
|
||||
if (!grid_in_bounds(x, y)) return d;
|
||||
const cell_t *c = &g[y][x];
|
||||
d["x"] = x;
|
||||
d["y"] = y;
|
||||
d["terrain"] = (int)c->terrain;
|
||||
d["liquid"] = (int)c->liquid;
|
||||
d["surface"] = (int)c->surface;
|
||||
d["flags"] = (int)c->flags;
|
||||
d["room_id"] = (int)c->room_id;
|
||||
d["machine_id"] = (int)c->machine_id;
|
||||
return d;
|
||||
}
|
||||
22
godot/src/grid_to_dict.h
Normal file
22
godot/src/grid_to_dict.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
#include <godot_cpp/variant/vector2i.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include "gen/grid.h"
|
||||
}
|
||||
|
||||
namespace godot {
|
||||
|
||||
/* Pack a grid_t into a Dictionary with the shape documented in the plugin
|
||||
README (terrain/liquid/flags/room_id/machine_id/surface arrays). Used by
|
||||
both BrogueGen.generate() and BrogueReplay.get_grid(). Additional
|
||||
metadata (rooms, machines, chokepoints, stairs) are computed and added
|
||||
when add_metadata is true. */
|
||||
Dictionary grid_to_dictionary(grid_t g, int seed, int depth, bool add_metadata);
|
||||
|
||||
/* Pack a single cell as a Dictionary. */
|
||||
Dictionary cell_to_dictionary(grid_t g, int x, int y);
|
||||
|
||||
} // namespace godot
|
||||
33
godot/src/register_types.cpp
Normal file
33
godot/src/register_types.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include <gdextension_interface.h>
|
||||
#include <godot_cpp/core/defs.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
|
||||
#include "brogue_gen.h"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
void initialize_brogue_gen_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) return;
|
||||
ClassDB::register_class<BrogueGen>();
|
||||
}
|
||||
|
||||
void uninitialize_brogue_gen_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) return;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
GDExtensionBool GDE_EXPORT brogue_gen_library_init(
|
||||
GDExtensionInterfaceGetProcAddress p_get_proc_address,
|
||||
const GDExtensionClassLibraryPtr p_library,
|
||||
GDExtensionInitialization *r_initialization) {
|
||||
|
||||
GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
|
||||
init_obj.register_initializer(initialize_brogue_gen_module);
|
||||
init_obj.register_terminator(uninitialize_brogue_gen_module);
|
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
return init_obj.init();
|
||||
}
|
||||
}
|
||||
8
godot/src/register_types.h
Normal file
8
godot/src/register_types.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
void initialize_brogue_gen_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_brogue_gen_module(ModuleInitializationLevel p_level);
|
||||
Loading…
Add table
Add a link
Reference in a new issue