from __future__ import annotations
import logging
from typing import Optional, Tuple
import pygame
import tcod
from snecs.typedefs import EntityID
import scripts.engine.core.matter
from scripts.engine.core import hourglass, query, world
from scripts.engine.core.component import (
Aesthetic,
Afflictions,
FOV,
Immunities,
IsActive,
Knowledge,
Lifespan,
Opinion,
Physicality,
Position,
Reaction,
Sight,
Tracked,
)
from scripts.engine.internal.constant import (
Direction,
EventType,
FOV_ALGORITHM,
FOV_LIGHT_WALLS,
INFINITE,
MAX_ACTIVATION_DISTANCE,
ReactionTrigger,
ReactionTriggerType,
SpriteCategory,
SpriteCategoryType,
)
from scripts.engine.internal.definition import EffectData, ReactionData
from scripts.engine.internal.event import (
AffectCooldownEvent,
AffectStatEvent,
AfflictionEvent,
ChangeMapEvent,
DamageEvent,
event_hub,
MoveEvent,
Subscriber,
UseSkillEvent,
WinConditionMetEvent,
)
from scripts.engine.world_objects.tile import Tile
__all__ = [
"process_activations",
"process_light_map",
"process_fov",
"process_tile_visibility",
"reduce_skill_cooldowns",
"reduce_affliction_durations",
"reduce_lifespan_durations",
"reduce_immunity_durations",
]
########################### GENERAL ################################
[docs]def process_activations():
"""
Allocate active component to appropriate NPCs. Entity with no position or with position and close to player.
"""
# all entities with no position must be active
for entity, (_,) in query.not_position:
if not scripts.engine.core.matter.entity_has_component(entity, IsActive):
scripts.engine.core.matter.add_component(entity, IsActive())
# check entities in range of player
player = scripts.engine.core.matter.get_player()
player_pos: Position = scripts.engine.core.matter.get_entitys_component(player, Position)
for entity, (pos,) in query.position:
# check if they're close enough that we care
distance_x = abs(player_pos.x - pos.x)
distance_y = abs(player_pos.y - pos.y)
if max(distance_x, distance_y) < MAX_ACTIVATION_DISTANCE:
# they're close, now check they arent already active
if not scripts.engine.core.matter.entity_has_component(entity, IsActive):
scripts.engine.core.matter.add_component(entity, IsActive())
# update tracked to current time (otherwise they will be behind and act repeatedly)
if scripts.engine.core.matter.entity_has_component(entity, Tracked):
tracked = scripts.engine.core.matter.get_entitys_component(entity, Tracked)
tracked.time_spent = hourglass.get_time() + 1
else:
# not close enough, remove active
if scripts.engine.core.matter.entity_has_component(entity, IsActive):
scripts.engine.core.matter.remove_component(entity, IsActive)
########################## VISION ##################################
[docs]def process_light_map():
"""
Update light map and light box using light sources of all entities
"""
# get game map details
game_map = world.get_game_map()
light_map = game_map.light_map
# create transparency layer
block_sight_map = game_map.block_sight_map
# reset light map
light_map[:] = False
for entity, (
is_active,
light_source,
pos,
) in query.active_and_light_source_and_position:
radius = light_source.radius
# create fov for light source and add to light map
fov = tcod.map.compute_fov(block_sight_map, (pos.x, pos.y), radius, FOV_LIGHT_WALLS, FOV_ALGORITHM)
light_map |= fov
# assign back post updates
game_map.light_map = light_map
[docs]def process_fov():
"""
Update FOV for all active entities
"""
# get game map details
game_map = world.get_game_map()
# create transparency layer
block_sight_map = game_map.block_sight_map
for entity, (is_active, pos, physicality, identity, stats, traits, fov, tracked, immunities) in query.active_actors:
# get all entities blocking sight
updated_block_sight_map = block_sight_map.copy()
for other_entity, (_, other_pos, other_physicality) in query.active_and_position_and_physicality:
assert isinstance(other_pos, Position)
assert isinstance(other_physicality, Physicality)
# dont check against self
if entity == other_entity:
continue
# is viewing_entity taller and therefore their sight isnt blocked?
if physicality.height > other_physicality.height:
continue
# set all positions to blocking
for x, y in other_pos.coordinates:
updated_block_sight_map[x, y] = 0
# update entities fov map
sight_range = scripts.engine.core.matter.get_entitys_component(entity, Sight).sight_range
fov.map = tcod.map.compute_fov(
updated_block_sight_map, (pos.x, pos.y), sight_range, FOV_LIGHT_WALLS, FOV_ALGORITHM
)
[docs]def process_tile_visibility():
"""
Update tile visibility based on player fov
"""
# get player info
player = scripts.engine.core.matter.get_player()
fov_map = scripts.engine.core.matter.get_entitys_component(player, FOV).map
# get game map details
game_map = world.get_game_map()
width = game_map.width
height = game_map.height
light_map = game_map.light_map
tile_map = game_map.tile_map
# set all tiles to not visible
for x in range(0, width):
for y in range(0, height):
game_map.tile_map[x][y].is_visible = False
# combine maps
visible_map = fov_map & light_map
# loop all map
for x in range(0, width):
for y in range(0, height):
tile_map[x][y].is_visible = bool(visible_map[x, y]) # cast to bool as it is numpy _bool
########################## TIME ####################################
[docs]def reduce_skill_cooldowns():
"""
Reduce skill cool down for all entities.
"""
for entity, (knowledge,) in query.knowledge:
assert isinstance(knowledge, Knowledge)
for skill_name in knowledge.skill_names:
skill_cooldown = knowledge.cooldowns[skill_name]
if skill_cooldown > 0:
knowledge.set_skill_cooldown(skill_name, skill_cooldown - 1)
[docs]def reduce_affliction_durations():
"""
Reduce all affliction durations
"""
for entity, (afflictions,) in query.afflictions:
assert isinstance(afflictions, Afflictions)
for affliction in afflictions.active:
if affliction.duration != INFINITE:
# reduce duration if not infinite
affliction.duration -= 1
# handle expiry
if affliction.duration <= 0:
scripts.engine.core.matter.remove_affliction(entity, affliction)
logging.debug(f"Removed {affliction.name} from '{scripts.engine.core.matter.get_name(entity)}'.")
[docs]def reduce_lifespan_durations():
"""
Reduce all lifespan durations
"""
for entity, (lifespan,) in query.lifespan:
assert isinstance(lifespan, Lifespan)
if lifespan.duration != INFINITE:
# reduce duration if not infinite
lifespan.duration -= 1
# handle expiry
if lifespan.duration <= 0:
scripts.engine.core.matter.kill_entity(entity)
logging.debug(
f"'{scripts.engine.core.matter.get_name(entity)}'s lifespan has expired and they have been killed."
)
[docs]def reduce_immunity_durations():
"""
Reduce all immunity durations
"""
for entity, (immunities,) in query.immunities:
assert isinstance(immunities, Immunities)
_active = immunities.active.copy()
for immunity_name, duration in _active.items():
if duration != INFINITE:
# reduce duration if not infinite
immunities.active[immunity_name] -= 1
duration -= 1
# handle expiry
if duration <= 0:
immunities.active.pop(immunity_name)
logging.debug(
f"Removed {immunity_name} from '{scripts.engine.core.matter.get_name(entity)}'s "
f"list of immunities."
)