pyrogue/game/screens/game_screens.py

142 lines
6 KiB
Python

"""All states the are used in-game"""
from __future__ import annotations
from random import Random
import attrs
import tcod.console
import tcod.constants
import tcod.ecs
import tcod.event
from tcod.event import KeySym
from tcod.map import Map
import g
from game.components import HP, Action, Gold, Graphic, Position
from game.constants import DIRECTION_KEYS, ACTION_KEYS, FLOOR_CHAR
from game.screens import Push, Screen, ScreenResult
from game.tags import IsWalllike, IsItem, IsPlayer, IsActor
from game.constants import WALL_CHAR, VERTICAL_WALL_CHAR
from game.screens import menu_screens
from game.world_tools import world_pos_to_map_pos, map_pos_to_world_pos
def _recalc_fov(pos):
cam_map = world_pos_to_map_pos(pos)
g.world[None].components[Map].compute_fov(cam_map.x, cam_map.y, 100)
def _handle_movement(player, map, dir):
pos = player.components[Position]
new_pos = pos + dir
map_pos = world_pos_to_map_pos(new_pos)
if action_entities := g.world.Q.all_of(components=[Action], tags=[new_pos]):
for entity in action_entities:
entity.components[Action](entity)
map_pos = world_pos_to_map_pos(pos)
_recalc_fov(map_pos)
return None
if not map.walkable[map_pos.y, map_pos.x]:
return None
player.components[Position] = new_pos
_recalc_fov(new_pos)
# Auto pickup items
for gold in g.world.Q.all_of(components=[Gold], tags=[new_pos, IsItem]):
player.components[Gold] += gold.components[Gold]
text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g"
g.world[None].components[("Text", str)] = text
gold.clear()
for hp in g.world.Q.all_of(components=[HP, Position], tags=[new_pos, IsItem]):
h_p = hp.components[HP]
player.components[HP] += hp.components[HP]
text = f"You stepped into a trap and took {-h_p} HP damage"
if h_p > 0:
text = f"You found a Poition and gained {h_p} HP"
text += f", you now have {player.components[HP]} HP"
g.world[None].components[("HP_Text", str)] = text
hp.clear()
def _handle_action(player):
for entity in g.world.Q.all_of(components=[Action, Position]):
player_pos = player.components[Position]
if (player_pos - entity.components[Position]).length() < 2:
entity.components[Action](entity)
_recalc_fov(player_pos)
def _draw_entity(entity: tcod.ecs.Entity, camera_pos, camera_radius_x, camera_radius_y):
pos = entity.components[Position]
screen_pos = pos - camera_pos
if not (-camera_radius_x <= screen_pos.x < camera_radius_x\
and -camera_radius_y <= screen_pos.y < camera_radius_y):
return
map_pos = world_pos_to_map_pos(pos)
if g.world[None].components[Map].fov[map_pos.y, map_pos.x]:
graphic = entity.components[Graphic]
r,gg,b,_ = graphic.fg
fg = (r,gg,b,255)
g.renderer.foreground.rgba[["ch", "fg"]][screen_pos.y + camera_radius_y, screen_pos.x + camera_radius_x] = graphic.ch, fg
@attrs.define()
class MainScreen(Screen):
"""Primary in-game state."""
def on_event(self, event: tcod.event.Event) -> ScreenResult:
"""Handle events for the in-game state."""
(player,) = g.world.Q.all_of(tags=[IsPlayer])
map: Map = g.world[None].components[Map]
match event:
case tcod.event.KeyDown(sym=sym) if sym in ACTION_KEYS:
_handle_action(player)
case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
dir = DIRECTION_KEYS[sym]
return _handle_movement(player, map, dir)
case tcod.event.KeyDown(sym=KeySym.ESCAPE):
return Push(menu_screens.MainMenu())
case _:
return None
def on_draw(self) -> None:
"""Draw the standard screen."""
console = g.renderer.background
centers = [a.components[Position] for a in g.world.Q.all_of(tags=[IsPlayer])]
camera_pos = sum(centers, start=Position(0,0))
camera_pos = camera_pos.mod(len(centers))
h = console.height//2
w = console.width//2
map: Map = g.world[None].components[Map]
# Draw walls and floors
doors = [ d.components[Position] for d in g.world.Q.all_of(tags=[IsWalllike]) ]
for i in range(console.width):
for j in range(console.height):
world_pos = Position(i-w,j-h)+camera_pos
map_pos = world_pos_to_map_pos(world_pos)
in_map = 0 <= map_pos.x < map.width and 0 <= map_pos.y < map.height
in_fov = in_map and map.fov[map_pos.y,map_pos.x]
if not in_fov:
console.rgba[["ch","fg"]][j, i] = 0x2591, (50,50,50, 255)
continue
walkable = (not in_map) or map.walkable[map_pos.y, map_pos.x]
if walkable or world_pos in doors:
console.rgba[["ch","fg"]][j, i] = FLOOR_CHAR, (70,70,70, 255)
continue
ch = VERTICAL_WALL_CHAR
if map.walkable[map_pos.y+1, map_pos.x] and world_pos+Position(0,1) not in doors:
ch = WALL_CHAR
console.rgba[["ch", "fg", "bg"]][j, i] = ch, (255,255,255, 255), (0,0,0,0)
# draw all entities that are not actors
for entity in g.world.Q.all_of(components=[Position, Graphic]).none_of(tags=[IsActor]):
_draw_entity(entity, camera_pos, w, h)
# draw all actors
for actor in g.world.Q.all_of(components=[Position, Graphic], tags=[IsActor]).none_of(tags=[IsPlayer]):
_draw_entity(actor, camera_pos, w, h)
# draw the player
for player in g.world.Q.all_of(tags=[IsPlayer]):
_draw_entity(player, camera_pos, w, h)
if text := g.world[None].components.get(("Text", str)):
console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0))
if hp_text := g.world[None].components.get(("HP_Text", str)):
console.print(x=0, y=console.height - 2, string=hp_text, fg=(255, 255, 255), bg=(0, 0, 0))