This commit is contained in:
saarsena@gmail.com 2026-04-16 21:04:50 -04:00
commit e45f121fb9
89 changed files with 336069 additions and 0 deletions

68
godot/src/brogue_gen.cpp Normal file
View 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
View 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
View 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
View 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

View 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();
}
}

View 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);