NotQuiteParadise2

Source code for nqp.core.assets

from __future__ import annotations

import json
import logging
import os
import time
from typing import TYPE_CHECKING

import pygame

from nqp.core.constants import ASSET_PATH, DEFAULT_IMAGE_SIZE, FontEffects, FontType
from nqp.core.utility import clamp, clip
from nqp.ui_elements.generic.fancy_font import FancyFont
from nqp.ui_elements.generic.font import Font

if TYPE_CHECKING:
    from typing import Dict, List, Optional, Tuple

    from nqp.core.game import Game

__all__ = ["Assets"]


######### TO DO LIST  ##########

# TODO - Delete module when all classes updates to use new Visuals


[docs]class Assets: """ DO NOT USE. Use visuals.py instead """
[docs] def __init__(self, game: Game): # start timer start_time = time.time() self._game: Game = game self._fonts = { FontType.NEGATIVE: (str(ASSET_PATH / "fonts/small_font.png"), (255, 0, 0)), FontType.DISABLED: (str(ASSET_PATH / "fonts/small_font.png"), (128, 128, 128)), FontType.DEFAULT: (str(ASSET_PATH / "fonts/small_font.png"), (255, 255, 255)), FontType.POSITIVE: (str(ASSET_PATH / "fonts/small_font.png"), (0, 255, 0)), FontType.INSTRUCTION: (str(ASSET_PATH / "fonts/small_font.png"), (240, 205, 48)), FontType.NOTIFICATION: (str(ASSET_PATH / "fonts/large_font.png"), (117, 50, 168)), } # used to hold images so only one copy per dimension ever exists. self._images: Dict[str, Dict[str, pygame.Surface]] = self._load_images() self.unit_animations = { unit: { action: self.load_image_dir(ASSET_PATH / "units/" / unit / action) for action in os.listdir(ASSET_PATH / "units/" / unit) } for unit in os.listdir(ASSET_PATH / "units/") } # self.trap_animations = {} # # self.commander_animations = { # commander: { # action: self.load_image_dir(ASSET_PATH / "commanders/" / commander / action) # for action in os.listdir(ASSET_PATH / "commanders/" / commander) # } # for commander in os.listdir(ASSET_PATH / "commanders/") # } # # self.boss_animations = { # commander: { # action: self.load_image_dir(ASSET_PATH / "bosses/" / commander / action) # for action in os.listdir(ASSET_PATH / "bosses/" / commander) # } # for commander in os.listdir(ASSET_PATH / "bosses/") # } self.tilesets = { tileset.split(".")[0]: self.load_tileset(ASSET_PATH / "tiles" / tileset) for tileset in os.listdir(ASSET_PATH / "tiles") } # self.ui = { # ui_image.split(".")[0]: pygame.image.load(str(ASSET_PATH / "ui" / ui_image)).convert_alpha() # for ui_image in os.listdir(ASSET_PATH / "ui") # } # # self.actions = { # action_image.split(".")[0]: pygame.image.load(str(ASSET_PATH / "actions" / action_image)).convert_alpha() # for action_image in os.listdir(ASSET_PATH / "actions") # } # # self.projectiles = { # projectiles_image.split(".")[0]: pygame.image.load( # str(ASSET_PATH / "projectiles" / projectiles_image) # ).convert_alpha() # for projectiles_image in os.listdir(ASSET_PATH / "projectiles") # if projectiles_image.split(".")[-1] == "png" # } self.maps = dict() for file in os.listdir("data/maps"): if file.endswith("json"): name = file.split(".")[0] path = "data/maps/" + file with open(path) as fp: data = json.load(fp) self.maps[name] = data # record duration end_time = time.time() logging.debug(f"Assets: initialised in {format(end_time - start_time, '.2f')}s.")
[docs] def get_image( self, folder_name: str, image_name: str, desired_dimensions: pygame.Vector2 = (DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE), copy: bool = False, ) -> pygame.Surface: """ Get the specified image and resize if dimensions provided. Dimensions are in (width, height) format. A transparent surface can be returned by folder_name = "debug" and image_name = "blank". """ desired_width, desired_height = desired_dimensions # ensure numbers arent negative if desired_width <= 0 or desired_height <= 0: logging.warning( f"Get_image: Tried to use dimensions of {desired_dimensions}, which are negative. Default size " f"used instead." ) desired_dimensions = (DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE) internal_name = f"{image_name}@{desired_width}x{desired_height}" # check if exists already try: image = self._images[folder_name][internal_name] except KeyError: # try and get the image specified try: image = pygame.image.load(str(ASSET_PATH / folder_name / image_name) + ".png").convert_alpha() # resize if needed - should only need to resize if we havent got it from storage if image.get_width() != desired_width or image.get_height() != desired_height: image = pygame.transform.smoothscale(image, desired_dimensions) # add new image to storage self._images[folder_name][internal_name] = image except FileNotFoundError: # didnt find image requested so use not found image not_found_name = f"not_found@{desired_width}x{desired_height}" if not_found_name in self._images["debug"]: image = self._images["debug"][not_found_name] else: image = pygame.image.load(str(ASSET_PATH / "debug/image_not_found.png")).convert_alpha() # add new image to storage self._images["debug"][internal_name] = image logging.warning( f"Get_image: Tried to use {folder_name}/{image_name} but it wasn't found. " f"Used the not_found image instead." ) # return a copy if requested if copy: return image.copy() else: return image
[docs] def create_font(self, font_type: FontType, text: str, pos: pygame.Vector2 = (0, 0), line_width: int = 0) -> Font: """ Create a font instance. """ line_width = clamp(line_width, 0, self._game.window.width) path, colour = self._fonts[font_type] font = Font(path, colour, text, line_width, pos) return font
[docs] def create_fancy_font( self, text: str, pos: pygame.Vector2 = pygame.Vector2(0, 0), line_width: int = 0, font_effects: Optional[List[FontEffects]] = None, ) -> FancyFont: """ Create a FancyFont instance. If line_width isnt given then will default to full screen. """ line_width = clamp(line_width, 0, self._game.window.width) # handle mutable default if font_effects is None: font_effects = [] font = FancyFont(text, pos, line_width, font_effects) return font
[docs] def load_tileset(self, path): """ Loads a tileset from a spritesheet. """ tileset_data = [] spritesheet = pygame.image.load(str(path)).convert_alpha() for y in range(spritesheet.get_height() // DEFAULT_IMAGE_SIZE): tileset_data.append([]) for x in range(spritesheet.get_width() // DEFAULT_IMAGE_SIZE): tileset_data[-1].append( clip( spritesheet, x * DEFAULT_IMAGE_SIZE, y * DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE, ) ) return tileset_data
[docs] def load_image_dir(self, path, format="list"): """ Load images in a directory with specified format. """ images = None if format == "list": images = [] if format == "dict": images = {} for img_path in os.listdir(path): img = pygame.image.load(str(path) + "/" + img_path).convert_alpha() if format == "list": images.append(img) if format == "dict": images[img_path.split(".")[0]] = img return images
@staticmethod def _load_images() -> Dict[str, Dict[str, pygame.Surface]]: """ Load all images by folder. N.B. if image isnt loading ensure the containing folder is listed in the method. """ images = {} # specify folders in assets that need to be loaded folders = ["rooms"] for folder in folders: path = ASSET_PATH / folder images[folder] = {} for image_name in os.listdir(path): if image_name.split(".")[-1] == "png": # avoid duplicates if image_name in images[folder].keys(): logging.warning(f"{image_name} already loaded, non-unique file name.") image = pygame.image.load(str(path / image_name)).convert_alpha() width = image.get_width() height = image.get_height() images[folder][f"{image_name.split('.')[0]}@{width}x{height}"] = image # split to remove extension # add not found image to debug images["debug"] = {} image = pygame.image.load(str(ASSET_PATH / "debug/image_not_found.png")).convert_alpha() width = image.get_width() height = image.get_height() images["debug"][f"not_found@{width}x{height}"] = image # add transparent surface to debug image = pygame.Surface((DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE)) image.set_alpha(0) images["debug"][f"blank@{DEFAULT_IMAGE_SIZE}x{DEFAULT_IMAGE_SIZE}"] = image logging.debug(f"Assets: All images loaded.") return images