示例#1
0
文件: world.py 项目: Spferical/frogue
def get_random_passable_position(level):
    pos = Pos(-1, -1)
    while level.is_blocked(pos):
        x = random.randint(1, level.width - 1)
        y = random.randint(1, level.height - 1)
        pos = Pos(x, y)
    return pos
示例#2
0
    def init(self, game_data, gfx_data):
        # first items
        self.consumable_markers = []
        x = self.pos.x + 30
        y = self.pos.y + 30
        for idx, item in enumerate(game_data.inventory.items):
            cm = ConsumableMarker(item,
                                  idx,
                                  pos=Pos(x, y),
                                  size=Size(self.slot_width, 50),
                                  parent=self)
            self.consumable_markers.append(cm)
            x += 150
            if idx % self.items_per_row == 0:
                y += 60
                x = self.pos.x + 30

        # then quickslots
        self.quickslot_markers = []
        x = self.pos.x + 10
        y = 410
        for qs in range(self.num_quickslots):
            qm = QuickslotMarker(qs,
                                 pos=Pos(x, y),
                                 size=Size(self.slot_width, 50),
                                 parent=self)
            self.quickslot_markers.append(qm)
            x += self.slot_width + 20
示例#3
0
    def make_map(
        constants, level, monster_chances, key_ratio, ingredient_count={}, consumable_count={},
    ):
        retr = GameMap(constants.map_size, level)
        retr.chunks = TowerMapGenerator.chunkify(retr)
        TowerMapGenerator.cleanup_walls(retr)
        TowerMapGenerator.make_doors(retr, retr.chunks)
        TowerMapGenerator.place_monsters(retr, retr.chunks, monster_chances, level)
        TowerMapGenerator.place_stairs(retr, retr.chunks)

        TowerMapGenerator.place_decorations(retr, retr.chunks)
        TowerMapGenerator.place_lights(retr, retr.chunks)

        if config.conf.keys:
            TowerMapGenerator.place_keys(retr, retr.chunks, key_ratio)

        if config.conf.pickup:
            TowerMapGenerator.place_ingredients(retr, retr.chunks, ingredient_count)

        if config.conf.consumables:
            TowerMapGenerator.place_consumables(retr, retr.chunks, consumable_count)

        retr.set_tile_info(retr.tiles)

        px = retr.chunks[0].x + retr.chunks[0].width // 2
        py = retr.chunks[0].y + retr.chunks[0].height // 2
        retr.player_pos = Pos(px, py)
        retr.orig_player_pos = Pos(px, py)

        # print_map(retr)

        return retr
示例#4
0
文件: player.py 项目: dutt/formula
        def handle_targeting(left_click, right_click, turn_results):
            player_action = None
            if left_click:
                targetx, targety = left_click.cx, left_click.cy
                distance = (self.pos - Vec(targetx, targety)).length()
                if game_data.targeting_formula:

                    alternate = left_click.alternate
                    if alternate and config.conf.trapcast:
                        game_data.targeting_formula.trap = True
                        game_data.targeting_formula.set_texts()

                    if distance > game_data.targeting_formula.distance:
                        turn_results.append({
                            "target_out_of_range":
                            True,
                            "targeting_formula":
                            game_data.targeting_formula,
                        })
                    else:
                        player_action = ThrowVialAction(
                            self,
                            game_data.targeting_formula,
                            targetpos=(Pos(targetx, targety)),
                        )
                        # gfx_data.visuals.add_temporary(self.pos, Pos(targetx, targety), lifespan=distance * 0.1,
                        gfx_data.visuals.add_temporary(
                            self.pos,
                            Pos(targetx, targety),
                            lifespan=0.2,
                            asset=gfx_data.assets.throwing_bottle,
                        )
                        game_data.state = game_data.prev_state.pop()
                        game_data.targeting_formula_idx = None
                elif game_data.targeting_consumable:
                    if distance > game_data.targeting_consumable.distance:
                        turn_results.append({
                            "target_out_of_range":
                            True,
                            "targeting_consumable":
                            game_data.targeting_consumable,
                        })
                    else:
                        player_action = UseConsumableAction(
                            self,
                            game_data.targeting_consumable,
                            targetpos=(Pos(targetx, targety)),
                        )
                        gfx_data.visuals.add_temporary(
                            self.pos,
                            Pos(targetx, targety),
                            lifespan=0.2,
                            asset=gfx_data.assets.throwing_bottle,
                        )
                        game_data.state = game_data.prev_state.pop()
                        game_data.targeting_consumable = None

            elif right_click:
                turn_results.append({"targeting_cancelled": True})
            return player_action, turn_results
示例#5
0
 def get_children(node):
     check = []
     if node.x > 0:
         check.append(Pos(node.x - 1, node.y))
     if node.x < map.width - 1:
         check.append(Pos(node.x + 1, node.y))
     if node.y > 0:
         check.append(Pos(node.x, node.y - 1))
     if node.y < map.height - 1:
         check.append(Pos(node.x, node.y + 1))
     clean = []
     for c in check:
         if map.tiles[c.x][c.y].room != -1:
             continue
         if map.tiles[c.x][c.y].wall:
             continue
         queued = False
         for o in origins:
             q = queues[o]
             if c in q:
                 queued = True
                 break
         if not queued:
             # print("{}: {} is clean".format(o, c))
             clean.append(c)
     return clean
示例#6
0
    def setup_slots(self):
        self.input_slots = []
        for idx in range(self.slot_count):
            self.input_slots.append(
                CraftingSlot(Pos(100, 100 + idx * 40), index=idx))

        self.output_slot = CraftingSlot(Pos(400, 100 + ((40 * idx) // 2)))
示例#7
0
def main():
    w = 3
    h = 6
    r = 8
    w = 40
    h = 40
    m = Map(w, h, r, 3)
    # m = make_circular(m)
    rooms = random.randint(2, 4)
    min_dist = (w + h) / 4
    origins = []
    attempts = 0
    while len(origins) < rooms:
        attempts += 1
        if attempts > 100:
            break
        x = random.randint(0, w - 1)
        y = random.randint(0, h - 1)
        newpos = Pos(x, y)
        too_close = False
        for current in origins:
            if dist(newpos, current) < min_dist:
                too_close = True
        if too_close:
            continue
        origins.append(Pos(x, y))
    print("origins {}".format(origins))
    # m = make_improved_recursive_division(m)
    # r1 = Region(Pos(0, 0), m, 1)
    # r2 = Region(Pos(1, 3), m, 2)
    # run = True
    # while run:
    #    if not r1.expand() and not r2.expand():
    #        run = False
    # test(1, 2)
    # test(2, 1)
    # test(16.5, 4.2)
    # origins = [Pos(10, 6), Pos(10, 14)]
    # origins = [Pos(1, 1), Pos(1, 4)]
    # origins = [Pos(1, 0), Pos(1,3)]
    BFS(origins, m)
    # print("clear")
    # print_map(m)
    paths = []
    for p in get_pairs(origins):
        p1, p2 = p
        path = check_path(m, p1, p2)
        paths.append(path)

    make_walls(m)
    print("walled")
    print_map(m)
    print("doored")
    make_doors(m, origins, paths)
    # for x in range(m.width):
    #    m.tiles[x][1].wall = True
    # print(check_path(m, Pos(0, 0), Pos(2, 4)))
    print_map(m)

    room_centers = get_centers(m, 4)
示例#8
0
文件: player.py 项目: dutt/formula
        def handle_move(move, turn_results):
            player_action = None

            dx, dy = move
            destx = self.pos.x + dx
            desty = self.pos.y + dy

            if self.godmode:
                self.pos = Pos(destx, desty)
                gfx_data.camera.center_on(destx, desty)
                game_data.stats.move_player(Pos(destx, desty))
                player_action = MoveToPositionAction(self,
                                                     targetpos=Pos(
                                                         destx, desty))
            elif not game_data.map.is_blocked(destx, desty):
                target = get_blocking_entites_at_location(
                    game_data.map.entities, destx, desty)
                if target and target.fighter:
                    player_action = AttackAction(self, target=target)
                else:
                    gfx_data.camera.center_on(destx, desty)
                    game_data.stats.move_player(Pos(destx, desty))
                    player_action = MoveToPositionAction(self,
                                                         targetpos=Pos(
                                                             destx, desty))
            return player_action, turn_results
示例#9
0
文件: ui.py 项目: Spferical/frogue
 def redraw_level(self, memory, vision):
     self.clear()
     map_offset = self.get_map_pos(Pos(0, 0))
     for x in range(0, self.width):
         for y in range(0, self.height):
             window_pos = Pos(x, y)
             map_pos = window_pos + map_offset
             self.draw_tile(map_pos, memory, vision, window_pos=window_pos)
示例#10
0
    def test_mobs(self):
        level = Level(25, 25)
        level.up_stairs_pos = Pos(1, 1)
        world = World([level])

        mob = level.mobs[2, 2] = Mob(Pos(2, 2), 0, mobinfo['orc'])
        mob.move_to(Pos(3, 3))
        self.assertNotIn((2, 2), level.mobs)
        self.assertEquals(mob, level.mobs.get((3, 3)))

        self.assertEquals(level.mobs.get(world.levels[0].up_stairs_pos),
                          world.player)
示例#11
0
文件: fighter.py 项目: dutt/formula
    def show_on_hit(self, dmg):
        above = Pos(self.owner.pos.x, self.owner.pos.y - 1)

        text_surface = [Assets.get().font_title.render(str(dmg), True, colors.RED)]
        VisualEffectSystem.get().add_temporary(
            self.owner.pos, above, lifespan=0.3, asset=text_surface, color=colors.RED
        )
示例#12
0
 def draw_mouse_over_info(self, game_data, gfx_data, main):
     info_surface = pygame.Surface(game_data.constants.window_size.tuple(),
                                   pygame.SRCALPHA)
     pos = pygame.mouse.get_pos()
     px, py = pos
     map_screen_pos = self.global_screen_pos_to_map_screen_pos(
         px, py, game_data)
     tile_x, tile_y = self.map_screen_pos_to_tile(map_screen_pos[0],
                                                  map_screen_pos[1],
                                                  gfx_data)
     tile_pos = Pos(tile_x, tile_y)
     names = []
     for e in game_data.map.entities:
         if e.pos == tile_pos and tcod.map_is_in_fov(
                 game_data.fov_map, tile_pos.x, tile_pos.y):
             names.append(e.raw_name)
     if not names:
         return
     text = ", ".join(names)
     display_text(
         info_surface,
         text,
         gfx_data.assets.font_message,
         (px - self.pos.x + 20, py),
         text_color=colors.WHITE,
         bg_color=colors.BACKGROUND,
     )
     main.blit(info_surface, (0, 0))
示例#13
0
 def update_formula_markers(self, player, start_y):
     y = start_y
     self.formula_markers.clear()
     for idx, _ in enumerate(player.caster.formulas):
         marker = FormulaMarker(Pos(20, y), idx, player)
         self.formula_markers.append(marker)
         y += 40
示例#14
0
 def setup_consumables(self, count):
     self.consumable_markers = []
     y = 650
     for idx in range(count):
         pos = Pos(10, y)
         self.consumable_markers.append(ConsumableMarker(pos, idx))
         y += 40
示例#15
0
文件: fov.py 项目: Spferical/frogue
def cast_light(start_pos, start_y, start_slope, end_slope, radius, quad,
               level):
    if start_slope > end_slope:
        return
    prev_blocked = False
    for y in range(start_y, radius + 1):
        dy = y
        for dx in range(y + 1):
            trans = QUAD_TRANSFORMATIONS[quad]
            pos = start_pos + Pos(dx * trans[0] + dy * trans[1],
                                  dx * trans[2] + dy * trans[3])
            left_slope = (dx - .5) / (dy + .5)
            right_slope = (dx + .5) / (dy - .5)

            if start_slope > right_slope or end_slope < left_slope:
                continue

            yield pos

            if level[pos].opaque:
                if prev_blocked:
                    new_start = right_slope
                else:
                    # end of row of see-through tiles
                    prev_blocked = True
                    for pos in cast_light(start_pos, y + 1, start_slope,
                                          left_slope, radius, quad, level):
                        yield pos
                    new_start = right_slope
            elif prev_blocked:
                # end of series of walls
                prev_blocked = False
                start_slope = new_start
        if prev_blocked:
            break
示例#16
0
    def place_consumables(m, chunks, consumable_count):
        def has_consumables_left():
            for val in consumable_count.values():
                if val > 0:
                    return True

        def get_consumable():
            while True:
                itemtype = random.choice(list(consumable_count.keys()))
                if consumable_count[itemtype] > 0:
                    consumable_count[itemtype] -= 1
                    return itemtype()

        placed_consumables = []  # not two consumables in the same square
        while has_consumables_left():
            c = random.choice(chunks)
            occupied = True
            while occupied:
                x = random.randint(c.x + 1, c.x + c.width - 2)
                y = random.randint(c.y + 1, c.y + c.height - 2)
                occupied = False
                for ent in m.entities:
                    if ent.pos == Pos(x, y):
                        occupied = True
                if (x, y) in placed_consumables:
                    occupied = True
            placed_consumables.append((x, y))
            drawable_component = Drawable(Assets.get().consumable)
            consumable_component = get_consumable()
            name = consumable_component.name.capitalize()
            consumable = Entity(
                x, y, name, render_order=RenderOrder.ITEM, drawable=drawable_component, consumable=consumable_component,
            )
            m.entities.append(consumable)
示例#17
0
def make_improved_recursive_division(map):
    region = []
    for x in range(map.width):
        for y in range(map.height):
            region.append(Pos(x, y))

    def get_point_in_region(region):
        while True:
            x = random.randint(0, map.width)
            y = random.randint(0, map.height)
            if (x, y) in region:
                return Pos(x, y)

    def flood(region, points):
        def has_unfilled_cells(region):
            for p in region:
                if p.room == -1:
                    return True
            return False

        # clear point room assignments
        for p in region:
            p.room = -1
        while region.expand():
            pass

    p1 = get_point_in_region(region)
    p2 = get_point_in_region(region)
    map.tiles[p1.x][p1.y].symbol = "1"
    map.tiles[p2.x][p2.y].symbol = "2"
    return map
示例#18
0
 def __init__(self, constants, visible=False):
     super().__init__(
         constants.helper_window_pos,
         constants.helper_window_size,
         visible,
         click_mode=ClickMode.LEFT,
     )
     self.title = Label(Pos(200, 25), "Inventory")
     self.quickslots_label = Label(Pos(240, 390), "Quickslots")
     self.bottomlabel = Label(Pos(180, 560),
                              "Press Tab for help, Space to close")
     self.selected = 0
     self.items_per_row = 2
     self.num_quickslots = constants.num_quickslots
     self.slot_width = 140
     self.consumable_markers = []
     self.quickslot_markers = []
示例#19
0
 def parse_raw(self, raw_event):
     mouse_pos = Pos(pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1])
     return attrdict.AttrDict({
         "type": InputType.MOUSE.name,
         "x": mouse_pos.x,
         "y": mouse_pos.y,
         "button": raw_event.button
     })
示例#20
0
 def __init__(self, constants, visible=False, parent=None):
     super().__init__(
         Pos(constants.right_panel_size.width, 0),
         constants.game_window_size,
         visible,
         parent=parent,
     )
     self.show_all = False
     self.drawing_priority = 2
示例#21
0
    def draw_crafted_ingredient_list(self, surface, game_data, gfx_data):
        counts = game_data.ingredient_storage.remaining(game_data.formula_builder)

        x = 450
        y = 120

        self.ingredient_markers = []
        self.ingredient_markers.append(IngredientMarker(Pos(x, y), "Empty", Ingredient.EMPTY, parent=self))
        y += 40
        for ing, count in counts.items():
            if count <= 0:
                continue
            text = "{}, {} left".format(ing.name.capitalize(), count)
            self.ingredient_markers.append(IngredientMarker(Pos(x, y), text, ing, parent=self))
            y += 40

        for im in self.ingredient_markers:
            im.draw(surface, game_data, gfx_data)
示例#22
0
文件: ui.py 项目: Spferical/frogue
 def examine(self, memory):
     self.clear()
     if memory is None:
         return
     if memory.mob:
         drawables[memory.mob.info['name']].draw(self.console, Pos(1, 1))
         tcod.console_print(self.console, 3, 1,
                            get_short_mob_description(memory.mob))
         tcod.console_set_default_foreground(self.console, tcod.red)
         tcod.console_print(self.console, 3, 2, str(memory.mob.hp))
         tcod.console_set_default_foreground(self.console, tcod.white)
         tcod.console_print(self.console, 4 + len(str(memory.mob.hp)), 2,
                            "hp")
         if DEBUG:
             tcod.console_print(self.console, 3, 3, str(memory.mob.pos))
             tcod.console_print(self.console, 3, 4, str(memory.mob.target))
     else:
         drawables[memory.tile_name].draw(self.console, Pos(1, 1))
         tcod.console_print(self.console, 3, 1, memory.tile_name)
示例#23
0
def get_constants():
    window_title = "Formula"

    map_size = Size(40, 30)
    camera_size = Size(25, 20)
    game_window_size = Size((camera_size.width + 1) * CELL_WIDTH,
                            (camera_size.height + 1) * CELL_HEIGHT)
    window_size = Size(150 + game_window_size.width, 900)

    right_panel_size = Size(150, window_size.height)
    message_log_size = Size(
        window_size.width - right_panel_size.width,
        window_size.height - game_window_size.height,
    )
    message_log_text_size = Size(
        message_log_size.width - 2 * CELL_WIDTH,
        message_log_size.height - 2 * CELL_HEIGHT,
    )

    helper_window_size = Size(800, 600)
    helper_window_pos = Pos(100, 100)

    num_consumables = 5
    num_quickslots = 3

    room_max_size = 15
    room_min_size = 6
    max_rooms = 5

    fov_algorithm = 0
    fov_light_walls = True
    fov_radius = 10

    retr = AttrDict({
        "window_title": window_title,
        "window_size": window_size,
        "game_window_size": game_window_size,
        "camera_size": camera_size,
        "map_size": map_size,
        "right_panel_size": right_panel_size,
        "message_log_size": message_log_size,
        "message_log_text_size": message_log_text_size,
        "helper_window_size": helper_window_size,
        "helper_window_pos": helper_window_pos,
        "room_max_size": room_max_size,
        "room_min_size": room_min_size,
        "max_rooms": max_rooms,
        "fov_algorithm": fov_algorithm,
        "fov_light_walls": fov_light_walls,
        "fov_radius": fov_radius,
        "num_consumables": num_consumables,
        "num_quickslots": num_quickslots,
    })

    return retr
示例#24
0
 def __init__(self, constants, parent):
     super().__init__(
         Pos(constants.right_panel_size.width,
             constants.game_window_size.height),
         constants.message_log_size,
         visible=False,
         parent=parent,
     )
     self.offset = 0
     self.num_messages = 9
     self.drawing_priority = 2
示例#25
0
    def apply(self, game_data, gfx_data, target):
        explored_tiles = []
        for x in range(game_data.map.width):
            for y in range(game_data.map.height):
                if game_data.map.tiles[x][y].explored and not game_data.map.tiles[x][y].blocked:
                    explored_tiles.append((x, y))

        x, y = random.choice(explored_tiles)
        game_data.player.pos = Pos(x, y)
        results = [{"message": Message("Teleported to a previously explored location")}]
        return results
示例#26
0
 def get_neighbours(p, queue, visited):
     retr = []
     if p.x > 0:
         retr.append(Pos(p.x - 1, p.y))
     if p.x < m.width - 1:
         retr.append(Pos(p.x + 1, p.y))
     if p.y > 0:
         retr.append(Pos(p.x, p.y - 1))
     if p.y < m.height - 1:
         retr.append(Pos(p.x, p.y + 1))
     clean = []
     for r in retr:
         if m.tiles[r.x][r.y].wall:
             continue
         if r in queue:
             continue
         if r in visited:
             continue
         clean.append(r)
     return clean
示例#27
0
def get_tiles_within(map, distance, target):
    import math

    from util import Pos

    retr = []
    for x in range(math.ceil(target.x - distance), math.ceil(target.x + distance),):
        for y in range(math.ceil(target.y - distance), math.ceil(target.y + distance),):
            dist = math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2)
            if dist < distance:
                retr.append(Pos(x, y))
    return retr
示例#28
0
文件: ui.py 项目: Spferical/frogue
class MapWindow(Window):
    center_pos = Pos(0, 0)

    def __init__(self, *args, **kwargs):
        self.center_pos = Pos(0, 0)
        super().__init__(*args, **kwargs)

    def draw_cursor(self, map_pos):
        x, y = self.get_window_pos(map_pos)
        tcod.console_set_char_background(self.console, x, y,
                                         tcod.light_grey)

    def draw_cursor_at_center(self):
        self.draw_cursor(self.center_pos)

    def move(self, direction):
        self.center_pos += direction

    def center(self, pos):
        self.center_pos = pos

    def get_window_pos(self, map_pos):
        return map_pos - self.center_pos + \
            Pos(self.width, self.height) // 2

    def get_map_pos(self, world_pos):
        return world_pos + self.center_pos - \
            Pos(self.width, self.height) // 2

    def redraw_level(self, memory, vision):
        self.clear()
        map_offset = self.get_map_pos(Pos(0, 0))
        for x in range(0, self.width):
            for y in range(0, self.height):
                window_pos = Pos(x, y)
                map_pos = window_pos + map_offset
                self.draw_tile(map_pos, memory, vision, window_pos=window_pos)

    def draw_tile(self, map_pos, memory, vision, window_pos=None):
        memory = memory.get(map_pos, None)
        if memory:
            if window_pos is None:
                window_pos = self.get_window_pos(map_pos)

            tile_drawable = drawables[memory.tile_name]
            mob_drawable = drawables[memory.mob.info['name']] if memory.mob \
                else None

            visible = map_pos in vision

            tile_drawable.draw(self.console, window_pos, not visible)
            if mob_drawable:
                mob_drawable.draw(self.console, window_pos, not visible)
示例#29
0
    def draw_terrain(self, game_data, gfx_data, main):
        surface = pygame.Surface(game_data.constants.game_window_size.tuple(),
                                 pygame.SRCALPHA)
        surface.fill(colors.BACKGROUND)
        for x in range(gfx_data.camera.x1, gfx_data.camera.x2):
            for y in range(gfx_data.camera.y1, gfx_data.camera.y2):
                visible = tcod.map_is_in_fov(game_data.fov_map, x, y)
                asset = game_data.map.tiles[x][y].get_drawable(self.show_all
                                                               or visible)
                if visible:
                    game_data.map.tiles[x][y].explored = True

                if asset:
                    distance = (game_data.player.pos - Pos(x, y)).length()
                    if visible:
                        if distance < 3.5:  # 1 range
                            darken = 20
                        elif distance < 5.5:  # 2 range
                            darken = 40
                        elif distance < 7.5:  # 3 range
                            darken = 60
                        else:
                            darken = 80
                    else:
                        darken = 80

                    # if visible:
                    darken -= game_data.map.tiles[x][y].light

                    darken = max(0, min(255, darken))
                    drawable = Drawable(asset)
                    drawable.colorize((darken, darken, darken),
                                      pygame.BLEND_RGBA_SUB)
                    sx, sy = gfx_data.camera.map_to_screen(x, y)
                    surface.blit(drawable.asset,
                                 (sx * CELL_WIDTH, sy * CELL_HEIGHT))

                    if game_data.map.tiles[x][y].decor:
                        for decor_drawable in game_data.map.tiles[x][y].decor:
                            drawable = Drawable(decor_drawable.asset)
                            drawable.colorize((darken, darken, darken),
                                              pygame.BLEND_RGB_SUB)
                            surface.blit(drawable.asset,
                                         (sx * CELL_WIDTH, sy * CELL_HEIGHT))

                    if game_data.map.tiles[x][y].trap:
                        drawable = Drawable(gfx_data.assets.trap)
                        drawable.colorize((darken, darken, darken),
                                          pygame.BLEND_RGB_SUB)
                        surface.blit(drawable.asset,
                                     (sx * CELL_WIDTH, sy * CELL_HEIGHT))

        main.blit(surface, (0, 0))
示例#30
0
    def place_monsters(m, chunks, monster_chances, level):
        entities = []
        chance_any = monster_chances["any"]
        del monster_chances["any"]
        max_monsters_per_room = from_dungeon_level([[2, 1], [3, 4], [5, 6]], level + 1)
        for idx, c in enumerate(chunks):
            c.monsters = []
            rval = random.randint(0, 100)
            if rval > chance_any:
                continue
            if idx == 0:
                num_monsters = 1  # don't overwhelm in the first room
            else:
                num_monsters = random.randint(1, max_monsters_per_room)
            room_center = Pos(c.x + c.width // 2, c.y + c.height // 2)
            skip_room = False

            for _ in range(num_monsters):
                x = random.randint(c.x + 1, c.x + c.width - 1)
                y = random.randint(c.y + 1, c.y + c.height - 1)
                if idx == 0:
                    # first room, don't spawn right next to player
                    attempts = 0
                    while Pos(x, y).distance_to(room_center) < 4:
                        x = random.randint(c.x + 1, c.x + c.width - 2)
                        y = random.randint(c.y + 1, c.y + c.height - 2)
                        attempts += 1
                        if attempts > 100:
                            skip_room = True
                if skip_room:
                    continue

                already_there = [entity for entity in entities if entity.pos.x == x and entity.pos.y == y]
                if not any(already_there) and not m.tiles[x][y].blocked:
                    monster_choice = random_choice_from_dict(monster_chances)
                    monster_data = get_monster(x, y, m, c, monster_choice, entities)
                    entities.extend(monster_data)
                    c.monsters.extend(monster_data)

        m.entities = entities