player sessions and coordinate models
All checks were successful
Build and Push Docker Image / build (push) Successful in 13s

This commit is contained in:
Corban-Lee Jones 2024-12-11 21:41:21 +00:00
parent e3da8ec805
commit 55304170b1
2 changed files with 118 additions and 39 deletions

View File

@ -118,7 +118,7 @@ class PlayersCog(commands.Cog):
""" """
""" """
log.debug("processing connected player") log.debug("processing connected player")
re_pattern = r'\[(?P<timestamp>.*?)\] (?P<steam_id>\d+) "(?P<username>.*?)" fully connected \((?P<coordinates>\d+,\d+,\d+)\)' re_pattern = r'\[(?P<timestamp>.*?)\] (?P<steam_id>\d+) "(?P<username>.*?)" fully connected \((?P<x>\d+),(?P<y>\d+),(?P<z>\d+)\)'
re_match = re.search(re_pattern, line) re_match = re.search(re_pattern, line)
if not re_match: if not re_match:
@ -126,9 +126,11 @@ class PlayersCog(commands.Cog):
return return
player, created = await Player.get_or_create(username=re_match.group("username")) player, created = await Player.get_or_create(username=re_match.group("username"))
player.last_connection = datetime.strptime( await player.open_session(
re_match.group("timestamp"), timestamp=datetime.strptime(re_match.group("timestamp"), "%m-%d-%y %H:%M:%S.%f"),
"%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.update_steam_summary(re_match.group("steam_id"), self.bot.steam_api_key)
await player.save() await player.save()
@ -153,7 +155,7 @@ class PlayersCog(commands.Cog):
""" """
""" """
log.debug("processing disconnected player") log.debug("processing disconnected player")
re_pattern = r'\[(?P<timestamp>.*?)\] (?P<steam_id>\d+) "(?P<username>.*?)" disconnected player \((?P<coordinates>\d+,\d+,\d+)\)' re_pattern = r'\[(?P<timestamp>.*?)\] (?P<steam_id>\d+) "(?P<username>.*?)" disconnected player \((?P<x>\d+),(?P<y>\d+),(?P<z>\d+)\)'
re_match = re.search(re_pattern, line) re_match = re.search(re_pattern, line)
if not re_match: if not re_match:
@ -161,9 +163,11 @@ class PlayersCog(commands.Cog):
return return
player, created = await Player.get_or_create(username=re_match.group("username")) player, created = await Player.get_or_create(username=re_match.group("username"))
player.last_disconnection = datetime.strptime( await player.close_session(
re_match.group("timestamp"), timestamp=datetime.strptime(re_match.group("timestamp"), "%m-%d-%y %H:%M:%S.%f"),
"%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.update_steam_summary(re_match.group("steam_id"), self.bot.steam_api_key)
await player.save() await player.save()

View File

@ -3,12 +3,13 @@ Database schemas.
""" """
import logging import logging
from datetime import datetime from datetime import datetime, timedelta
import httpx import httpx
from tortoise import fields from tortoise import fields
from tortoise.queryset import QuerySet from tortoise.functions import Sum
from tortoise.models import Model from tortoise.expressions import F
from tortoise.models import Model, Q
from discord import Embed from discord import Embed
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -29,68 +30,141 @@ class SteamProfileSummary(Model):
table = "steam_profile_summary" table = "steam_profile_summary"
class Coordinates(Model):
x = fields.IntField()
y = fields.IntField()
z = fields.IntField()
class Meta:
table = "ingame_coordinates"
class PlayerDeath(Model): class PlayerDeath(Model):
player = fields.ForeignKeyField( player = fields.ForeignKeyField(
model_name="models.Player", model_name="models.Player",
on_delete=fields.CASCADE on_delete=fields.CASCADE
) )
coordinate_x = fields.IntField()
coordinate_y = fields.IntField()
coordinate_z = fields.IntField()
cause = fields.CharField(max_length=32) cause = fields.CharField(max_length=32)
timestamp = fields.DatetimeField(auto_now_add=True) timestamp = fields.DatetimeField(auto_now_add=True)
coordinates = fields.ForeignKeyField(
model_name="models.Coordinates",
on_delete=fields.CASCADE
)
class Meta: class Meta:
table = "player_deaths" 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): class Player(Model):
""" """
""" """
username = fields.CharField(max_length=20, unique=True) 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) is_dead = fields.BooleanField(default=False)
class Meta: class Meta:
table = "players" table = "players"
@property async def get_playtime(self) -> timedelta:
def is_online(self) -> bool: playtime = await PlayerSession.filter(player=self).exclude(disconnected_at=None).annotate(
""" total_playtime=Sum(F("disconnected_at") - F("connected_at"))
""" ).values()
if not self.last_connection: return timedelta(seconds=playtime[0]["total_playtime"].total_seconds() if playtime else timedelta())
return False
return (self.last_connection and not self.last_disconnection) \ async def get_deaths(self) -> list[PlayerDeath]:
or self.last_connection > self.last_disconnection
async def get_deaths(self) -> QuerySet[PlayerDeath]:
return await PlayerDeath.filter(player=self) return await PlayerDeath.filter(player=self)
async def add_death( async def add_death(
self, self,
coord_x: str | int, coord_x: str | int,
coord_y: str | int, coord_y: str | int,
coord_z: str | int, coord_z: str | int,
cause: str, cause: str,
timestamp: datetime timestamp: datetime
) -> PlayerDeath: ) -> PlayerDeath:
""" """
""" """
log.debug("Assigning death to player: %s", self.username) log.debug("Assigning death to player: %s", self.username)
self.is_dead = True self.is_dead = True
await self.save() await self.save()
coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z)
return await PlayerDeath.create( return await PlayerDeath.create(
player=self, player=self,
coordinate_x=coord_x,
coordinate_y=coord_y,
coordinate_z=coord_z,
cause=cause, 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: 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.") raise ValueError("You must fetch the steam_profile_summary before creating an embed.")
death_count = len(await self.get_deaths()) death_count = len(await self.get_deaths())
playtime = str(await self.get_playtime())
embed = Embed( embed = Embed(
title="Player", title="Player",
description=( description=(
f"{self.username} ([{summary.profile_name}]({summary.url}))\n" f"{self.username} ([{summary.profile_name}]({summary.url}))\n"
f"Deaths: {death_count}\n" f"Deaths: {death_count}\n"
f"Playtime ???" f"Playtime: {playtime}"
) )
) )
embed.set_thumbnail(url=summary.avatar_url) embed.set_thumbnail(url=summary.avatar_url)