Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2 beta #340

Merged
merged 8 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
- name: Test
shell: bash
run: |
poetry run coverage run -m pytest --durations=10 -s --traceconfig --log-cli-level=DEBUG tests/
poetry run coverage run -m pytest --durations=10
poetry run coverage report -m

# - name: Archive code coverage results
Expand Down
13 changes: 0 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,6 @@ jobs:
- name: Install awpy
run: |
poetry install --no-interaction

# - name: Publish to test PyPI
# run: |
# poetry publish --build --repository testpypi --username __token__ --password ${{ secrets.TEST_PYPI_API_TOKEN }} -vvv

- name: Debug Step
run: |
echo "Listing directory contents"
ls -la
echo "Checking Poetry version"
poetry --version
echo "Checking installed packages"
poetry show

- name: Publish to PyPI
run: |
Expand Down
48 changes: 34 additions & 14 deletions awpy/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@
from demoparser2 import DemoParser # pylint: disable=E0611
from loguru import logger

from awpy.parsers import (
from awpy.parsers.clock import parse_times
from awpy.parsers.events import (
parse_bomb,
parse_damages,
parse_grenades,
parse_infernos,
parse_kills,
parse_rounds,
parse_smokes,
parse_ticks,
parse_weapon_fires,
)
from awpy.parsers.rounds import parse_rounds
from awpy.parsers.ticks import parse_ticks
from awpy.utils import apply_round_num

PROP_WARNING_LIMIT = 40
Expand Down Expand Up @@ -193,21 +194,40 @@ def _parse_events(self) -> None:
raise ValueError(no_events_error_msg)

if self.parse_rounds is True:
self.rounds = parse_rounds(self.parser)
self.rounds = parse_rounds(
self.parser, self.events
) # Must pass parser for round start/end events

self.kills = apply_round_num(self.rounds, parse_kills(self.events))
self.damages = apply_round_num(self.rounds, parse_damages(self.events))
self.bomb = apply_round_num(self.rounds, parse_bomb(self.events))
self.smokes = apply_round_num(
self.rounds, parse_smokes(self.events), tick_col="start_tick"
self.kills = parse_times(
apply_round_num(self.rounds, parse_kills(self.events)), self.rounds
)
self.infernos = apply_round_num(
self.rounds, parse_infernos(self.events), tick_col="start_tick"
self.damages = parse_times(
apply_round_num(self.rounds, parse_damages(self.events)), self.rounds
)
self.weapon_fires = apply_round_num(
self.rounds, parse_weapon_fires(self.events)
self.bomb = parse_times(
apply_round_num(self.rounds, parse_bomb(self.events)), self.rounds
)
self.smokes = parse_times(
apply_round_num(
self.rounds, parse_smokes(self.events), tick_col="start_tick"
),
self.rounds,
tick_col="start_tick",
)
self.infernos = parse_times(
apply_round_num(
self.rounds, parse_infernos(self.events), tick_col="start_tick"
),
self.rounds,
tick_col="start_tick",
)
self.weapon_fires = parse_times(
apply_round_num(self.rounds, parse_weapon_fires(self.events)),
self.rounds,
)
self.grenades = parse_times(
apply_round_num(self.rounds, parse_grenades(self.parser)), self.rounds
)
self.grenades = apply_round_num(self.rounds, parse_grenades(self.parser))

# Parse ticks
if self.parse_ticks is True:
Expand Down
1 change: 1 addition & 0 deletions awpy/parsers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Module for specific parsing functions."""
120 changes: 120 additions & 0 deletions awpy/parsers/clock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Module for time and clock parsing functions."""

import math
from typing import Literal, Union

import pandas as pd

ROUND_START_DEFAULT_TIME_IN_SECS = 20
FREEZE_DEFAULT_TIME_IN_SECS = 115
BOMB_DEFAULT_TIME_IN_SECS = 40


def parse_clock(
seconds_since_phase_change: int,
max_time_ticks: Union[Literal["start", "freeze", "bomb"], int],
tick_rate: int = 64,
) -> str:
"""Parse the remaining time in a round or phase to a clock string.

Args:
seconds_since_phase_change (int): The number of seconds since the phase change.
max_time_ticks (Union[Literal['start', 'freeze', 'bomb'], int]): The maximum
time in ticks for the phase.
tick_rate (int, optional): The tick rate of the server. Defaults to 64.

Returns:
str: The remaining time in MM:SS format.
"""
if max_time_ticks == "start":
max_time_ticks = ROUND_START_DEFAULT_TIME_IN_SECS * tick_rate
elif max_time_ticks == "freeze":
max_time_ticks = FREEZE_DEFAULT_TIME_IN_SECS * tick_rate
elif max_time_ticks == "bomb":
max_time_ticks = BOMB_DEFAULT_TIME_IN_SECS * tick_rate

# Calculate the remaining time in ticks
remaining_ticks = max_time_ticks - seconds_since_phase_change

# Convert remaining ticks to total seconds
remaining_seconds = remaining_ticks / tick_rate

# Round up the seconds
remaining_seconds = math.ceil(remaining_seconds)

# Calculate minutes and seconds
minutes = remaining_seconds // 60
seconds = remaining_seconds % 60

# Format as MM:SS with leading zeros
return f"{int(minutes):02}:{int(seconds):02}"


def _find_clock_time(row: pd.Series) -> str:
"""Find the clock time for a row.

Args:
row: A row from a dataframe with ticks_since_* columns.
"""
times = {
"start": row["ticks_since_round_start"],
"freeze": row["ticks_since_freeze_time_end"],
"bomb": row["ticks_since_bomb_plant"],
}
# Filter out NA values and find the key with the minimum value
min_key = min((k for k in times if pd.notna(times[k])), key=lambda k: times[k])
return parse_clock(times[min_key], min_key)


def parse_times(
df: pd.DataFrame, rounds_df: pd.DataFrame, tick_col: str = "tick"
) -> pd.DataFrame:
"""Adds time_since_* columns to the dataframe.

Args:
df (pd.DataFrame): The dataframe to add the time columns to.
rounds_df (pd.DataFrame): The rounds dataframe.
tick_col (str): The column name of the tick column.

Returns:
pd.DataFrame: The dataframe with the timesince_* columns added.
"""
if tick_col not in df.columns:
tick_col_missing_msg = f"{tick_col} not found in dataframe."
raise ValueError(tick_col_missing_msg)

df_with_round_info = df.merge(rounds_df, on="round", how="left")
df_with_round_info["ticks_since_round_start"] = (
df_with_round_info[tick_col] - df_with_round_info["start"]
)
df_with_round_info["ticks_since_freeze_time_end"] = (
df_with_round_info[tick_col] - df_with_round_info["freeze_end"]
)
df_with_round_info["ticks_since_bomb_plant"] = (
df_with_round_info[tick_col] - df_with_round_info["bomb_plant"]
)

# Apply the function to the selected columns
for col in df_with_round_info.columns:
if col.startswith("ticks_since_"):
df_with_round_info[col] = (
df_with_round_info[col]
.map(lambda x: pd.NA if x < 0 else x)
.astype(pd.Int64Dtype())
)

df_with_round_info = df_with_round_info.drop(
columns=[
"start",
"freeze_end",
"end",
"official_end",
"winner",
"reason",
"bomb_plant",
]
)

df_with_round_info["clock"] = df_with_round_info.apply(_find_clock_time, axis=1)

return df_with_round_info
Loading