base freq 0.08→0.012 (~260 cell wide hills, was ~40) detail freq 0.23→0.04 (~78 cell features, was ~14) micro freq 0.57→0.11 (~28 cell ripples, was ~6) amplitudes reduced: base ±20→±15, detail ±10→±7.5, micro ±5→±2.5 Result: wide rolling hills instead of narrow sharp peaks.
1296 lines
41 KiB
Rust
1296 lines
41 KiB
Rust
use crate::entity::player::Player;
|
|
use crate::entity::{EntityManager, ItemManager, ItemType};
|
|
use crate::world::cell::{Cell, MaterialId};
|
|
use crate::world::cellular::CellularAutomaton;
|
|
use crate::world::chunk::CHUNK_SIZE;
|
|
use crate::world::chunked_grid::ChunkedGrid;
|
|
|
|
pub const WORLD_SCALE: i32 = 5;
|
|
|
|
#[inline]
|
|
fn sc(v: i32) -> i32 {
|
|
v * WORLD_SCALE
|
|
}
|
|
|
|
#[inline]
|
|
fn scf(v: f32) -> f32 {
|
|
v * WORLD_SCALE as f32
|
|
}
|
|
|
|
pub struct WorldGenerator<'a> {
|
|
ca: &'a mut CellularAutomaton,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
struct Rect {
|
|
x: i32,
|
|
y: i32,
|
|
w: i32,
|
|
h: i32,
|
|
}
|
|
|
|
impl<'a> WorldGenerator<'a> {
|
|
pub fn new(ca: &'a mut CellularAutomaton) -> Self {
|
|
Self { ca }
|
|
}
|
|
|
|
pub fn generate(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
items: &mut ItemManager,
|
|
player: &mut Player,
|
|
entities: &mut EntityManager,
|
|
depth: u32,
|
|
) -> (f32, f32) {
|
|
let (px, py) = if grid.width <= 2048 && grid.height <= 2048 {
|
|
if depth <= 3 {
|
|
self.generate_surface(grid, depth)
|
|
} else if depth <= 6 {
|
|
self.generate_caves(grid, depth)
|
|
} else {
|
|
self.generate_dungeon(grid, items, depth)
|
|
}
|
|
} else {
|
|
self.generate_spawn_region(grid, items, depth)
|
|
};
|
|
|
|
player.spawn_at(entities, px as f32, py as f32);
|
|
if let Some(e) = player.entity_mut(entities) {
|
|
e.facing_right = true;
|
|
}
|
|
|
|
self.place_items(grid, items, px, py, depth);
|
|
(px as f32, py as f32)
|
|
}
|
|
|
|
fn generate_spawn_region(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
_items: &mut ItemManager,
|
|
_depth: u32,
|
|
) -> (i32, i32) {
|
|
let sx = if grid.is_infinite() {
|
|
1000
|
|
} else {
|
|
(grid.width as i32 / 2).max(100)
|
|
};
|
|
let surface_y = self.surface_height_world(sx);
|
|
let spawn_cx = sx / CHUNK_SIZE as i32;
|
|
let spawn_cy = (surface_y / CHUNK_SIZE as i32).max(0);
|
|
for dy in -1..=2 {
|
|
for dx in -2..=2 {
|
|
let cx = spawn_cx + dx;
|
|
let cy = spawn_cy + dy;
|
|
if grid.in_bounds(cx * CHUNK_SIZE as i32, cy * CHUNK_SIZE as i32) {
|
|
self.generate_chunk(grid, cx, cy);
|
|
}
|
|
}
|
|
}
|
|
grid.set_material(sx, surface_y, MaterialId::Stairs);
|
|
if grid.get(sx, surface_y + 1).is_empty() {
|
|
grid.set_material(sx, surface_y + 1, MaterialId::Stone);
|
|
}
|
|
let spawn_y = surface_y - sc(8);
|
|
(sx, spawn_y)
|
|
}
|
|
|
|
pub fn generate_chunk(&mut self, grid: &mut ChunkedGrid, cx: i32, cy: i32) {
|
|
if grid.is_chunk_generated(cx, cy) {
|
|
return;
|
|
}
|
|
if cy < 2 {
|
|
self.generate_surface_chunk(grid, cx, cy);
|
|
} else if cy < 6 {
|
|
self.generate_cave_chunk(grid, cx, cy);
|
|
} else {
|
|
self.generate_dungeon_chunk(grid, cx, cy);
|
|
}
|
|
if let Some(chunk) = grid.get_chunk_mut(cx, cy) {
|
|
chunk.generated = true;
|
|
chunk.modified = true;
|
|
chunk.was_modified = false;
|
|
chunk.dirty = None;
|
|
}
|
|
}
|
|
|
|
fn generate_surface_chunk(&mut self, grid: &mut ChunkedGrid, cx: i32, cy: i32) {
|
|
let x0 = cx * CHUNK_SIZE as i32;
|
|
let y0 = cy * CHUNK_SIZE as i32;
|
|
let mut surface = Vec::with_capacity(CHUNK_SIZE);
|
|
for lx in 0..CHUNK_SIZE as i32 {
|
|
let x = x0 + lx;
|
|
let s = self.surface_height_world(x);
|
|
surface.push(s);
|
|
for y in y0..(y0 + CHUNK_SIZE as i32) {
|
|
if y < s {
|
|
continue;
|
|
}
|
|
if y == s {
|
|
grid.set_material(x, y, MaterialId::Grass);
|
|
} else if y > s + sc(8) {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
} else {
|
|
grid.set_material(x, y, MaterialId::Dirt);
|
|
}
|
|
}
|
|
}
|
|
let chunk_depth = (cy / 2).max(1) as u32;
|
|
self.place_surface_features_chunk(grid, cx, cy, &surface, chunk_depth);
|
|
grid.fill_border(MaterialId::Stone);
|
|
}
|
|
|
|
fn generate_cave_chunk(&mut self, grid: &mut ChunkedGrid, cx: i32, cy: i32) {
|
|
let x0 = cx * CHUNK_SIZE as i32;
|
|
let y0 = cy * CHUNK_SIZE as i32;
|
|
let depth = (cy / 2).max(2) as u32;
|
|
for y in y0..(y0 + CHUNK_SIZE as i32) {
|
|
for x in x0..(x0 + CHUNK_SIZE as i32) {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
let fill_prob = 0.42;
|
|
let iterations = 4;
|
|
self.carve_ca_caves_chunk(grid, cx, cy, fill_prob, iterations);
|
|
let pool_count = (1 + depth / 3).min(3) as i32;
|
|
for _ in 0..pool_count {
|
|
let typ = self.random_cave_pool_type(depth);
|
|
self.place_cave_pool_chunk(grid, cx, cy, typ, sc(2), sc(4));
|
|
}
|
|
grid.fill_border(MaterialId::Stone);
|
|
}
|
|
|
|
fn generate_dungeon_chunk(&mut self, grid: &mut ChunkedGrid, cx: i32, cy: i32) {
|
|
let x0 = cx * CHUNK_SIZE as i32;
|
|
let y0 = cy * CHUNK_SIZE as i32;
|
|
for y in y0..(y0 + CHUNK_SIZE as i32) {
|
|
for x in x0..(x0 + CHUNK_SIZE as i32) {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
let root = Rect {
|
|
x: x0 + 6,
|
|
y: y0 + 6,
|
|
w: CHUNK_SIZE as i32 - 12,
|
|
h: CHUNK_SIZE as i32 - 12,
|
|
};
|
|
let tree = self.build_bsp(root);
|
|
let mut rooms = Vec::new();
|
|
self.collect_rooms(&tree, &mut rooms);
|
|
for room in &rooms {
|
|
self.carve_room(grid, *room);
|
|
}
|
|
self.connect_bsp_rooms(&tree, grid);
|
|
grid.fill_border(MaterialId::Stone);
|
|
if let Some(chunk) = grid.get_chunk_mut(cx, cy) {
|
|
chunk.modified = true;
|
|
chunk.was_modified = true;
|
|
}
|
|
}
|
|
|
|
fn generate_surface(&mut self, grid: &mut ChunkedGrid, depth: u32) -> (i32, i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
let mut surface = vec![h - 3; grid.width];
|
|
|
|
for x in 0..grid.width {
|
|
let s = self.surface_height(x as i32, h);
|
|
surface[x] = s;
|
|
for y in s..h - 2 {
|
|
if y == s {
|
|
let mat = if depth == 1 {
|
|
MaterialId::Grass
|
|
} else {
|
|
MaterialId::Dirt
|
|
};
|
|
grid.set_material(x as i32, y, mat);
|
|
} else if y > s + sc(8) {
|
|
grid.set_material(x as i32, y, MaterialId::Stone);
|
|
} else {
|
|
grid.set_material(x as i32, y, MaterialId::Dirt);
|
|
}
|
|
}
|
|
grid.set_material(x as i32, h - 2, MaterialId::Dirt);
|
|
grid.set_material(x as i32, h - 1, MaterialId::Stone);
|
|
}
|
|
|
|
self.carve_underground_caves(grid, &surface);
|
|
self.place_surface_features(grid, &surface, depth);
|
|
|
|
grid.fill_border(MaterialId::Stone);
|
|
|
|
for cy in 0..grid.chunks_y as i32 {
|
|
for cx in 0..grid.chunks_x as i32 {
|
|
grid.set_chunk_dirty(cx, cy, None);
|
|
if let Some(chunk) = grid.get_chunk_mut(cx, cy) {
|
|
chunk.modified = false;
|
|
chunk.was_modified = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
let sx = w / 2;
|
|
let mut surface_y = h - 3;
|
|
for y in 0..h {
|
|
let cell = grid.get(sx, y);
|
|
if cell.is_solid() && cell.material != MaterialId::Stone {
|
|
surface_y = y;
|
|
break;
|
|
}
|
|
}
|
|
grid.set_material(sx, surface_y, MaterialId::Stairs);
|
|
if grid.get(sx, surface_y + 1).is_empty() {
|
|
grid.set_material(sx, surface_y + 1, MaterialId::Stone);
|
|
}
|
|
|
|
let spawn_y = surface_y - 5;
|
|
(sx, spawn_y)
|
|
}
|
|
|
|
fn generate_caves(&mut self, grid: &mut ChunkedGrid, depth: u32) -> (i32, i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
|
|
for y in 0..h {
|
|
for x in 0..w {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
|
|
self.carve_ca_caves(grid, 0.38, 5);
|
|
self.seal_disconnected_regions(grid);
|
|
|
|
let pool_count = (2 + depth / 2).min(6) as i32;
|
|
for _ in 0..pool_count {
|
|
let typ = self.random_cave_pool_type(depth);
|
|
self.place_cave_pool(grid, typ, sc(3), sc(5));
|
|
}
|
|
|
|
let stalactites = self.random_range_i32(3, 7);
|
|
for _ in 0..stalactites {
|
|
let x = self.random_range_i32(8, w - 8);
|
|
let y_top = self.random_range_i32(3, 12);
|
|
let len = self.random_range_i32(sc(3), sc(9));
|
|
for dy in 0..len {
|
|
let y = y_top + dy;
|
|
if grid.get(x, y).material != MaterialId::Stone {
|
|
continue;
|
|
}
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
|
|
grid.fill_border(MaterialId::Stone);
|
|
|
|
for cy in 0..grid.chunks_y as i32 {
|
|
for cx in 0..grid.chunks_x as i32 {
|
|
grid.set_chunk_dirty(cx, cy, None);
|
|
if let Some(chunk) = grid.get_chunk_mut(cx, cy) {
|
|
chunk.modified = false;
|
|
chunk.was_modified = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
let spawn = self.find_safe_spawn(grid);
|
|
let stairs = self.find_empty_with_floor(grid, w - 15, h - 15);
|
|
if let Some((sx, sy)) = stairs {
|
|
grid.set_material(sx, sy, MaterialId::Stairs);
|
|
if grid.get(sx, sy + 1).is_empty() {
|
|
grid.set_material(sx, sy + 1, MaterialId::Stone);
|
|
}
|
|
}
|
|
|
|
spawn
|
|
}
|
|
|
|
fn generate_dungeon(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
items: &mut ItemManager,
|
|
_depth: u32,
|
|
) -> (i32, i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
|
|
for y in 0..h {
|
|
for x in 0..w {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
|
|
let root = Rect {
|
|
x: 8,
|
|
y: 8,
|
|
w: w - 16,
|
|
h: h - 16,
|
|
};
|
|
let tree = self.build_bsp(root);
|
|
let mut rooms = Vec::new();
|
|
self.collect_rooms(&tree, &mut rooms);
|
|
|
|
if rooms.is_empty() {
|
|
return self.fallback_spawn(grid);
|
|
}
|
|
|
|
for room in &rooms {
|
|
self.carve_room(grid, *room);
|
|
}
|
|
self.connect_bsp_rooms(&tree, grid);
|
|
grid.fill_border(MaterialId::Stone);
|
|
|
|
for cy in 0..grid.chunks_y as i32 {
|
|
for cx in 0..grid.chunks_x as i32 {
|
|
grid.set_chunk_dirty(cx, cy, None);
|
|
if let Some(chunk) = grid.get_chunk_mut(cx, cy) {
|
|
chunk.modified = false;
|
|
chunk.was_modified = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
let spawn_room = rooms[0];
|
|
let spawn_x = spawn_room.x + spawn_room.w / 2;
|
|
let spawn_y = spawn_room.y + spawn_room.h - 5;
|
|
let spawn = (spawn_x, spawn_y);
|
|
|
|
let mut stairs_room = rooms[0];
|
|
let mut best_dist = 0;
|
|
for room in &rooms {
|
|
let cx = room.x + room.w / 2;
|
|
let cy = room.y + room.h / 2;
|
|
let d = (cx - spawn_x).abs() + (cy - spawn_y).abs();
|
|
if d > best_dist {
|
|
best_dist = d;
|
|
stairs_room = *room;
|
|
}
|
|
}
|
|
let sx = stairs_room.x + stairs_room.w / 2;
|
|
let sy = stairs_room.y + stairs_room.h;
|
|
grid.set_material(sx, sy, MaterialId::Stairs);
|
|
|
|
let item_count = (3 + self.random_range_i32(0, 3)).min(rooms.len() as i32);
|
|
let mut placed_rooms = rooms.clone();
|
|
self.shuffle_rooms(&mut placed_rooms);
|
|
for i in 0..item_count as usize {
|
|
let room = placed_rooms[i];
|
|
let (ix, iy) = self.random_point_in_room(room);
|
|
if let Some(typ) = self.random_item_type() {
|
|
items.spawn(typ, ix, iy);
|
|
}
|
|
}
|
|
|
|
let trap_count = self.random_range_i32(1, 3).min(rooms.len() as i32);
|
|
for _ in 0..trap_count as usize {
|
|
let room = rooms[self.random_usize(rooms.len())];
|
|
let (tx, ty) = self.random_point_in_room(room);
|
|
if grid.get(tx, ty).is_empty() {
|
|
grid.set_material(tx, ty, MaterialId::Acid);
|
|
}
|
|
}
|
|
|
|
spawn
|
|
}
|
|
|
|
fn place_items(
|
|
&mut self,
|
|
grid: &ChunkedGrid,
|
|
items: &mut ItemManager,
|
|
px: i32,
|
|
py: i32,
|
|
depth: u32,
|
|
) {
|
|
if depth <= 3 {
|
|
let base_y = py + 1;
|
|
let offsets = [
|
|
(sc(-6), sc(1)),
|
|
(sc(6), sc(1)),
|
|
(sc(-3), sc(-8)),
|
|
(sc(10), sc(1)),
|
|
(sc(-10), sc(1)),
|
|
(sc(3), sc(-6)),
|
|
];
|
|
let types = [
|
|
ItemType::Sword,
|
|
ItemType::HealthPotion,
|
|
ItemType::LeatherArmor,
|
|
ItemType::Bow,
|
|
ItemType::Shield,
|
|
ItemType::ManaPotion,
|
|
];
|
|
for (i, (dx, dy)) in offsets.iter().enumerate() {
|
|
let x = px + dx;
|
|
let y = base_y + dy;
|
|
if grid.in_bounds(x, y) {
|
|
items.spawn(types[i], x, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_surface_features(&mut self, grid: &mut ChunkedGrid, surface: &[i32], depth: u32) {
|
|
let tree_count = self.random_range_i32(3, 7);
|
|
self.place_trees(grid, surface, tree_count);
|
|
|
|
let pool_count = (2 + depth / 2).min(5) as i32;
|
|
for _ in 0..pool_count {
|
|
let typ = self.random_surface_pool_type(depth);
|
|
self.place_surface_pool(grid, typ, sc(3), sc(6));
|
|
}
|
|
|
|
let dune_count = self.random_range_i32(1, 4);
|
|
self.place_sand_dunes(grid, surface, dune_count);
|
|
|
|
let wall_count = self.random_range_i32(1, 4);
|
|
self.place_walls(grid, surface, wall_count);
|
|
}
|
|
|
|
fn place_surface_features_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
cx: i32,
|
|
_cy: i32,
|
|
surface: &[i32],
|
|
depth: u32,
|
|
) {
|
|
let x0 = cx * CHUNK_SIZE as i32;
|
|
let x1 = x0 + CHUNK_SIZE as i32;
|
|
let tree_count = self.random_range_i32(0, 2);
|
|
self.place_trees_chunk(grid, x0, x1, surface, tree_count);
|
|
|
|
let pool_count = (depth / 2).min(2) as i32;
|
|
for _ in 0..pool_count {
|
|
let typ = self.random_surface_pool_type(depth);
|
|
self.place_surface_pool_chunk(grid, x0, x1, surface, typ, sc(2), sc(4));
|
|
}
|
|
|
|
let dune_count = self.random_range_i32(0, 2);
|
|
self.place_sand_dunes_chunk(grid, x0, x1, surface, dune_count);
|
|
|
|
let wall_count = self.random_range_i32(0, 2);
|
|
self.place_walls_chunk(grid, x0, x1, surface, wall_count);
|
|
}
|
|
|
|
fn place_trees_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
x0: i32,
|
|
_x1: i32,
|
|
surface: &[i32],
|
|
count: i32,
|
|
) {
|
|
for _ in 0..count {
|
|
let lx = self.random_range_i32(2, CHUNK_SIZE as i32 - 2);
|
|
let x = x0 + lx;
|
|
let s = surface[lx as usize];
|
|
if s < 10 {
|
|
continue;
|
|
}
|
|
for y in (s - sc(6))..s {
|
|
if grid.in_bounds(x, y) {
|
|
grid.set_material(x, y, MaterialId::Wood);
|
|
}
|
|
}
|
|
for dy in -sc(2)..=0 {
|
|
for dx in -sc(2)..=sc(2) {
|
|
if dx * dx + dy * dy <= sc(5) {
|
|
let cx = x + dx;
|
|
let cy = s - sc(6) + dy;
|
|
if grid.in_bounds(cx, cy) && grid.get(cx, cy).is_empty() {
|
|
grid.set_material(cx, cy, MaterialId::Grass);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_surface_pool_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
x0: i32,
|
|
_x1: i32,
|
|
surface: &[i32],
|
|
typ: MaterialId,
|
|
min_r: i32,
|
|
max_r: i32,
|
|
) {
|
|
let lx = self.random_range_i32(4, CHUNK_SIZE as i32 - 4);
|
|
let x = x0 + lx;
|
|
let s = surface[lx as usize];
|
|
let r = self.random_range_i32(min_r, max_r);
|
|
let cy = self.random_range_i32(s + 2, s + r + 2);
|
|
for dy in -r..=r {
|
|
for dx in -r..=r {
|
|
if dx * dx + dy * dy <= r * r {
|
|
let px = x + dx;
|
|
let py = cy + dy;
|
|
if grid.in_bounds(px, py) {
|
|
grid.set_material(px, py, typ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_cave_pool_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
cx: i32,
|
|
cy: i32,
|
|
typ: MaterialId,
|
|
min_r: i32,
|
|
max_r: i32,
|
|
) {
|
|
let x0 = cx * CHUNK_SIZE as i32;
|
|
let y0 = cy * CHUNK_SIZE as i32;
|
|
let x1 = x0 + CHUNK_SIZE as i32;
|
|
let y1 = y0 + CHUNK_SIZE as i32;
|
|
for _ in 0..100 {
|
|
let px = self.random_range_i32(x0 + 4, x1 - 4);
|
|
let py = self.random_range_i32(y0 + 4, y1 - 4);
|
|
if grid.get(px, py).is_empty() {
|
|
continue;
|
|
}
|
|
let mut has_empty_neighbor = false;
|
|
for (dx, dy) in NEIGHBORS4 {
|
|
if grid.get(px + dx, py + dy).is_empty() {
|
|
has_empty_neighbor = true;
|
|
break;
|
|
}
|
|
}
|
|
if !has_empty_neighbor {
|
|
continue;
|
|
}
|
|
let r = self.random_range_i32(min_r, max_r);
|
|
for dy in -r..=r {
|
|
for dx in -r..=r {
|
|
if dx * dx + dy * dy <= r * r {
|
|
let x = px + dx;
|
|
let y = py + dy;
|
|
if grid.in_bounds(x, y) && !grid.get(x, y).is_empty() {
|
|
grid.set_material(x, y, typ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
fn place_sand_dunes_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
x0: i32,
|
|
x1: i32,
|
|
surface: &[i32],
|
|
count: i32,
|
|
) {
|
|
for _ in 0..count {
|
|
let lx = self.random_range_i32(4, (x1 - x0 - 4).max(5));
|
|
let x = x0 + lx;
|
|
let s = surface[lx as usize];
|
|
let width = self.random_range_i32(sc(4), sc(10));
|
|
for dx in -width / 2..=width / 2 {
|
|
let pile = (width / 2 - dx.abs()).max(1) + 1;
|
|
for dy in 0..pile {
|
|
let y = s - 1 - dy;
|
|
if grid.in_bounds(x + dx, y) && grid.get(x + dx, y).is_empty() {
|
|
grid.set_material(x + dx, y, MaterialId::Sand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_walls_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
x0: i32,
|
|
x1: i32,
|
|
surface: &[i32],
|
|
count: i32,
|
|
) {
|
|
for _ in 0..count {
|
|
let lx = self.random_range_i32(4, (x1 - x0 - 4).max(5));
|
|
let x = x0 + lx;
|
|
let s = surface[lx as usize];
|
|
let h = self.random_range_i32(sc(2), sc(5));
|
|
for y in (s - h)..s {
|
|
if grid.in_bounds(x, y) {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 1, y) {
|
|
grid.set_material(x + 1, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 2, y) {
|
|
grid.set_material(x + 2, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 3, y) {
|
|
grid.set_material(x + 3, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 4, y) {
|
|
grid.set_material(x + 4, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_trees(&mut self, grid: &mut ChunkedGrid, surface: &[i32], count: i32) {
|
|
let w = grid.width as i32;
|
|
for _ in 0..count {
|
|
let x = self.random_range_i32(10, w - 10);
|
|
let s = surface[x as usize];
|
|
if s < 10 {
|
|
continue;
|
|
}
|
|
for y in (s - sc(6))..s {
|
|
if grid.in_bounds(x, y) {
|
|
grid.set_material(x, y, MaterialId::Wood);
|
|
}
|
|
}
|
|
for dy in -sc(2)..=0 {
|
|
for dx in -sc(2)..=sc(2) {
|
|
if dx * dx + dy * dy <= sc(5) {
|
|
let cx = x + dx;
|
|
let cy = s - sc(6) + dy;
|
|
if grid.in_bounds(cx, cy) && grid.get(cx, cy).is_empty() {
|
|
grid.set_material(cx, cy, MaterialId::Grass);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_surface_pool(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
typ: MaterialId,
|
|
min_r: i32,
|
|
max_r: i32,
|
|
) {
|
|
let w = grid.width as i32;
|
|
let cx = self.random_range_i32(15, w - 15);
|
|
let surface = self.find_surface_near(grid, cx);
|
|
let r = self.random_range_i32(min_r, max_r);
|
|
let cy = self.random_range_i32(surface + 2, surface + r + 2);
|
|
for dy in -r..=r {
|
|
for dx in -r..=r {
|
|
if dx * dx + dy * dy <= r * r {
|
|
let x = cx + dx;
|
|
let y = cy + dy;
|
|
if grid.in_bounds(x, y) {
|
|
grid.set_material(x, y, typ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_cave_pool(&mut self, grid: &mut ChunkedGrid, typ: MaterialId, min_r: i32, max_r: i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
for _ in 0..100 {
|
|
let cx = self.random_range_i32(10, w - 10);
|
|
let cy = self.random_range_i32(10, h - 10);
|
|
if grid.get(cx, cy).is_empty() {
|
|
continue;
|
|
}
|
|
let mut has_empty_neighbor = false;
|
|
for (dx, dy) in NEIGHBORS4 {
|
|
if grid.get(cx + dx, cy + dy).is_empty() {
|
|
has_empty_neighbor = true;
|
|
break;
|
|
}
|
|
}
|
|
if !has_empty_neighbor {
|
|
continue;
|
|
}
|
|
let r = self.random_range_i32(min_r, max_r);
|
|
for dy in -r..=r {
|
|
for dx in -r..=r {
|
|
if dx * dx + dy * dy <= r * r {
|
|
let x = cx + dx;
|
|
let y = cy + dy;
|
|
if grid.in_bounds(x, y) && !grid.get(x, y).is_empty() {
|
|
grid.set_material(x, y, typ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
fn place_sand_dunes(&mut self, grid: &mut ChunkedGrid, surface: &[i32], count: i32) {
|
|
let w = grid.width as i32;
|
|
for _ in 0..count {
|
|
let x = self.random_range_i32(15, w - 15);
|
|
let s = surface[x as usize];
|
|
let width = self.random_range_i32(sc(6), sc(16));
|
|
for dx in -width / 2..=width / 2 {
|
|
let pile = (width / 2 - dx.abs()).max(1) + 1;
|
|
for dy in 0..pile {
|
|
let y = s - 1 - dy;
|
|
if grid.in_bounds(x + dx, y) && grid.get(x + dx, y).is_empty() {
|
|
grid.set_material(x + dx, y, MaterialId::Sand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn place_walls(&mut self, grid: &mut ChunkedGrid, surface: &[i32], count: i32) {
|
|
let w = grid.width as i32;
|
|
for _ in 0..count {
|
|
let x = self.random_range_i32(10, w - 10);
|
|
let s = surface[x as usize];
|
|
let h = self.random_range_i32(sc(3), sc(7));
|
|
for y in (s - h)..s {
|
|
if grid.in_bounds(x, y) {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 1, y) {
|
|
grid.set_material(x + 1, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 2, y) {
|
|
grid.set_material(x + 2, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 3, y) {
|
|
grid.set_material(x + 3, y, MaterialId::Stone);
|
|
}
|
|
if grid.in_bounds(x + 4, y) {
|
|
grid.set_material(x + 4, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn carve_underground_caves(&mut self, grid: &mut ChunkedGrid, surface: &[i32]) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
for _ in 0..8 {
|
|
let cx = self.random_range_i32(10, w - 10);
|
|
let lower = (h * 2 / 3).max(surface[cx as usize] + 12);
|
|
let cy = self.random_range_i32(lower, h - 10);
|
|
let r = self.random_range_i32(sc(3), sc(6));
|
|
for dy in -r..=r {
|
|
for dx in -r..=r {
|
|
if dx * dx + dy * dy <= r * r {
|
|
let x = cx + dx;
|
|
let y = cy + dy;
|
|
if grid.in_bounds(x, y) && y > surface[x as usize] + 4 {
|
|
grid.set(x, y, Cell::empty());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn carve_ca_caves(&mut self, grid: &mut ChunkedGrid, fill_prob: f64, iterations: i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
for y in 1..h - 1 {
|
|
for x in 1..w - 1 {
|
|
if self.ca.random_u32() as f64 / (u32::MAX as f64) < fill_prob {
|
|
grid.set(x, y, Cell::empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
let size = (w * h) as usize;
|
|
let mut buf = Vec::with_capacity(size);
|
|
for y in 0..h {
|
|
for x in 0..w {
|
|
buf.push(grid.get(x, y));
|
|
}
|
|
}
|
|
for _ in 0..iterations {
|
|
for y in 1..h - 1 {
|
|
for x in 1..w - 1 {
|
|
let walls = self.wall_count(grid, x, y);
|
|
let i = (y * w + x) as usize;
|
|
if walls > 4 {
|
|
buf[i] = Cell::new(MaterialId::Stone);
|
|
} else if walls < 4 {
|
|
buf[i] = Cell::empty();
|
|
}
|
|
}
|
|
}
|
|
for y in 0..h {
|
|
for x in 0..w {
|
|
let i = (y * w + x) as usize;
|
|
grid.set(x, y, buf[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn carve_ca_caves_chunk(
|
|
&mut self,
|
|
grid: &mut ChunkedGrid,
|
|
cx: i32,
|
|
cy: i32,
|
|
fill_prob: f64,
|
|
iterations: i32,
|
|
) {
|
|
let x0 = cx * CHUNK_SIZE as i32;
|
|
let y0 = cy * CHUNK_SIZE as i32;
|
|
let x1 = x0 + CHUNK_SIZE as i32;
|
|
let y1 = y0 + CHUNK_SIZE as i32;
|
|
for y in y0 + 1..y1 - 1 {
|
|
for x in x0 + 1..x1 - 1 {
|
|
if self.ca.random_u32() as f64 / (u32::MAX as f64) < fill_prob {
|
|
grid.set(x, y, Cell::empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
let size = CHUNK_SIZE * CHUNK_SIZE;
|
|
let mut buf = Vec::with_capacity(size);
|
|
for y in y0..y1 {
|
|
for x in x0..x1 {
|
|
buf.push(grid.get(x, y));
|
|
}
|
|
}
|
|
let w = CHUNK_SIZE as i32;
|
|
for _ in 0..iterations {
|
|
for y in y0 + 1..y1 - 1 {
|
|
for x in x0 + 1..x1 - 1 {
|
|
let walls = self.wall_count(grid, x, y);
|
|
let ly = y - y0;
|
|
let lx = x - x0;
|
|
let i = (ly * w + lx) as usize;
|
|
if walls > 4 {
|
|
buf[i] = Cell::new(MaterialId::Stone);
|
|
} else if walls < 4 {
|
|
buf[i] = Cell::empty();
|
|
}
|
|
}
|
|
}
|
|
for y in y0..y1 {
|
|
for x in x0..x1 {
|
|
let ly = y - y0;
|
|
let lx = x - x0;
|
|
let i = (ly * w + lx) as usize;
|
|
grid.set(x, y, buf[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn wall_count(&self, grid: &ChunkedGrid, x: i32, y: i32) -> i32 {
|
|
let mut count = 0;
|
|
for dy in -1..=1 {
|
|
for dx in -1..=1 {
|
|
if dx == 0 && dy == 0 {
|
|
continue;
|
|
}
|
|
let nx = x + dx;
|
|
let ny = y + dy;
|
|
if !grid.in_bounds(nx, ny) {
|
|
count += 1;
|
|
} else {
|
|
let cell = grid.get(nx, ny);
|
|
if cell.is_solid() || cell.material == MaterialId::Stone {
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
count
|
|
}
|
|
|
|
fn seal_disconnected_regions(&mut self, grid: &mut ChunkedGrid) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
let size = (w * h) as usize;
|
|
let mut visited = vec![false; size];
|
|
let mut best = Vec::new();
|
|
|
|
for y in 0..h {
|
|
for x in 0..w {
|
|
let idx = (y * w + x) as usize;
|
|
if grid.get(x, y).is_empty() && !visited[idx] {
|
|
let mut comp = Vec::new();
|
|
self.flood_fill(grid, &mut visited, x, y, &mut comp);
|
|
if comp.len() > best.len() {
|
|
best = comp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut in_best = vec![false; size];
|
|
for (x, y) in &best {
|
|
in_best[(*y * w + *x) as usize] = true;
|
|
}
|
|
|
|
for y in 0..h {
|
|
for x in 0..w {
|
|
let idx = (y * w + x) as usize;
|
|
if grid.get(x, y).is_empty() && !in_best[idx] {
|
|
grid.set_material(x, y, MaterialId::Stone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn flood_fill(
|
|
&self,
|
|
grid: &ChunkedGrid,
|
|
visited: &mut [bool],
|
|
x: i32,
|
|
y: i32,
|
|
comp: &mut Vec<(i32, i32)>,
|
|
) {
|
|
let w = grid.width as i32;
|
|
let mut stack = vec![(x, y)];
|
|
while let Some((cx, cy)) = stack.pop() {
|
|
let idx = (cy * w + cx) as usize;
|
|
if visited[idx] || !grid.in_bounds(cx, cy) || !grid.get(cx, cy).is_empty() {
|
|
continue;
|
|
}
|
|
visited[idx] = true;
|
|
comp.push((cx, cy));
|
|
for &(dx, dy) in &NEIGHBORS4 {
|
|
stack.push((cx + dx, cy + dy));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_bsp(&mut self, rect: Rect) -> BspNode {
|
|
let mut node = BspNode {
|
|
left: None,
|
|
right: None,
|
|
room: None,
|
|
};
|
|
if let Some((left, right)) = self.split_rect(rect) {
|
|
node.left = Some(Box::new(self.build_bsp(left)));
|
|
node.right = Some(Box::new(self.build_bsp(right)));
|
|
} else {
|
|
node.room = Some(self.carve_room_rect(rect));
|
|
}
|
|
node
|
|
}
|
|
|
|
fn split_rect(&mut self, rect: Rect) -> Option<(Rect, Rect)> {
|
|
let min_size = sc(9);
|
|
if rect.w < min_size * 2 || rect.h < min_size * 2 {
|
|
return None;
|
|
}
|
|
if rect.w > rect.h {
|
|
let split = self.random_range_i32(min_size, rect.w - min_size);
|
|
let left = Rect {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
w: split,
|
|
h: rect.h,
|
|
};
|
|
let right = Rect {
|
|
x: rect.x + split,
|
|
y: rect.y,
|
|
w: rect.w - split,
|
|
h: rect.h,
|
|
};
|
|
Some((left, right))
|
|
} else {
|
|
let split = self.random_range_i32(min_size, rect.h - min_size);
|
|
let top = Rect {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
w: rect.w,
|
|
h: split,
|
|
};
|
|
let bottom = Rect {
|
|
x: rect.x,
|
|
y: rect.y + split,
|
|
w: rect.w,
|
|
h: rect.h - split,
|
|
};
|
|
Some((top, bottom))
|
|
}
|
|
}
|
|
|
|
fn carve_room_rect(&mut self, rect: Rect) -> Rect {
|
|
let min_w = sc(4);
|
|
let min_h = sc(4);
|
|
let max_pad_x = ((rect.w - min_w) / 2).max(1);
|
|
let max_pad_y = ((rect.h - min_h) / 2).max(1);
|
|
let pad_x = self.random_range_i32(1, max_pad_x + 1);
|
|
let pad_y = self.random_range_i32(1, max_pad_y + 1);
|
|
Rect {
|
|
x: rect.x + pad_x,
|
|
y: rect.y + pad_y,
|
|
w: rect.w - pad_x * 2,
|
|
h: rect.h - pad_y * 2,
|
|
}
|
|
}
|
|
|
|
fn carve_room(&mut self, grid: &mut ChunkedGrid, room: Rect) {
|
|
for y in room.y..room.y + room.h {
|
|
for x in room.x..room.x + room.w {
|
|
grid.set(x, y, Cell::empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn collect_rooms(&self, node: &BspNode, rooms: &mut Vec<Rect>) {
|
|
if let Some(room) = node.room {
|
|
rooms.push(room);
|
|
}
|
|
if let Some(ref left) = node.left {
|
|
self.collect_rooms(left, rooms);
|
|
}
|
|
if let Some(ref right) = node.right {
|
|
self.collect_rooms(right, rooms);
|
|
}
|
|
}
|
|
|
|
fn connect_bsp_rooms(&mut self, node: &BspNode, grid: &mut ChunkedGrid) {
|
|
if let (Some(left), Some(right)) = (&node.left, &node.right) {
|
|
let r1 = self.find_first_room(left);
|
|
let r2 = self.find_first_room(right);
|
|
if let (Some(a), Some(b)) = (r1, r2) {
|
|
self.carve_l_corridor(grid, &a, &b);
|
|
}
|
|
self.connect_bsp_rooms(left, grid);
|
|
self.connect_bsp_rooms(right, grid);
|
|
}
|
|
}
|
|
|
|
fn find_first_room(&self, node: &BspNode) -> Option<Rect> {
|
|
if let Some(room) = node.room {
|
|
return Some(room);
|
|
}
|
|
if let Some(ref left) = node.left {
|
|
if let Some(room) = self.find_first_room(left) {
|
|
return Some(room);
|
|
}
|
|
}
|
|
if let Some(ref right) = node.right {
|
|
if let Some(room) = self.find_first_room(right) {
|
|
return Some(room);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn carve_l_corridor(&mut self, grid: &mut ChunkedGrid, a: &Rect, b: &Rect) {
|
|
let c1 = (a.x + a.w / 2, a.y + a.h / 2);
|
|
let c2 = (b.x + b.w / 2, b.y + b.h / 2);
|
|
let cw = sc(2) / 2;
|
|
let x0 = c1.0.min(c2.0);
|
|
let x1 = c1.0.max(c2.0);
|
|
for x in x0..=x1 {
|
|
for dy in 0..cw {
|
|
if grid.in_bounds(x, c1.1 + dy) {
|
|
grid.set(x, c1.1 + dy, Cell::empty());
|
|
}
|
|
}
|
|
}
|
|
let y0 = c1.1.min(c2.1);
|
|
let y1 = c1.1.max(c2.1);
|
|
for y in y0..=y1 {
|
|
for dx in 0..cw {
|
|
if grid.in_bounds(c2.0 + dx, y) {
|
|
grid.set(c2.0 + dx, y, Cell::empty());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn find_safe_spawn(&mut self, grid: &ChunkedGrid) -> (i32, i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
for floor_y in 10..h - 10 {
|
|
for x in 10..w - 10 {
|
|
if !grid.get(x, floor_y).is_solid() {
|
|
continue;
|
|
}
|
|
let cy = floor_y - 3;
|
|
if !self.vertical_clear(grid, x, cy) {
|
|
continue;
|
|
}
|
|
if !self.horizontal_clear(grid, x, cy) {
|
|
continue;
|
|
}
|
|
return (x, cy);
|
|
}
|
|
}
|
|
(w / 2, h / 2)
|
|
}
|
|
|
|
fn find_empty_with_floor(
|
|
&mut self,
|
|
grid: &ChunkedGrid,
|
|
target_x: i32,
|
|
target_y: i32,
|
|
) -> Option<(i32, i32)> {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
let mut best = None;
|
|
let mut best_dist = i32::MAX;
|
|
for y in 5..h - 5 {
|
|
for x in 5..w - 5 {
|
|
if grid.get(x, y).is_empty()
|
|
&& grid.in_bounds(x, y + 1)
|
|
&& grid.get(x, y + 1).is_solid()
|
|
{
|
|
let d = (x - target_x).abs() + (y - target_y).abs();
|
|
if d < best_dist {
|
|
best_dist = d;
|
|
best = Some((x, y));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
best
|
|
}
|
|
|
|
fn vertical_clear(&self, grid: &ChunkedGrid, x: i32, y: i32) -> bool {
|
|
for dy in -4..=2 {
|
|
if !grid.in_bounds(x, y + dy) || !grid.get(x, y + dy).is_empty() {
|
|
return false;
|
|
}
|
|
}
|
|
grid.in_bounds(x, y + 3) && grid.get(x, y + 3).is_solid()
|
|
}
|
|
|
|
fn horizontal_clear(&self, grid: &ChunkedGrid, x: i32, y: i32) -> bool {
|
|
for dx in -3..=3 {
|
|
if !grid.in_bounds(x + dx, y) || !grid.get(x + dx, y).is_empty() {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn fallback_spawn(&mut self, grid: &mut ChunkedGrid) -> (i32, i32) {
|
|
let w = grid.width as i32;
|
|
let h = grid.height as i32;
|
|
for y in (10..h - 10).rev() {
|
|
for x in 10..w - 10 {
|
|
if grid.get(x, y).is_empty() {
|
|
grid.set(x, y, Cell::empty());
|
|
return (x, y);
|
|
}
|
|
}
|
|
}
|
|
(w / 2, h / 2)
|
|
}
|
|
|
|
fn surface_height(&mut self, x: i32, h: i32) -> i32 {
|
|
let base = (h - 3) - ((x as f32 * 0.012).sin() * scf(3.0)) as i32;
|
|
let detail = ((x as f32 * 0.04).sin() * scf(1.5)) as i32;
|
|
let micro = ((x as f32 * 0.11).sin() * scf(0.5)) as i32;
|
|
(base + detail + micro).max(10).min(h - 3)
|
|
}
|
|
|
|
const SURFACE_BASE_Y: i32 = 120;
|
|
|
|
fn surface_height_world(&mut self, x: i32) -> i32 {
|
|
let base = Self::SURFACE_BASE_Y - ((x as f32 * 0.012).sin() * scf(3.0)) as i32;
|
|
let detail = ((x as f32 * 0.04).sin() * scf(1.5)) as i32;
|
|
let micro = ((x as f32 * 0.11).sin() * scf(0.5)) as i32;
|
|
(base + detail + micro).max(10)
|
|
}
|
|
|
|
fn find_surface_near(&mut self, grid: &ChunkedGrid, x: i32) -> i32 {
|
|
let h = grid.height as i32;
|
|
for y in 0..h {
|
|
if grid.get(x, y).is_solid() && grid.get(x, y).material != MaterialId::Stone {
|
|
return y;
|
|
}
|
|
}
|
|
h - 3
|
|
}
|
|
|
|
fn random_surface_pool_type(&mut self, depth: u32) -> MaterialId {
|
|
let r = self.ca.random_u32() % 100;
|
|
if depth == 1 {
|
|
match r {
|
|
0..=50 => MaterialId::Water,
|
|
51..=80 => MaterialId::Sand,
|
|
_ => MaterialId::Acid,
|
|
}
|
|
} else {
|
|
match r {
|
|
0..=40 => MaterialId::Water,
|
|
41..=60 => MaterialId::Sand,
|
|
61..=80 => MaterialId::Acid,
|
|
_ => MaterialId::Lava,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn random_cave_pool_type(&mut self, depth: u32) -> MaterialId {
|
|
let r = self.ca.random_u32() % 100;
|
|
match depth {
|
|
4 => match r {
|
|
0..=50 => MaterialId::Water,
|
|
51..=75 => MaterialId::Lava,
|
|
_ => MaterialId::Acid,
|
|
},
|
|
_ => match r {
|
|
0..=30 => MaterialId::Water,
|
|
31..=60 => MaterialId::Lava,
|
|
_ => MaterialId::Acid,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn random_item_type(&mut self) -> Option<ItemType> {
|
|
let types = [
|
|
ItemType::Dagger,
|
|
ItemType::Sword,
|
|
ItemType::Bow,
|
|
ItemType::LeatherArmor,
|
|
ItemType::PlateArmor,
|
|
ItemType::Shield,
|
|
ItemType::HealthPotion,
|
|
ItemType::ManaPotion,
|
|
ItemType::Food,
|
|
ItemType::Scroll,
|
|
];
|
|
let idx = self.random_usize(types.len());
|
|
Some(types[idx])
|
|
}
|
|
|
|
fn random_point_in_room(&mut self, room: Rect) -> (i32, i32) {
|
|
let x = room.x + self.random_range_i32(1, room.w - 1);
|
|
let y = room.y + self.random_range_i32(1, room.h - 1);
|
|
(x, y)
|
|
}
|
|
|
|
fn shuffle_rooms(&mut self, rooms: &mut Vec<Rect>) {
|
|
for i in (1..rooms.len()).rev() {
|
|
let j = self.random_usize(i + 1);
|
|
rooms.swap(i, j);
|
|
}
|
|
}
|
|
|
|
fn random_range_i32(&mut self, min: i32, max: i32) -> i32 {
|
|
if max <= min {
|
|
return min;
|
|
}
|
|
min + (self.ca.random_u32() % (max - min) as u32) as i32
|
|
}
|
|
|
|
fn random_usize(&mut self, max: usize) -> usize {
|
|
if max == 0 {
|
|
return 0;
|
|
}
|
|
self.ca.random_usize(max)
|
|
}
|
|
}
|
|
|
|
struct BspNode {
|
|
left: Option<Box<BspNode>>,
|
|
right: Option<Box<BspNode>>,
|
|
room: Option<Rect>,
|
|
}
|
|
|
|
const NEIGHBORS4: [(i32, i32); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)];
|