163 lines
5.8 KiB
Python
163 lines
5.8 KiB
Python
"""Advanced: Cave-Carved Dungeon
|
|
|
|
Combines: BSP (room structure) + Noise (organic cave walls) + Erosion
|
|
Creates a dungeon where rooms have been carved from natural cave formations.
|
|
"""
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
|
|
GRID_WIDTH, GRID_HEIGHT = 64, 48
|
|
CELL_SIZE = 16
|
|
|
|
def run_demo(runtime):
|
|
# Step 1: Create base cave system using noise
|
|
noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=42)
|
|
|
|
cave_map = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
|
|
cave_map.add_noise(noise, world_size=(12, 10), mode='fbm', octaves=4)
|
|
cave_map.normalize(0.0, 1.0)
|
|
|
|
# Step 2: Create BSP rooms
|
|
bsp = mcrfpy.BSP(pos=(3, 3), size=(GRID_WIDTH - 6, GRID_HEIGHT - 6))
|
|
bsp.split_recursive(depth=3, min_size=(10, 8), max_ratio=1.5, seed=42)
|
|
|
|
rooms_hmap = bsp.to_heightmap(
|
|
size=(GRID_WIDTH, GRID_HEIGHT),
|
|
select='leaves',
|
|
shrink=2,
|
|
value=1.0
|
|
)
|
|
|
|
# Step 3: Combine - rooms carve into cave, cave affects walls
|
|
combined = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
|
|
combined.copy_from(cave_map)
|
|
|
|
# Scale cave values to mid-range so rooms stand out
|
|
combined.scale(0.5)
|
|
combined.add_constant(0.2)
|
|
|
|
# Add room interiors (rooms become high values)
|
|
combined.max(rooms_hmap)
|
|
|
|
# Step 4: Apply GENTLE erosion for organic edges
|
|
# Use fewer drops and lower erosion rate
|
|
combined.rain_erosion(drops=100, erosion=0.02, sedimentation=0.01, seed=42)
|
|
|
|
# Re-normalize to ensure we use the full value range
|
|
combined.normalize(0.0, 1.0)
|
|
|
|
# Step 5: Create corridor connections
|
|
adjacency = bsp.adjacency
|
|
connected = set()
|
|
|
|
corridor_map = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
|
|
|
|
for leaf_idx in range(len(bsp)):
|
|
leaf = bsp.get_leaf(leaf_idx)
|
|
cx1, cy1 = leaf.center()
|
|
|
|
for neighbor_idx in adjacency[leaf_idx]:
|
|
pair = (min(leaf_idx, neighbor_idx), max(leaf_idx, neighbor_idx))
|
|
if pair in connected:
|
|
continue
|
|
connected.add(pair)
|
|
|
|
neighbor = bsp.get_leaf(neighbor_idx)
|
|
cx2, cy2 = neighbor.center()
|
|
|
|
# Draw corridor using bezier for organic feel
|
|
mid_x = (cx1 + cx2) // 2 + ((leaf_idx * 3) % 5 - 2)
|
|
mid_y = (cy1 + cy2) // 2 + ((neighbor_idx * 7) % 5 - 2)
|
|
|
|
corridor_map.dig_bezier(
|
|
points=((cx1, cy1), (mid_x, cy1), (mid_x, cy2), (cx2, cy2)),
|
|
start_radius=1.5, end_radius=1.5,
|
|
start_height=0.0, end_height=0.0
|
|
)
|
|
|
|
# Add corridors - dig_bezier creates low values where corridors are
|
|
# We want high values there, so invert the corridor map logic
|
|
for y in range(GRID_HEIGHT):
|
|
for x in range(GRID_WIDTH):
|
|
corr_val = corridor_map.get((x, y))
|
|
if corr_val < 0.5: # Corridor was dug here
|
|
current = combined.get((x, y))
|
|
combined.fill(max(current, 0.7), pos=(x, y), size=(1, 1))
|
|
|
|
# Step 6: Render with cave aesthetics
|
|
for y in range(GRID_HEIGHT):
|
|
for x in range(GRID_WIDTH):
|
|
h = combined.get((x, y))
|
|
|
|
if h < 0.30:
|
|
# Solid rock/wall - darker
|
|
base = 30 + int(cave_map.get((x, y)) * 20)
|
|
color_layer.set(((x, y)), mcrfpy.Color(base + 10, base + 5, base + 15))
|
|
elif h < 0.40:
|
|
# Cave wall edge (rough transition)
|
|
t = (h - 0.30) / 0.10
|
|
base = int(40 + t * 15)
|
|
color_layer.set(((x, y)), mcrfpy.Color(base + 10, base + 5, base + 15))
|
|
elif h < 0.55:
|
|
# Cave floor (natural stone)
|
|
t = (h - 0.40) / 0.15
|
|
base = 65 + int(t * 20)
|
|
var = ((x * 7 + y * 11) % 10)
|
|
color_layer.set(((x, y)), mcrfpy.Color(base + var, base - 5 + var, base - 10))
|
|
elif h < 0.70:
|
|
# Corridor/worked passage
|
|
base = 85 + ((x + y) % 2) * 5
|
|
color_layer.set(((x, y)), mcrfpy.Color(base, base - 3, base - 6))
|
|
else:
|
|
# Room floor (finely worked stone)
|
|
base = 105 + ((x + y) % 2) * 8
|
|
color_layer.set(((x, y)), mcrfpy.Color(base, base - 8, base - 12))
|
|
|
|
# Mark room centers with special tile
|
|
for leaf in bsp.leaves():
|
|
cx, cy = leaf.center()
|
|
if 0 <= cx < GRID_WIDTH and 0 <= cy < GRID_HEIGHT:
|
|
color_layer.set(((cx, cy)), mcrfpy.Color(160, 140, 120))
|
|
# Cross pattern
|
|
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
|
nx, ny = cx + dx, cy + dy
|
|
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
|
color_layer.set(((nx, ny)), mcrfpy.Color(140, 125, 105))
|
|
|
|
# Outer border
|
|
for x in range(GRID_WIDTH):
|
|
color_layer.set(((x, 0)), mcrfpy.Color(20, 15, 25))
|
|
color_layer.set(((x, GRID_HEIGHT - 1)), mcrfpy.Color(20, 15, 25))
|
|
for y in range(GRID_HEIGHT):
|
|
color_layer.set(((0, y)), mcrfpy.Color(20, 15, 25))
|
|
color_layer.set(((GRID_WIDTH - 1, y)), mcrfpy.Color(20, 15, 25))
|
|
|
|
# Title
|
|
title = mcrfpy.Caption(text="Cave-Carved Dungeon: BSP + Noise + Erosion", pos=(10, 10))
|
|
title.fill_color = mcrfpy.Color(255, 255, 255)
|
|
title.outline = 1
|
|
title.outline_color = mcrfpy.Color(0, 0, 0)
|
|
scene.children.append(title)
|
|
|
|
|
|
# Setup
|
|
scene = mcrfpy.Scene("cave_dungeon")
|
|
|
|
grid = mcrfpy.Grid(
|
|
grid_size=(GRID_WIDTH, GRID_HEIGHT),
|
|
pos=(0, 0),
|
|
size=(GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE),
|
|
layers={}
|
|
)
|
|
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
|
color_layer = grid.add_layer("color", z_index=-1)
|
|
scene.children.append(grid)
|
|
|
|
scene.activate()
|
|
|
|
# Run the demo
|
|
run_demo(0)
|
|
|
|
# Take screenshot
|
|
automation.screenshot("procgen_30_advanced_cave_dungeon.png")
|
|
print("Screenshot saved: procgen_30_advanced_cave_dungeon.png")
|