Source code for scripts.engine.world_objects.game_map

from __future__ import annotations

import json
import logging
import random
from typing import Any, Dict, List, Optional

import numpy as np
from pygame.constants import BLEND_RGBA_MULT

from scripts.engine.core import dungen
from scripts.engine.internal import library
from scripts.engine.internal.constant import Height, MAP_BORDER_SIZE, TILE_SIZE, TileCategory
from scripts.engine.internal.definition import ActorData
from scripts.engine.world_objects import lighting
from scripts.engine.world_objects.lighting import LightBox
from scripts.engine.world_objects.tile import Tile

__all__ = ["GameMap"]


[docs]class GameMap: """ Holds tiles for a map. Handles generation of the map and placement of the entities. Fills map with floors on init. """
[docs] def __init__(self, map_name: str, seed: Any): self.is_dirty = True self.name: str = map_name self.seed = seed self.rng = random.Random() self.rng.seed(seed) _map_data = library.MAPS[map_name] self.width = _map_data.width + (MAP_BORDER_SIZE * 2) self.height = _map_data.height + (MAP_BORDER_SIZE * 2) map_size = (self.width, self.height) self.light_map: np.ndarray = np.zeros(map_size, dtype=bool, order="F") # what positions are lit self.tile_map: List[List[Tile]] = [] # array of all Tiles window = library.VIDEO_CONFIG.base_window self.light_box: LightBox = lighting.LightBox((window.width, window.height), BLEND_RGBA_MULT) # lighting that # needs processing self._block_movement_map: np.ndarray = np.zeros(map_size, dtype=bool, order="F") # array for move blocked self._block_sight_map: np.ndarray = np.zeros(map_size, dtype=bool, order="F") # array for sight blocked self._air_tile_positions: List[List[int]] = [] # what positions dont block sight self.generation_info: str = "" self._init_tile_map()
################ INIT ################################################## def _init_tile_map(self): """ Only called during init to populate tile map with walls """ from scripts.engine.internal import library _map_data = library.MAPS[self.name] # get details for a wall tile from scripts.engine.core import utility wall_sprite_path = _map_data.sprite_paths[TileCategory.WALL] wall_sprite = utility.get_image(wall_sprite_path) height = Height.MIN blocks_movement = True # populate tile_map with wall tiles for x in range(self.width): self.tile_map.append([]) # create new list for every col for y in range(self.height): self.tile_map[x].append(Tile(x, y, wall_sprite, wall_sprite_path, blocks_movement, height)) ################## MAP GEN #############################################
[docs] def generate_new_map(self, player_data: Optional[ActorData] = None): """ Generate the map for the current game map. Creates tiles. Saves the values directly to the GameMap. """ self.tile_map, self.generation_info = dungen.generate(self.name, self.rng, player_data) self.is_dirty = True
[docs] def dump(self, path: str): """ Dumps the dungeon tree into a file """ with open(path, "w") as fp: fp.write(json.dumps(self.generation_info, indent=4))
######################## UTIL ##########################################
[docs] def get_open_space(self): """ Returns a random open space from the tile map. """ return random.choice(self.air_tile_positions)
################### DATA MANAGEMENT #################################### def _refresh_internals(self): """ Refresh the data in the self._block_movement_map and self._block_sight_map """ block_movement_map = [[not tile.blocks_movement for tile in columns] for columns in self.tile_map] self._block_movement_map = np.asarray(block_movement_map, dtype=np.int8) # assumes tiles only have min or max height block_sight_map = [[not tile.height == Height.MAX for tile in columns] for columns in self.tile_map] self._block_sight_map = np.asarray(block_sight_map, dtype=np.int8) # get all the non-blocking, or "air", tiles. self._air_tile_positions = np.argwhere(self._block_sight_map == 1).tolist() # self.block_sight_map == 1 does the if not block_sight_map part of the loop. # np.argwhere gets the indexes of all nonzero elements. # tolist converts this back into a nested list. # update the walls in the light box lighting.generate_walls(self.light_box, self._air_tile_positions, TILE_SIZE) ################### SERIALISATION #####################################
[docs] def serialise(self) -> Dict[str, Any]: """ Serialise the game map to dict. """ tiles: List[List[Dict]] = [] # build list of lists for x in range(self.width): # give each new row an empty list tiles.append([]) for y in range(self.height): # add to the column tiles[x].append(self.tile_map[x][y].serialise()) _dict = {"width": self.width, "height": self.height, "tiles": tiles, "seed": self.seed, "map_name": self.name} return _dict
[docs] @classmethod def deserialise(cls, serialised: Dict[str, Any]): """ Loads the details from the serialised data back into the GameMap. """ try: seed = serialised["seed"] map_name = serialised["map_name"] width = serialised["width"] height = serialised["height"] tiles: List[List[Tile]] = [] for x in range(width): tiles.append([]) for y in range(height): tiles[x].append(Tile.deserialise(serialised["tiles"][x][y])) game_map = GameMap(map_name, seed) game_map.tile_map = tiles return game_map except KeyError as e: logging.warning(f"GameMap.Deserialise: Incorrect key ({e.args[0]}) given. Data not loaded correctly.") raise KeyError # throw exception to hit outer error handler and exit
################### PROPERTIES ######################################## @property def block_movement_map(self) -> np.ndarray: """ Return a copy of an array containing ints, 0 for blocked and 1 for open """ if self.is_dirty: self._refresh_internals() self.is_dirty = False return self._block_movement_map.copy("F") @property def block_sight_map(self) -> np.ndarray: """ Return a copy of an array containing ints, 0 for blocked and 1 for open """ if self.is_dirty: self._refresh_internals() self.is_dirty = False return self._block_sight_map.copy("F") @property def air_tile_positions(self) -> List[List[int]]: """ Return a copy of an array containing ints, 0 for blocked and 1 for open """ if self.is_dirty: self._refresh_internals() self.is_dirty = False return self._air_tile_positions.copy()