Source code for scripts.nqp.command

from __future__ import annotations

import logging
import os
from threading import Timer
from typing import List

import pygame
import snecs
from snecs import Component
from snecs.world import default_world

import scripts.engine.core.matter
from scripts.engine.core import hourglass, state, system, utility, world
from scripts.engine.core.component import Knowledge  # for demonstration purposes
from scripts.engine.core.component import (
    Aesthetic,
    Exists,
    Identity,
    IsPlayer,
    LightSource,
    MapCondition,
    Shrine,
    Position,
    WinCondition,
)
from scripts.engine.core.ui import ui
from scripts.engine.internal import library
from scripts.engine.internal.action import register_action
from scripts.engine.internal.constant import (
    ASSET_PATH,
    DEBUG_START,
    GameState,
    GAP_SIZE,
    Height,
    MAX_SKILLS,
    RenderLayer,
    SAVE_PATH,
    SKILL_BUTTON_SIZE,
    UIElement,
)
from scripts.engine.internal.data import store
from scripts.engine.internal.definition import ActorData, TraitSpritePathsData
from scripts.engine.internal.interaction import InteractionEventSubscriber
from scripts.engine.world_objects.game_map import GameMap
from scripts.nqp.actions.affliction import BoggedDown, Flaming
from scripts.nqp.actions.behaviour import FollowPlayer, SearchAndAttack, SkipTurn
from scripts.nqp.actions.blessing import MoveFast, NoMove, AttackMove, Aftershock, SaltTheWound, KeepAnEvenKeel
from scripts.nqp.actions.skill import BasicAttack, Lightning, Lunge, Move, Splash, TarAndFeather
from scripts.nqp.processors.game import GameEventSubscriber
from scripts.nqp.ui_elements.camera import camera
from scripts.nqp.ui_elements.character_selector import CharacterSelector
from scripts.nqp.ui_elements.skill_bar import SkillBar
from scripts.nqp.ui_elements.title_screen import TitleScreen

__all__ = [
    "initialise_game",
    "goto_character_select",
    "load_game",
    "exit_game",
    "win_game",
    "register_actions",
    "get_element_rect",
]


[docs]def initialise_game(): """ Init the game`s required info """ register_actions() init_subscribers() if DEBUG_START: _start_debug_game() else: state.set_new(GameState.MENU) goto_to_title()
[docs]def _start_debug_game(): """ Create a new game and show the gamemap """ # create clean snecs.world empty_world = snecs.World() world.move_world(empty_world) # init and save map game_map = GameMap("debug", 10) store.current_game_map = game_map # populate the map player_data = ActorData( key="player", possible_names=["player"], description="Player desc", position_offsets=[(0, 0)], trait_names=["shoom", "soft_tops", "dandy"], height=Height.MIDDLING, ) game_map.generate_new_map(player_data) logging.info(game_map.generation_info) # init the player player = scripts.engine.core.matter.get_player() # tell places about the player hourglass.set_turn_holder(player) # create actor near to player player_pos = scripts.engine.core.matter.get_entitys_component(player, Position) actor_data = library.ACTORS["crocturion"] scripts.engine.core.matter.create_actor(actor_data, (player_pos.x, player_pos.y - 2)) # create god god_data = library.GODS["the_small_gods"] scripts.engine.core.matter.create_god(god_data) # update draw position for all entities for entity, (aesthetic, position) in scripts.engine.core.matter.get_components([Aesthetic, Position]): assert isinstance(aesthetic, Aesthetic) assert isinstance(position, Position) aesthetic.draw_x, aesthetic.draw_y = (position.x, position.y) aesthetic.target_draw_x = aesthetic.draw_x aesthetic.target_draw_y = aesthetic.draw_y # entities load with a blank fov, update them now system.process_light_map() system.process_fov() system.process_tile_visibility() # point the camera at the player, now that FOV is updated camera.set_target((player_pos.x, player_pos.y), True) # create terrain next to the player scripts.engine.core.matter.create_terrain(library.TERRAIN["bog"], (player_pos.x + 1, player_pos.y)) # loading finished, give player control state.set_new(GameState.GAME_MAP) # prompt turn actions hourglass.end_turn(player, 0)
[docs]def start_game(player_data: ActorData): """ Create a new game and show the gamemap """ # create clean snecs.world empty_world = snecs.World() world.move_world(empty_world) # init and save map game_map = GameMap("cave", 10) store.current_game_map = game_map # populate the map game_map.generate_new_map(player_data) # init the player player = scripts.engine.core.matter.get_player() # allocate player skills scripts.engine.core.matter.learn_skill(player, "Lightning") # blessing test cases knowledge = scripts.engine.core.matter.get_entitys_component(player, Knowledge) #knowledge.add_blessing(Move, NoMove(player)) #knowledge.add_blessing(Move, Aftershock(player)) #knowledge.remove_blessing(BasicAttack, AttackMove) #knowledge.add_blessing(BasicAttack, SaltTheWound(player)) #knowledge.add_blessing(BasicAttack, KeepAnEvenKeel(player)) # create blessing shrine player_pos = scripts.engine.core.matter.get_entitys_component(player, Position) shrine_x = player_pos.x + 3 shrine_y = player_pos.y components: List[Component] = [] components.append(Position((shrine_x, shrine_y))) # lets hope this doesnt spawn in a wall components.append(Shrine()) traits_paths = [TraitSpritePathsData(idle=str(ASSET_PATH / "world/shrine.png"))] sprites = utility.build_sprites_from_paths(traits_paths) components.append(Aesthetic(sprites, traits_paths, RenderLayer.ACTOR, (shrine_x, shrine_y))) scripts.engine.core.matter.create_entity(components) # create win condition and place next to player player_pos = scripts.engine.core.matter.get_entitys_component(player, Position) win_x = player_pos.x + 1 win_y = player_pos.y components = [] components.append(Position((win_x, win_y))) # lets hope this doesnt spawn in a wall components.append(WinCondition()) traits_paths = [TraitSpritePathsData(idle=str(ASSET_PATH / "world/win_flag.png"))] sprites = utility.build_sprites_from_paths(traits_paths) components.append(Aesthetic(sprites, traits_paths, RenderLayer.ACTOR, (win_x, win_y))) scripts.engine.core.matter.create_entity(components) # create map change condition and place next to player map_x = player_pos.x + 2 map_y = player_pos.y components = [] components.append(Position((map_x, map_y))) # lets hope this doesnt spawn in a wall components.append(MapCondition()) traits_paths = [TraitSpritePathsData(idle=str(ASSET_PATH / "world/map_flag.png"))] sprites = utility.build_sprites_from_paths(traits_paths) components.append(Aesthetic(sprites, traits_paths, RenderLayer.ACTOR, (map_x, map_y))) scripts.engine.core.matter.create_entity(components) # tell places about the player hourglass.set_turn_holder(player) # create a god god_data = library.GODS["the_small_gods"] scripts.engine.core.matter.create_god(god_data) # show the in game screens # message_log = MessageLog(get_element_rect(UIElement.MESSAGE_LOG), ui.get_gui_manager()) # ui.register_element(UIElement.MESSAGE_LOG, message_log) # ui.set_element_visibility(UIElement.MESSAGE_LOG, True) skill_bar = SkillBar(get_element_rect(UIElement.SKILL_BAR), ui.get_gui_manager()) ui.register_element(UIElement.SKILL_BAR, skill_bar) ui.set_element_visibility(UIElement.SKILL_BAR, True) # welcome message ui.create_screen_message("Welcome to Not Quite Paradise") # FIXME - entities load before camera so they cant get their screen position. # If ui loads before entities then it fails due to player not existing. Below is a hacky fix. for entity, (aesthetic, position) in scripts.engine.core.matter.get_components([Aesthetic, Position]): assert isinstance(aesthetic, Aesthetic) assert isinstance(position, Position) aesthetic.draw_x, aesthetic.draw_y = (position.x, position.y) aesthetic.target_draw_x = aesthetic.draw_x aesthetic.target_draw_y = aesthetic.draw_y # entities load with a blank fov, update them now system.process_light_map() system.process_fov() system.process_tile_visibility() # point the camera at the player, now that FOV is updated pos = scripts.engine.core.matter.get_entitys_component(player, Position) camera.set_target((pos.x, pos.y), True) # loading finished, give player control state.set_new(GameState.GAME_MAP) # prompt turn actions hourglass.end_turn(player, 0)
[docs]def load_game(): """ Load existing game state """ full_save_path = str(SAVE_PATH) for save_name in os.listdir(full_save_path): save = save_name.replace(".json", "") state.load_game(save) break # move camera to player player_pos = scripts.engine.core.matter.get_entitys_component(scripts.engine.core.matter.get_player(), Position) camera.set_target((player_pos.x, player_pos.y), True) # show UI skill_bar = SkillBar(get_element_rect(UIElement.SKILL_BAR), ui.get_gui_manager()) ui.register_element(UIElement.SKILL_BAR, skill_bar) ui.set_element_visibility(UIElement.SKILL_BAR, True) # welcome message ui.create_screen_message("Welcome back to Not Quite Paradise") # loading finished, give player control state.set_new(GameState.GAME_MAP)
[docs]def exit_game(): """ Exit the game """ state.set_new(GameState.EXITING)
[docs]def win_game(): """ Trigger the win game actions """ state.set_new(GameState.MENU) ui.create_screen_message("You wonned. Huzzah.") # quit to main menu after a few seconds timer = Timer(2.0, goto_to_title) timer.start()
[docs]def lose_game(): state.set_new(GameState.MENU) ui.create_screen_message("You hath died. Huzzah?") # quit to main menu after a few seconds timer = Timer(2.0, goto_to_title) timer.start()
[docs]def change_map(): # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH # extremely scuffed and for testing. copied a bunch of stuff from start_game() # entities must be forcefully deleted or components will delete lights from the new lightbox once the delete in main runs for entity, (_,) in list(scripts.engine.core.matter.get_components([Exists])): if not scripts.engine.core.matter.entity_has_component( entity, IsPlayer ) and scripts.engine.core.matter.entity_has_component(entity, Identity): snecs.delete_entity_immediately(entity, default_world) # init and save map game_map = GameMap("cave", 10) store.current_game_map = game_map # regenerate lights for transferred entities for entity, (light_source, position) in list(scripts.engine.core.matter.get_components([LightSource, Position])): # set up light radius = 2 # TODO - pull radius and colour from external data colour = (255, 255, 255) alpha = 200 light_source.light_id = scripts.engine.core.matter.create_light([position.x, position.y], radius, colour, alpha) # populate the map game_map.generate_new_map(None) # get the player player = scripts.engine.core.matter.get_player() # transfer player to new spawn player_position = scripts.engine.core.matter.get_entitys_component(player, Position) new_pos = tuple(game_map.get_open_space()) player_position.set(*new_pos) # visually update player position aesthetic = scripts.engine.core.matter.get_entitys_component(player, Aesthetic) if aesthetic: aesthetic.target_draw_x, aesthetic.target_draw_y = new_pos aesthetic.current_sprite = aesthetic.sprites.move # entities load with a blank fov, update them now system.process_light_map() system.process_fov() system.process_tile_visibility() # point the camera at the player, now that FOV is updated pos = scripts.engine.core.matter.get_entitys_component(player, Position) camera.set_target((pos.x, pos.y), True)
############### NAVIGATION #####################
[docs]def goto_character_select(): """ Create a new game """ character_select = CharacterSelector(get_element_rect(UIElement.CHARACTER_SELECTOR), ui.get_gui_manager()) ui.register_element(UIElement.CHARACTER_SELECTOR, character_select) ui.set_element_visibility(UIElement.CHARACTER_SELECTOR, True)
[docs]def goto_to_title(): """ Quit out of the game back to the title screen """ state.set_new(GameState.MENU) # remove any existing ui ui.kill_all_elements() # clear existing resource store.current_game_map = None # TODO - add better clearing method. # show the title screen title_screen = TitleScreen(get_element_rect(UIElement.TITLE_SCREEN), ui.get_gui_manager()) ui.register_element(UIElement.TITLE_SCREEN, title_screen) ui.set_element_visibility(UIElement.TITLE_SCREEN, True)
################## INIT ##########################
[docs]def register_actions(): """ Register all Actions with the engine """ # afflictions register_action(BoggedDown) register_action(Flaming) # behaviour register_action(SkipTurn) register_action(FollowPlayer) register_action(SearchAndAttack) # skills register_action(Move) register_action(BasicAttack) register_action(Lunge) register_action(TarAndFeather) register_action(Splash) register_action(Lightning) # blessings register_action(MoveFast) register_action(NoMove) register_action(AttackMove) register_action(Aftershock) register_action(SaltTheWound) register_action(KeepAnEvenKeel)
[docs]def init_subscribers(): """ Initialise event subscribers. N.B. When init'd they are held in reference by the event hub and do not need to be referred to directly. """ game_subscriber = GameEventSubscriber() interaction_subscriber = InteractionEventSubscriber()
##################### UI ######################
[docs]def get_element_rect(element_type: UIElement) -> pygame.Rect: """ Get the predefined rect for the specified element. """ desired_width = ui.desired_width desired_height = ui.desired_height # Message Log message_width = int(desired_width * 0.31) message_height = int(desired_height * 0.28) message_x = 0 message_y = -message_height # Skill Bar skill_width = (MAX_SKILLS * (SKILL_BUTTON_SIZE + GAP_SIZE)) + (GAP_SIZE * 2) # gap * 2 for borders skill_height = SKILL_BUTTON_SIZE + (GAP_SIZE * 2) skill_x = (desired_width // 2) - (skill_width // 2) skill_y = -SKILL_BUTTON_SIZE # Title Screen title_screen_width = desired_width title_screen_height = desired_height title_screen_x = 0 title_screen_y = 0 # Dungeon dev view dungen_viewer_width = desired_width dungen_viewer_height = desired_height dungen_viewer_x = 0 dungen_viewer_y = 0 # Tile Info tile_info_width = int(desired_width * 0.19) tile_info_height = int(desired_height * 0.22) tile_info_x = -tile_info_width tile_info_y = -tile_info_height # Npc info npc_info_width = desired_width / 2 npc_info_height = desired_height - (desired_height / 4) npc_info_x = 5 npc_info_y = 10 # character selector char_selector_width = desired_width char_selector_height = desired_height char_selector_x = 0 char_selector_y = 0 # blessings menu blessing_menu_width = desired_width / 2 blessing_menu_height = desired_width * 0.4 blessing_menu_x = 5 blessing_menu_y = 10 layout = { UIElement.MESSAGE_LOG: pygame.Rect((message_x, message_y), (message_width, message_height)), UIElement.TILE_INFO: pygame.Rect((tile_info_x, tile_info_y), (tile_info_width, tile_info_height)), UIElement.SKILL_BAR: pygame.Rect((skill_x, skill_y), (skill_width, skill_height)), UIElement.DUNGEN_VIEWER: pygame.Rect( (dungen_viewer_x, dungen_viewer_y), (dungen_viewer_width, dungen_viewer_height) ), UIElement.ACTOR_INFO: pygame.Rect((npc_info_x, npc_info_y), (npc_info_width, npc_info_height)), UIElement.BLESSING_MENU: pygame.Rect((blessing_menu_x, blessing_menu_y), (blessing_menu_width, blessing_menu_height)), UIElement.TITLE_SCREEN: pygame.Rect( (title_screen_x, title_screen_y), (title_screen_width, title_screen_height) ), UIElement.CHARACTER_SELECTOR: pygame.Rect( (char_selector_x, char_selector_y), (char_selector_width, char_selector_height) ), } return layout[element_type]