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