diff --git a/awpy/analytics/map_control.py b/awpy/analytics/map_control.py index d9727c9b1..b232a8f10 100644 --- a/awpy/analytics/map_control.py +++ b/awpy/analytics/map_control.py @@ -292,7 +292,7 @@ def _extract_team_metadata( """ coords = ("x", "y", "z") alive_players: list[PlayerPosition] = [ - [player[dim] for dim in coords] + tuple(player[dim] for dim in coords) for player in side_data["players"] or [] if player["isAlive"] ] diff --git a/awpy/analytics/nav.py b/awpy/analytics/nav.py index 9dde5cd1a..533d8c89a 100644 --- a/awpy/analytics/nav.py +++ b/awpy/analytics/nav.py @@ -39,7 +39,7 @@ from collections import defaultdict from itertools import pairwise from statistics import mean, median -from typing import Literal, get_args +from typing import Literal, get_args, overload import networkx as nx import numpy as np @@ -56,6 +56,8 @@ DistanceType, GameFrame, PlaceMatrix, + PlayerPosition, + PlayerPosition2D, TileId, Token, ) @@ -104,8 +106,22 @@ def point_in_area(map_name: str, area_id: int, point: list[float]) -> bool: return contains_x and contains_y +@overload def find_closest_area( - map_name: str, point: list[float], *, flat: bool = False + map_name: str, point: PlayerPosition, *, flat: Literal[False] = ... +) -> ClosestArea: + ... + + +@overload +def find_closest_area( + map_name: str, point: PlayerPosition2D, *, flat: Literal[True] = ... +) -> ClosestArea: + ... + + +def find_closest_area( + map_name: str, point: PlayerPosition | PlayerPosition2D, *, flat: bool = False ) -> ClosestArea: """Finds the closest area in the nav mesh. @@ -113,7 +129,7 @@ def find_closest_area( Args: map_name (string): Map to search - point (list): Point as a list [x,y,z] + point (tuple): Point as a tuple (x,y,z) or (x, y) flat (Boolean): Whether z should be ignored. Returns: @@ -128,10 +144,10 @@ def find_closest_area( raise ValueError(msg) if flat: if len(point) != 2: # noqa: PLR2004 - msg = "Point must be a list [X,Y] when flat is True" + msg = "Point must be a tuple (X,Y) when flat is True" raise ValueError(msg) elif len(point) != 3: # noqa: PLR2004 - msg = "Point must be a list [X,Y,Z]" + msg = "Point must be a list (X,Y,Z)" raise ValueError(msg) closest_area: ClosestArea = { "mapName": map_name, @@ -148,7 +164,8 @@ def find_closest_area( dist = np.sqrt( (point[0] - avg_x) ** 2 + (point[1] - avg_y) ** 2 - + (point[2] - avg_z) ** 2 + # If flat is false we have a 3D PlayerPosition + + (point[2] - avg_z) ** 2 # pyright: ignore [reportGeneralTypeIssues] ) if dist < closest_area["distance"]: closest_area["areaId"] = area @@ -184,7 +201,7 @@ def _check_arguments_area_distance( if map_name not in NAV: msg = "Map not found." raise ValueError(msg) - if (area_a not in NAV[map_name].keys()) or (area_b not in NAV[map_name].keys()): + if (area_a not in NAV[map_name]) or (area_b not in NAV[map_name]): msg = "Area ID not found." raise ValueError(msg) if dist_type not in get_args(DistanceType): @@ -342,8 +359,8 @@ def area_distance( def point_distance( map_name: str, - point_a: list[float], - point_b: list[float], + point_a: PlayerPosition, + point_b: PlayerPosition, dist_type: PointDistanceType = "graph", ) -> DistanceObject: """Returns the distance between two points. @@ -442,7 +459,7 @@ def generate_position_token(map_name: str, frame: GameFrame) -> Token: for player in ct_players: if player["isAlive"]: closest_area = find_closest_area( - map_name, [player["x"], player["y"], player["z"]] + map_name, (player["x"], player["y"], player["z"]) ) ct_token[ map_area_names.index(NAV[map_name][closest_area["areaId"]]["areaName"]) @@ -452,7 +469,7 @@ def generate_position_token(map_name: str, frame: GameFrame) -> Token: for player in t_players: if player["isAlive"]: closest_area = find_closest_area( - map_name, [player["x"], player["y"], player["z"]] + map_name, (player["x"], player["y"], player["z"]) ) t_token[ map_area_names.index(NAV[map_name][closest_area["areaId"]]["areaName"]) @@ -784,20 +801,20 @@ def generate_centroids( # Get the centroids and rep. point of the hull try: my_polygon = Polygon(hull) - my_centroid = [ + my_centroid = ( *list(np.array(my_polygon.centroid.coords)[0]), mean(z_s[area_name]), - ] - rep_point = [ + ) + rep_point = ( *list(np.array(my_polygon.representative_point().coords)[0]), mean(z_s[area_name]), - ] + ) except ValueError: # A LinearRing must have at least 3 coordinate tuples - my_centroid = [ + my_centroid = ( mean([x for (x, _) in hull]), mean([y for (_, y) in hull]), mean(z_s[area_name]), - ] + ) rep_point = my_centroid # Find the closest tile for these points diff --git a/awpy/types.py b/awpy/types.py index bbc33c21e..24472ec89 100644 --- a/awpy/types.py +++ b/awpy/types.py @@ -708,8 +708,11 @@ class RoundStatistics(TypedDict): # Type to represent tile id for navigation tiles. TileId: TypeAlias = int -# Type to represent player position (list of floats [x, y, z]) -PlayerPosition: TypeAlias = list[float] +# Type to represent player position (tuple of floats [x, y, z]) +PlayerPosition: TypeAlias = tuple[float, float, float] + +# Type to represent player position (tuple of floats [x, y, z]) +PlayerPosition2D: TypeAlias = tuple[float, float] # Return type for awpy.analytics.map_control._bfs_helper. # Contains map control values for one team. diff --git a/pyproject.toml b/pyproject.toml index d9abbb36c..cf84bf948 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -194,7 +194,8 @@ extension-pkg-allow-list = ["cv2"] [tool.pylint.basic] # Good variable names which should always be accepted, separated by a comma. -good-names = ["i", "j", "k", "ex", "Run", "_", "x", "y", "z", "e"] +good-names = ["i", "j", "k", "ex", "Run", "_", "x", "y", "z", "e", "PlayerPosition2D"] +include-naming-hint = true [tool.pylint.design] # Maximum number of arguments for function / method. diff --git a/tests/test_nav.py b/tests/test_nav.py index d1115d1e6..466568729 100644 --- a/tests/test_nav.py +++ b/tests/test_nav.py @@ -517,11 +517,13 @@ def test_point_in_area(self): def test_find_area(self): """Tests find_area.""" with pytest.raises(ValueError, match="Map not found."): - find_closest_area(map_name="test", point=[0, 0, 0]) - with pytest.raises(ValueError, match=re.escape("Point must be a list [X,Y,Z]")): + find_closest_area(map_name="test", point=(0, 0, 0)) + with pytest.raises( + ValueError, match=re.escape("Point must be a tuple (X,Y,Z)") + ): find_closest_area(map_name="de_dust2", point=[0, 0]) with pytest.raises( - ValueError, match=re.escape("Point must be a list [X,Y] when flat is True") + ValueError, match=re.escape("Point must be a tuple (X,Y) when flat is True") ): find_closest_area(map_name="de_dust2", point=[0, 0, 0], flat=True) example_area = NAV["de_dust2"][152]