Stepwise/source/procgen.py

147 lines
4.6 KiB
Python

from __future__ import annotations
from typing import Iterator, List, Tuple
import random
import tcod
import tile_types
import entity_types
from floor_map import FloorMap
#utils
def make_corridor(start: Tuple[int, int], end: Tuple[int, int]) -> Iterator[Tuple[int,int]]:
#simplistic, but it works
x1, y1 = start
x2, y2 = end
if random.random() < 0.5:
corner_x, corner_y = x2, y1
else:
corner_x, corner_y = x1, y2
for x, y in tcod.los.bresenham((x1, y1), (corner_x, corner_y)).tolist():
yield x, y
for x, y in tcod.los.bresenham((corner_x, corner_y), (x2, y2)).tolist():
yield x, y
class RectangularRoom:
def __init__(self, x: int, y: int, width: int, height: int):
self.x1 = x
self.y1 = y
self.x2 = x + width
self.y2 = y + height
@property
def center(self) -> Tuple[int, int]:
center_x = (self.x1 + self.x2) // 2
center_y = (self.y1 + self.y2) // 2
return center_x, center_y
@property
def inner(self) -> Tuple[slice, slice]:
return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
def intersects(self, other: RectangularRoom) -> bool:
return (
self.x1 <= other.x2 and
self.x2 >= other.x1 and
self.y1 <= other.y2 and
self.y2 >= other.y1
)
def spawn_monsters(floor_map: FloorMap, room: RectangularRoom, room_monsters_max: int, floor_monsters_max: int) -> None:
if "monster_count" not in floor_map.procgen_cache:
floor_map.procgen_cache["monster_count"] = 0
else:
if floor_monsters_max >= 0 and floor_map.procgen_cache["monster_count"] >= floor_monsters_max:
return #cap the monsters total
#There can only be one
if "gobbo_red" not in floor_map.procgen_cache:
floor_map.procgen_cache["gobbo_red"] = False
monster_count = random.randint(0, room_monsters_max)
for i in range(monster_count):
#admittedly weird layout here, because player isn't in the entities list yet
x, y = floor_map.player.x, floor_map.player.y
while x == floor_map.player.x and y == floor_map.player.y:
x = random.randint(room.x1 + 1, room.x2 - 1)
y = random.randint(room.y1 + 1, room.y2 - 1)
#if there's no entity at that position
if not any(entity.x == x and entity.y == y and not entity.walkable for entity in floor_map.entities):
#there's never more than one red gobbo, but there can be none at all
if not floor_map.procgen_cache["gobbo_red"] and random.random() < 0.2:
floor_map.procgen_cache["gobbo_red"] = True
entity_types.gobbo_red.spawn(x, y, floor_map)
else:
entity_types.gobbo.spawn(x, y, floor_map)
floor_map.procgen_cache["monster_count"] += 1
def spawn_items(floor_map: FloorMap, room: RectangularRoom, room_items_max: int, floor_items_max: int) -> None:
item_count = random.randint(0, room_items_max)
if "item_count" not in floor_map.procgen_cache:
floor_map.procgen_cache["item_count"] = 0
else:
if floor_items_max >= 0 and floor_map.procgen_cache["item_count"] >= floor_items_max:
return #cap the item total
for i in range(item_count):
x = random.randint(room.x1 + 1, room.x2 - 1)
y = random.randint(room.y1 + 1, room.y2 - 1)
#if there's no entity at that position (not really needed for walkable entities)
if not any(entity.x == x and entity.y == y for entity in floor_map.entities):
entity_types.potion_of_healing.spawn(x, y, floor_map)
floor_map.procgen_cache["item_count"] += 1
#generators
def generate_floor_map(
map_width: int,
map_height: int,
room_width_max: int,
room_height_max: int,
room_width_min: int = 6,
room_height_min: int = 6,
room_count_max: int = 20,
room_monsters_max: int = 2,
room_items_max: int = 1,
floor_monsters_max: int = -1,
floor_items_max: int = -1,
) -> FloorMap:
#simplistic floor generator - it'll get rewritten eventually
floor_map: FloorMap = FloorMap(map_width, map_height)
rooms: List[RectangularRoom] = []
for r in range(room_count_max):
room_width = random.randint(room_width_min, room_width_max)
room_height = random.randint(room_height_min, room_height_max)
x = random.randint(0, floor_map.width - room_width - 1)
y = random.randint(0, floor_map.height - room_height - 1)
new_room = RectangularRoom(x, y, room_width, room_height)
if any(new_room.intersects(other_room) for other_room in rooms):
continue #nope.jpg
floor_map.tiles[new_room.inner] = tile_types.floor
if len(rooms) == 0:
x, y = new_room.center
floor_map.player = entity_types.player.spawn(x, y, floor_map)
else:
for x, y in make_corridor(rooms[-1].center, new_room.center):
floor_map.tiles[x, y] = tile_types.floor
spawn_monsters(floor_map, new_room, room_monsters_max, floor_monsters_max)
spawn_items(floor_map, new_room, room_items_max, floor_items_max)
rooms.append(new_room)
return floor_map