From 55304170b1fcd2e7dc0b253abba08dbe0c4d45f8 Mon Sep 17 00:00:00 2001 From: Corban-Lee Jones Date: Wed, 11 Dec 2024 21:41:21 +0000 Subject: [PATCH] player sessions and coordinate models --- cogs/players.py | 20 ++++--- utils/models.py | 137 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 118 insertions(+), 39 deletions(-) diff --git a/cogs/players.py b/cogs/players.py index 2a961c0..7afe9a2 100644 --- a/cogs/players.py +++ b/cogs/players.py @@ -118,7 +118,7 @@ class PlayersCog(commands.Cog): """ """ log.debug("processing connected player") - re_pattern = r'\[(?P.*?)\] (?P\d+) "(?P.*?)" fully connected \((?P\d+,\d+,\d+)\)' + re_pattern = r'\[(?P.*?)\] (?P\d+) "(?P.*?)" fully connected \((?P\d+),(?P\d+),(?P\d+)\)' re_match = re.search(re_pattern, line) if not re_match: @@ -126,9 +126,11 @@ class PlayersCog(commands.Cog): return player, created = await Player.get_or_create(username=re_match.group("username")) - player.last_connection = datetime.strptime( - re_match.group("timestamp"), - "%m-%d-%y %H:%M:%S.%f" + await player.open_session( + timestamp=datetime.strptime(re_match.group("timestamp"), "%m-%d-%y %H:%M:%S.%f"), + coord_x=re_match.group("x"), + coord_y=re_match.group("y"), + coord_z=re_match.group("z") ) await player.update_steam_summary(re_match.group("steam_id"), self.bot.steam_api_key) await player.save() @@ -153,7 +155,7 @@ class PlayersCog(commands.Cog): """ """ log.debug("processing disconnected player") - re_pattern = r'\[(?P.*?)\] (?P\d+) "(?P.*?)" disconnected player \((?P\d+,\d+,\d+)\)' + re_pattern = r'\[(?P.*?)\] (?P\d+) "(?P.*?)" disconnected player \((?P\d+),(?P\d+),(?P\d+)\)' re_match = re.search(re_pattern, line) if not re_match: @@ -161,9 +163,11 @@ class PlayersCog(commands.Cog): return player, created = await Player.get_or_create(username=re_match.group("username")) - player.last_disconnection = datetime.strptime( - re_match.group("timestamp"), - "%m-%d-%y %H:%M:%S.%f" + await player.close_session( + timestamp=datetime.strptime(re_match.group("timestamp"), "%m-%d-%y %H:%M:%S.%f"), + coord_x=re_match.group("x"), + coord_y=re_match.group("y"), + coord_z=re_match.group("z") ) await player.update_steam_summary(re_match.group("steam_id"), self.bot.steam_api_key) await player.save() diff --git a/utils/models.py b/utils/models.py index 1704fcb..abd10f6 100644 --- a/utils/models.py +++ b/utils/models.py @@ -3,12 +3,13 @@ Database schemas. """ import logging -from datetime import datetime +from datetime import datetime, timedelta import httpx from tortoise import fields -from tortoise.queryset import QuerySet -from tortoise.models import Model +from tortoise.functions import Sum +from tortoise.expressions import F +from tortoise.models import Model, Q from discord import Embed log = logging.getLogger(__name__) @@ -29,68 +30,141 @@ class SteamProfileSummary(Model): table = "steam_profile_summary" +class Coordinates(Model): + x = fields.IntField() + y = fields.IntField() + z = fields.IntField() + + class Meta: + table = "ingame_coordinates" + + class PlayerDeath(Model): player = fields.ForeignKeyField( model_name="models.Player", on_delete=fields.CASCADE ) - coordinate_x = fields.IntField() - coordinate_y = fields.IntField() - coordinate_z = fields.IntField() cause = fields.CharField(max_length=32) timestamp = fields.DatetimeField(auto_now_add=True) + coordinates = fields.ForeignKeyField( + model_name="models.Coordinates", + on_delete=fields.CASCADE + ) class Meta: table = "player_deaths" +class PlayerSession(Model): + player = fields.ForeignKeyField( + model_name="models.Player", + on_delete=fields.CASCADE + ) + connected_at = fields.DatetimeField() + disconnected_at = fields.DatetimeField(null=False) + connected_coords = fields.ForeignKeyField( + model_name="models.Coordinates", + on_delete=fields.CASCADE + ) + disconnected_coords = fields.ForeignKeyField( + model_name="models.Coordinates", + on_delete=fields.CASCADE, + null=True + ) + + class Meta: + table = "player_session" + + @property + def playtime(self) -> timedelta: + if not self.disconnected_at: + return datetime.now() - self.connected_at + + return self.disconnected_at - self.connected_at + + class Player(Model): """ """ username = fields.CharField(max_length=20, unique=True) - last_connection = fields.DatetimeField(null=True) - last_disconnection = fields.DatetimeField(null=True) - play_time_seconds = fields.IntField(default=0) is_dead = fields.BooleanField(default=False) class Meta: table = "players" - @property - def is_online(self) -> bool: - """ - """ - if not self.last_connection: - return False + async def get_playtime(self) -> timedelta: + playtime = await PlayerSession.filter(player=self).exclude(disconnected_at=None).annotate( + total_playtime=Sum(F("disconnected_at") - F("connected_at")) + ).values() + return timedelta(seconds=playtime[0]["total_playtime"].total_seconds() if playtime else timedelta()) - return (self.last_connection and not self.last_disconnection) \ - or self.last_connection > self.last_disconnection - - async def get_deaths(self) -> QuerySet[PlayerDeath]: + async def get_deaths(self) -> list[PlayerDeath]: return await PlayerDeath.filter(player=self) async def add_death( - self, - coord_x: str | int, - coord_y: str | int, - coord_z: str | int, - cause: str, - timestamp: datetime - ) -> PlayerDeath: + self, + coord_x: str | int, + coord_y: str | int, + coord_z: str | int, + cause: str, + timestamp: datetime + ) -> PlayerDeath: """ """ log.debug("Assigning death to player: %s", self.username) self.is_dead = True await self.save() + coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z) return await PlayerDeath.create( player=self, - coordinate_x=coord_x, - coordinate_y=coord_y, - coordinate_z=coord_z, cause=cause, - timestamp=timestamp + timestamp=timestamp, + coordinates=coordinates ) + async def get_latest_session(self, ignore_closed_sessions: bool = False) -> PlayerSession: + queryset = PlayerSession.filter(player=self) + if ignore_closed_sessions: + queryset = queryset.filter(disconnected_at=None) + + return await queryset.last() + + async def open_session( + self, + timestamp: datetime, + coord_x: str | int, + coord_y: str | int, + coord_z: str | int, + ) -> PlayerSession: + log.debug("creating session for player: %s", self.username) + existing_session = await self.get_latest_session(ignore_closed_sessions=True) + if existing_session: + raise ValueError("Tried to open session while an open one exists.") + + coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z) + return await PlayerSession.create( + player=self, + connected_at=timestamp, + connected_coords=coordinates + ) + + async def close_session( + self, + timestamp: datetime, + coord_x: str | int, + coord_y: str | int, + coord_z: str | int, + ) -> PlayerSession: + log.debug("closing session for player: %s", self.username) + current_session = await self.get_latest_session(ignore_closed_sessions=True) + if not current_session: + raise ValueError("Tried to close session that doesn't exist.") + + coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z) + current_session.disconnected_coords = coordinates + current_session.disconnected_at = timestamp + await current_session.save() + async def get_steam_summary(self) -> SteamProfileSummary | None: """ """ @@ -139,13 +213,14 @@ class Player(Model): raise ValueError("You must fetch the steam_profile_summary before creating an embed.") death_count = len(await self.get_deaths()) + playtime = str(await self.get_playtime()) embed = Embed( title="Player", description=( f"{self.username} ([{summary.profile_name}]({summary.url}))\n" f"Deaths: {death_count}\n" - f"Playtime ???" + f"Playtime: {playtime}" ) ) embed.set_thumbnail(url=summary.avatar_url)