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]