Source code for scripts.nqp.processors.targeting

import math
import os
from typing import Iterable, List, Optional, Tuple

import pygame
from pygame.rect import Rect
from pygame.surface import Surface

import scripts.engine.core.matter
from scripts.engine.core import query, state, utility, world
from scripts.engine.core.component import Aesthetic, Position
from scripts.engine.core.ui import ui
from scripts.engine.internal.constant import (
    EventType,
    GameState,
    Height,
    InputEventType,
    InputIntent,
    TargetingMethod,
    TILE_SIZE,
    UIElement,
)
from scripts.nqp import command
from scripts.nqp.ui_elements.tile_info import TileInfo

__all__ = ["targeting"]


[docs]class Targeting:
[docs] def __init__(self): self.hovered_tile = None self.valid_line_of_sight = False self.possible_los_tiles = None
[docs] def process_events(self, camera): """ For processing local events based on camera data. Updates the current hovered tile based on mouse position and updates the TILE_INFO GUI element. """ mx, my = pygame.mouse.get_pos() mx /= camera._desired_width / camera._base_width my /= camera._desired_height / camera._base_height hover_pos = (int(camera.start_x + mx / TILE_SIZE), int(camera.start_y + my / TILE_SIZE)) try: new_hover = world.get_tile(hover_pos) if (new_hover is self.hovered_tile) == False: if new_hover.is_visible: if not ui.has_element(UIElement.TILE_INFO): tile_info: TileInfo = TileInfo( command.get_element_rect(UIElement.TILE_INFO), ui.get_gui_manager() ) ui.register_element(UIElement.TILE_INFO, tile_info) tile_info = ui.get_element(UIElement.TILE_INFO) tile_info.set_selected_tile_pos(hover_pos) tile_info.cleanse() ui.set_element_visibility(UIElement.TILE_INFO, True) else: ui.set_element_visibility(UIElement.TILE_INFO, False) self.hovered_tile = new_hover except IndexError: pass
[docs] def process_click(self): """ Process a click. This is currently just used during targeting to trigger the cast event since the camera tracks what tiles are targetable. TODO: Move targeting logic out of the camera. """ state.set_skill_target_valid(False) if self.hovered_tile and self.hovered_tile.is_visible: if state.get_current() == GameState.TARGETING: active_skill_name = state.get_active_skill() player = scripts.engine.core.matter.get_player() skill = scripts.engine.core.matter.get_known_skill(player, active_skill_name) # set the skill target for the skill cast if the hovered tile is a valid target during line of sight targeting if (skill.targeting_method == TargetingMethod.DIRECTION) and self.valid_line_of_sight: state.set_active_skill_target((self.hovered_tile.x, self.hovered_tile.y)) state.set_skill_target_valid(True) # cancel event triggering if the hovered target is invalid in the target mode if skill.targeting_method == TargetingMethod.TILE: position = scripts.engine.core.matter.get_entitys_component(player, Position) dif = (position.x - self.hovered_tile.x, position.y - self.hovered_tile.y) if ( (dif not in skill.target_directions) or (not self.hovered_tile.is_visible) or (not world.tile_has_tags(player, self.hovered_tile, skill.target_tags)) ): return event = pygame.event.Event( EventType.INPUT, subtype=InputEventType.TILE_CLICK, tile_pos=(self.hovered_tile.x, self.hovered_tile.y) ) pygame.event.post(event)
[docs] def render(self, camera, target_surf: pygame.Surface): # targeting mode related indicator rendering if state.get_current() == GameState.TARGETING: player = scripts.engine.core.matter.get_player() position = scripts.engine.core.matter.get_entitys_component(player, Position) active_skill_name = state.get_active_skill() skill = scripts.engine.core.matter.get_known_skill(player, active_skill_name) # if this is a directional attack if skill.targeting_method == TargetingMethod.TILE: for direction in skill.target_directions: target_x = position.x + direction[0] target_y = position.y + direction[1] tile = world.get_tile((target_x, target_y)) if tile.is_visible and world.tile_has_tags(player, tile, skill.target_tags): option_r = pygame.Rect( (target_x - camera.start_x) * TILE_SIZE, (target_y - camera.start_y) * TILE_SIZE, TILE_SIZE, TILE_SIZE, ) pygame.draw.rect(target_surf, (60, 70, 120), option_r, 1) # if this is a line of sight attack elif skill.targeting_method == TargetingMethod.DIRECTION: # regenerate possible targets if targeting mode was just entered if self.possible_los_tiles == None: self.possible_los_tiles = [] state.set_skill_target_valid(False) for y in range(skill.range * 2 + 1): for x in range(skill.range * 2 + 1): test_pos = (position.x - skill.range + x, position.y - skill.range + y) valid, _ = self.check_valid_line_of_sight(test_pos) if valid: self.possible_los_tiles.append(test_pos) # check if current hovered tile is a valid target self.valid_line_of_sight = False self.valid_line_of_sight, tile_path = self.check_valid_line_of_sight( (self.hovered_tile.x, self.hovered_tile.y) ) # render indicators for tile_pos in self.possible_los_tiles: highlight_r = pygame.Rect( (tile_pos[0] - camera.start_x) * TILE_SIZE, (tile_pos[1] - camera.start_y) * TILE_SIZE, TILE_SIZE, TILE_SIZE, ) pygame.draw.rect(target_surf, (60, 70, 120), highlight_r, 1) if self.valid_line_of_sight: for tile_pos in tile_path: highlight_r = pygame.Rect( (tile_pos[0] - camera.start_x) * TILE_SIZE, (tile_pos[1] - camera.start_y) * TILE_SIZE, TILE_SIZE, TILE_SIZE, ) pygame.draw.rect(target_surf, (50, 150, 200), highlight_r, 1) # empty targeting data if not in targeting mode else: self.possible_los_tiles = None if self.hovered_tile and self.hovered_tile.is_visible: hover_r = pygame.Rect( (self.hovered_tile.x - camera.start_x) * TILE_SIZE, (self.hovered_tile.y - camera.start_y) * TILE_SIZE, TILE_SIZE, TILE_SIZE, ) pygame.draw.rect(target_surf, (200, 200, 0), hover_r, 1) target_surf.blit(pygame.transform.scale(target_surf, target_surf.get_size()), (0, 0))
[docs] def check_valid_line_of_sight(self, pos: Tuple[int, int]) -> Tuple[bool, List]: """ Checks if a tile is in the line of sight from the player and returns the visible path to the target tile. """ # get base info and init player = scripts.engine.core.matter.get_player() position = scripts.engine.core.matter.get_entitys_component(player, Position) active_skill_name = state.get_active_skill() skill = scripts.engine.core.matter.get_known_skill(player, active_skill_name) valid_line_of_sight = False target_tile = world.get_tile(pos) tile_path = [] # check if the endpoint is even valid if target_tile.is_visible and world.tile_has_tags(player, target_tile, skill.target_tags): # init some values for search current_pos = [position.x, position.y] tile_path = [current_pos.copy()] target_pos = list(pos) # get list of possible search directions directions_for_steps = [(-1, 0), (1, 0), (0, 1), (0, -1), (-1, -1), (1, 1), (1, -1), (-1, 1)] # this set is for distance calculations # the 0.81 reduces the weight of diagonal movement so it isn't chosen too often directions_for_math = [ (-1, 0), (1, 0), (0, 1), (0, -1), (-0.81, -0.81), (0.81, 0.81), (0.81, -0.81), (-0.81, 0.81), ] valid = True # iteratively search for the bordering tile that's closest to the target while current_pos != target_pos: closest = [ current_pos.copy(), math.sqrt((target_pos[0] - current_pos[0]) ** 2 + (target_pos[1] - current_pos[1]) ** 2), ] for i, direction in enumerate(directions_for_steps): check_pos = [current_pos[0] + direction[0], current_pos[1] + direction[1]] math_check_pos = [ current_pos[0] + directions_for_math[i][0], current_pos[1] + directions_for_math[i][1], ] dis = math.sqrt((math_check_pos[0] - target_pos[0]) ** 2 + (math_check_pos[1] - target_pos[1]) ** 2) if dis < closest[1]: # type: ignore closest = [check_pos.copy(), dis] tile = world.get_tile(closest[0]) # type: ignore # check if tile step meets targeting criteria if (not tile.is_visible) or (not world.tile_has_tags(player, tile, skill.cast_tags)): valid = False current_pos = closest[0].copy() # type: ignore tile_path.append(current_pos.copy()) # determine if the final path was valid based on skill range and previous calculations if (len(tile_path) - 1 <= skill.range) and valid: valid_line_of_sight = True return (valid_line_of_sight, tile_path)
if "GENERATING_SPHINX_DOCS" not in os.environ: # when building in CI these fail targeting = Targeting() else: targeting = "" # type: ignore