Source code for scripts.nqp.actions.skill

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from snecs.typedefs import EntityID

import scripts.engine.core.matter
from scripts.engine.core import world
from scripts.engine.core.component import Position
from scripts.engine.core.effect import AffectCooldownEffect, ApplyAfflictionEffect, DamageEffect, Effect, MoveSelfEffect
from scripts.engine.internal import library
from scripts.engine.internal.action import Skill
from scripts.engine.internal.constant import (
    DamageType,
    Direction,
    DirectionType,
    PrimaryStat,
    ProjectileExpiry,
    ProjectileSpeed,
    Shape,
    ShapeType,
    TargetingMethod,
    TargetingMethodType,
    TerrainCollision,
    TileTag,
    TileTagType,
    TravelMethod,
)
from scripts.engine.internal.definition import DelayedSkillData, ProjectileData, TraitSpritePathsData
from scripts.engine.world_objects.tile import Tile

if TYPE_CHECKING:
    from typing import List, Optional


[docs]class Move(Skill): """ Basic move for an entity. """ # casting cast_tags: List[TileTagType] = [TileTag.NO_BLOCKING_TILE] # targeting range: int = 1 target_tags: List[TileTagType] = [TileTag.SELF] targeting_method: TargetingMethodType = TargetingMethod.DIRECTION target_directions: List[DirectionType] = [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT] shape: ShapeType = Shape.TARGET shape_size: int = 1 # delivery uses_projectile: bool = False projectile_data: Optional[ProjectileData] = None is_delayed: bool = False delayed_skill_data: Optional[DelayedSkillData] = None
[docs] def __init__(self, user: EntityID, target_tile: Tile, direction): """ Move needs an init as it overrides the target tile """ # override target position = scripts.engine.core.matter.get_entitys_component(user, Position) tile = world.get_tile((position.x, position.y)) super().__init__(user, tile, direction)
[docs] def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[Effect]: # type:ignore """ Build the effects of this skill applying to a single entity. """ move_effect = MoveSelfEffect( origin=self.user, target=entity, success_effects=[], failure_effects=[], direction=self.direction, move_amount=1, ) return self._post_build_effects(entity, potency, [move_effect])
[docs]class BasicAttack(Skill): """ Basic attack for an entity """ # casting cast_tags: List[TileTagType] = [TileTag.ACTOR] # targeting range: int = 1 target_tags: List[TileTagType] = [TileTag.ACTOR] targeting_method: TargetingMethodType = TargetingMethod.TILE target_directions: List[DirectionType] = [ Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT, Direction.UP_LEFT, Direction.UP_RIGHT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT, ] shape: ShapeType = Shape.TARGET shape_size: int = 1 # delivery uses_projectile: bool = False projectile_data: Optional[ProjectileData] = None is_delayed: bool = False delayed_skill_data: Optional[DelayedSkillData] = None
[docs] def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[Effect]: # type:ignore """ Build the effects of this skill applying to a single entity. """ damage_effect = DamageEffect( origin=self.user, success_effects=[], failure_effects=[], target=entity, stat_to_target=PrimaryStat.VIGOUR, accuracy=library.GAME_CONFIG.base_values.accuracy, damage=int(library.GAME_CONFIG.base_values.damage * potency), damage_type=DamageType.MUNDANE, mod_stat=PrimaryStat.CLOUT, mod_amount=0.1, ) return self._post_build_effects(entity, potency, [damage_effect])
[docs]class Lunge(Skill): """ Lunge skill for an entity """ # casting cast_tags: List[TileTagType] = [TileTag.ACTOR] # targeting range: int = 1 target_tags: List[TileTagType] = [TileTag.ACTOR] targeting_method: TargetingMethodType = TargetingMethod.DIRECTION target_directions: List[DirectionType] = [ Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT, Direction.UP_LEFT, Direction.UP_RIGHT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT, ] shape: ShapeType = Shape.TARGET shape_size: int = 1 # delivery uses_projectile: bool = False projectile_data: Optional[ProjectileData] = None is_delayed: bool = False delayed_skill_data: Optional[DelayedSkillData] = None
[docs] def __init__(self, user: EntityID, tile: Tile, direction: DirectionType): """ Set the target tile as the current tile since we need to move. N.B. ignores provided tile. """ position = scripts.engine.core.matter.get_entitys_component(user, Position) if position: _tile = world.get_tile((position.x, position.y)) else: _tile = world.get_tile((0, 0)) # should always have position but just in case super().__init__(user, _tile, direction) self.move_amount = 2
[docs] def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[Effect]: """ Build the skill effects """ # chain the effects conditionally cooldown_effect = self._build_cooldown_reduction_effect(entity=entity) damage_effect = self._build_damage_effect(success_effects=[cooldown_effect], potency=potency) move_effect = self._build_move_effect(entity=entity, success_effects=([damage_effect] if damage_effect else [])) return [move_effect]
[docs] def _build_move_effect(self, entity: EntityID, success_effects: List[Effect]) -> MoveSelfEffect: """ Return the move effect for the lunge """ move_effect = MoveSelfEffect( origin=self.user, target=entity, success_effects=success_effects, failure_effects=[], direction=self.direction, move_amount=self.move_amount, ) return move_effect
[docs] def _build_damage_effect(self, success_effects: List[Effect], potency: float = 1.0) -> Optional[DamageEffect]: """ Return the damage effect for the lunge """ target = self._find_target() damage_effect = None if target: damage_effect = DamageEffect( origin=self.user, success_effects=success_effects, failure_effects=[], target=target, stat_to_target=PrimaryStat.VIGOUR, accuracy=library.GAME_CONFIG.base_values.accuracy, damage=int(library.GAME_CONFIG.base_values.damage * potency), damage_type=DamageType.MUNDANE, mod_stat=PrimaryStat.CLOUT, mod_amount=0.1, ) return damage_effect
[docs] def _find_target(self) -> Optional[EntityID]: """ Find the first entity that will be affected by the lunge """ increment = (self.direction[0] * (self.move_amount + 1), self.direction[1] * (self.move_amount + 1)) target_tile_pos = (self.target_tile.x + increment[0], self.target_tile.y + increment[1]) entities = scripts.engine.core.matter.get_entities_on_tile(world.get_tile(target_tile_pos)) if not entities: return None return entities[0]
[docs] def _build_cooldown_reduction_effect(self, entity: EntityID) -> AffectCooldownEffect: """ Returns an effect that executes the cooldown effect for the lunge """ cooldown_effect = AffectCooldownEffect( origin=self.user, target=entity, skill_name=self.name, affect_amount=2, success_effects=[], failure_effects=[], ) return cooldown_effect
[docs]class TarAndFeather(Skill): """ TarAndFeather skill for an entity """ # casting cast_tags: List[TileTagType] = [TileTag.NO_BLOCKING_TILE] # targeting range: int = 5 target_tags: List[TileTagType] = [TileTag.ACTOR] targeting_method: TargetingMethodType = TargetingMethod.TILE target_directions: List[DirectionType] = [ Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT, Direction.UP_LEFT, Direction.UP_RIGHT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT, ] shape: ShapeType = Shape.TARGET shape_size: int = 1 # delivery uses_projectile: bool = True projectile_data: Optional[ProjectileData] = ProjectileData( sprite_paths=TraitSpritePathsData(idle="skills/projectile.png", move="skills/projectile.png"), speed=ProjectileSpeed.AVERAGE, travel_method=TravelMethod.STANDARD, range=5, terrain_collision=TerrainCollision.FIZZLE, expiry_type=ProjectileExpiry.FIZZLE, ) is_delayed: bool = False delayed_skill_data: Optional[DelayedSkillData] = None
[docs] def __init__(self, user: EntityID, target_tile: Tile, direction: DirectionType): super().__init__(user, target_tile, direction) self.affliction_name = "flaming" self.affliction_duration = 5 self.reduced_modifier = 0.5 self.cone_size = 1
[docs] def _build_effects(self, hit_entity: EntityID, potency: float = 1.0) -> List[Effect]: """ Build the skill effects """ # get position position = scripts.engine.core.matter.get_entitys_component(hit_entity, Position) if not position: return [] # the cone should start where the hit occurred and in the direction of the projectile. entities_in_cone = scripts.engine.core.matter.get_affected_entities( (position.x, position.y), Shape.CONE, self.cone_size, self.direction ) # we should also ignore the hit entity and the projectile from the extra effects entities_in_cone = [x for x in entities_in_cone if x is not hit_entity and x is not self.projectile] reduced_effects = [] for entity_in_cone in entities_in_cone: reduced_effects += self._create_effects(target=entity_in_cone, modifier=self.reduced_modifier * potency) logging.warning(f"creating effects for {entity_in_cone}") first_hit_effects = self._create_effects(target=hit_entity, success_effects=reduced_effects, modifier=potency) return first_hit_effects
def _create_effects(self, target: EntityID, success_effects: List[Effect] = None, modifier: float = 1.0): damage_effect = self._build_damage_effect(target, success_effects or [], modifier) flaming_effect = self._build_flaming_effect(target, modifier) return [damage_effect, flaming_effect] def _build_flaming_effect(self, entity: EntityID, modifier: float): flaming_effect = ApplyAfflictionEffect( origin=self.user, target=entity, affliction_name=self.affliction_name, duration=max(1, int(self.affliction_duration * modifier)), success_effects=[], failure_effects=[], ) return flaming_effect def _build_damage_effect(self, entity: EntityID, success_effects: List[Effect], modifier: float): damage_effect = DamageEffect( origin=self.user, success_effects=success_effects, failure_effects=[], target=entity, stat_to_target=PrimaryStat.VIGOUR, accuracy=library.GAME_CONFIG.base_values.accuracy, damage=int(library.GAME_CONFIG.base_values.damage * modifier), damage_type=DamageType.MUNDANE, mod_stat=PrimaryStat.CLOUT, mod_amount=0.1, ) return damage_effect
[docs]class Splash(Skill): """ Simple projectile attack """ # casting cast_tags: List[TileTagType] = [TileTag.NO_BLOCKING_TILE] # targeting range: int = 3 target_tags: List[TileTagType] = [TileTag.OTHER_ENTITY] targeting_method: TargetingMethodType = TargetingMethod.TILE target_directions: List[DirectionType] = [ Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT, Direction.UP_LEFT, Direction.UP_RIGHT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT, ] shape: ShapeType = Shape.TARGET shape_size: int = 1 # delivery uses_projectile: bool = True projectile_data: Optional[ProjectileData] = ProjectileData( sprite_paths=TraitSpritePathsData(idle="skills/projectile.png", move="skills/projectile.png"), speed=ProjectileSpeed.SLOW, travel_method=TravelMethod.STANDARD, range=3, terrain_collision=TerrainCollision.FIZZLE, expiry_type=ProjectileExpiry.FIZZLE, ) is_delayed: bool = False delayed_skill_data: Optional[DelayedSkillData] = None
[docs] def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[DamageEffect]: # type:ignore """ Build the effects of this skill applying to a single entity. """ damage_effect = DamageEffect( origin=self.user, success_effects=[], failure_effects=[], target=entity, stat_to_target=PrimaryStat.VIGOUR, accuracy=library.GAME_CONFIG.base_values.accuracy, damage=int(library.GAME_CONFIG.base_values.damage * potency), damage_type=DamageType.MUNDANE, mod_stat=PrimaryStat.CLOUT, mod_amount=0.1, ) return [damage_effect]
[docs]class Lightning(Skill): """ Test the Delayed Skill functionality. """ # casting cast_tags: List[TileTagType] = [TileTag.NO_BLOCKING_TILE] # targeting range: int = 3 target_tags: List[TileTagType] = [TileTag.ANY] targeting_method: TargetingMethodType = TargetingMethod.TILE target_directions: List[DirectionType] = [ Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT, Direction.UP_LEFT, Direction.UP_RIGHT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT, ] shape: ShapeType = Shape.TARGET shape_size: int = 1 # delivery uses_projectile: bool = False projectile_data: Optional[ProjectileData] = None is_delayed: bool = True delayed_skill_data: Optional[DelayedSkillData] = DelayedSkillData(duration=3)
[docs] def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[DamageEffect]: # type:ignore damage_effect = DamageEffect( origin=self.user, success_effects=[], failure_effects=[], target=entity, stat_to_target=PrimaryStat.VIGOUR, accuracy=library.GAME_CONFIG.base_values.accuracy + 20, damage=int(library.GAME_CONFIG.base_values.damage * potency), damage_type=DamageType.MUNDANE, mod_stat=PrimaryStat.CLOUT, mod_amount=0.1, ) return [damage_effect]