# Copyright 2018-present Jakub Kuczys (https://github.com/jack1142)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import sys
from typing import Any, Dict, List, Optional, Union
from .enums import Platform, PlaylistKey
from .tier_estimates import TierEstimates
from .typedefs import PlaylistBreakdownType, TierBreakdownType
if sys.version_info[:2] >= (3, 8):
from typing import Final
else:
from typing_extensions import Final
RANKS = (
"Unranked",
"Bronze I",
"Bronze II",
"Bronze III",
"Silver I",
"Silver II",
"Silver III",
"Gold I",
"Gold II",
"Gold III",
"Platinum I",
"Platinum II",
"Platinum III",
"Diamond I",
"Diamond II",
"Diamond III",
"Champion I",
"Champion II",
"Champion III",
"Grand Champion I",
"Grand Champion II",
"Grand Champion III",
"Supersonic Legend",
)
DIVISIONS = ("I", "II", "III", "IV")
PLAYLISTS_WITH_SEASON_REWARDS = (
PlaylistKey.solo_duel,
PlaylistKey.doubles,
PlaylistKey.standard,
PlaylistKey.hoops,
PlaylistKey.rumble,
PlaylistKey.dropshot,
PlaylistKey.snow_day,
)
__all__ = ("Playlist", "SeasonRewards", "Player")
[docs]class Playlist:
"""Playlist()
Represents Rocket League playlist stats data.
.. container:: operations
``str(x)``
Returns playlist's rank string, e.g. "Champion I Div III"
Attributes
----------
key: `PlaylistKey` or `int`
Playlist's key. Might be `int`, if that key
is not within the ones recognised by the enumerator.
tier: int
Tier on this playlist.
division: int
Division on this playlist.
mu: float
Mu on this playlist.
skill: int
Skill rating on this playlist.
sigma: float
Sigma on this playlist.
win_streak: int
Win streak on this playlist.
matches_played: int
Amount of matches played on this playlist.
breakdown: dict
Playlist tier breakdown.
tier_estimates: `TierEstimates`
Tier estimates for this playlist.
"""
TIER_MAX: Final[int] = 22
__slots__ = (
"key",
"tier",
"division",
"mu",
"skill",
"sigma",
"win_streak",
"matches_played",
"breakdown",
"tier_estimates",
)
def __init__(
self,
*,
breakdown: Optional[PlaylistBreakdownType] = None,
playlist_key: Union[PlaylistKey, int],
data: Dict[str, Any],
):
self.key = playlist_key
# only mu and sigma always exist, rest might be None or not be part of the dict
self.tier: int = data.get("tier") or 0
self.division: int = data.get("division") or 0
mu: Optional[float] = data.get("mu")
self.mu: float
if mu is not None:
self.mu = mu
else:
self.mu = 25
skill: Optional[int] = data.get("skill")
self.skill: int
if skill is not None:
self.skill = skill
else:
self.skill = int(self.mu * 20 + 100)
self.sigma: float = data.get("sigma") or 8.333
self.win_streak: int = data.get("win_streak") or 0
self.matches_played: int = data.get("matches_played") or 0
self.breakdown = breakdown if breakdown is not None else {}
self.tier_estimates = TierEstimates(self)
def __str__(self) -> str:
try:
if self.tier in {0, self.TIER_MAX}:
return RANKS[self.tier]
return f"{RANKS[self.tier]} Div {DIVISIONS[self.division]}"
except IndexError:
return "Unknown"
def __repr__(self) -> str:
return (
f"<{self.__class__.__name__}"
f" {self.key};"
f" Rank {self};"
f" mu={self.mu}"
f" skill={self.skill}"
f" win_streak={self.win_streak}"
f" matches_played={self.matches_played}"
f">"
)
[docs]class SeasonRewards:
"""SeasonRewards()
Represents season rewards informations.
Attributes
----------
level: int
Player's season reward level.
wins: int
Player's season reward wins.
can_advance: bool
Tells if player can advance in season rewards.
"""
__slots__ = ("level", "wins", "can_advance")
def __init__(self, *, highest_tier: int = 0, data: Dict[str, Any]) -> None:
self.level: int = data.get("level") or 0
self.wins: int = data.get("wins") or 0
self.can_advance: bool
if self.level == 0 or self.level * 3 < highest_tier:
self.can_advance = True
else:
self.can_advance = False
def __repr__(self) -> str:
return (
f"<{self.__class__.__name__}"
f" level={self.level}"
f" wins={self.wins}"
f" can_advance={self.can_advance}"
f">"
)
[docs]class Player:
"""Player()
Represents Rocket League Player
Attributes
----------
platform: `Platform`
Player's platform.
player_id: str
``player_id`` as passed to `Client.get_player()`.
user_id: str, optional
Player's user ID.
Only present for Steam and Epic Games players.
user_name: str
Player's username (display name).
playlists: dict
Dictionary mapping `PlaylistKey` with `Playlist`.
tier_breakdown: dict
Tier breakdown.
highest_tier: int
Highest tier of the player.
Doesn't include the playlists that don't count towards season rewards.
season_rewards: `SeasonRewards`
Season rewards info.
"""
__slots__ = (
"platform",
"player_id",
"user_id",
"user_name",
"playlists",
"tier_breakdown",
"highest_tier",
"season_rewards",
)
def __init__(
self,
*,
tier_breakdown: Optional[TierBreakdownType] = None,
platform: Platform,
player_id: str,
data: Dict[str, Any],
) -> None:
self.platform = platform
self.player_id = player_id
self.user_id: Optional[str] = data.get("player_id")
self.user_name: str = data["player_name"]
self.playlists: Dict[Union[PlaylistKey, int], Playlist] = {}
player_skills = data.get("player_skills", [])
self.tier_breakdown = tier_breakdown if tier_breakdown is not None else {}
self._prepare_playlists(player_skills)
self.highest_tier = max(
(
playlist.tier
for playlist in self.playlists.values()
if playlist.key in PLAYLISTS_WITH_SEASON_REWARDS
),
default=0,
)
season_rewards = data.get("season_rewards", {})
self.season_rewards = SeasonRewards(
highest_tier=self.highest_tier, data=season_rewards
)
def __repr__(self) -> str:
platform_repr = f"{self.platform.__class__.__name__}.{self.platform._name_}"
return (
f"<{self.__class__.__name__}"
f" platform={platform_repr}"
f" player_id={self.player_id!r}"
f">"
)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, self.__class__):
return False
# I could just check `player_id`, but it's user provided
# so two objects could potentially be equal even if `player_id` isn't
if self.platform is not other.platform:
return False
# both object have `user_id` so we can just compare those
if None not in (self.user_id, other.user_id):
return self.user_id == other.user_id
# it's rather unlikely that only one `user_id` is None if platforms are equal,
# but checking equality of both `user_id` and `user_name` just in case
return (self.user_id, self.user_name) == (other.user_id, other.user_name)
def __hash__(self) -> int:
if self.user_id is not None:
return hash((self.platform, "by_user_id", self.user_id))
return hash((self.platform, "by_user_name", self.user_name))
[docs] def get_playlist(self, playlist_key: PlaylistKey) -> Optional[Playlist]:
"""
Get playlist for the player.
Parameters
----------
playlist_key: PlaylistKey
`PlaylistKey` for playlist to get.
Returns
-------
`Playlist`, optional
Playlist object for provided playlist key.
"""
return self.playlists.get(playlist_key)
def add_playlist(self, playlist: Dict[str, Any]) -> None:
playlist_key = playlist.pop("playlist")
breakdown = self.tier_breakdown.get(playlist_key, {})
with contextlib.suppress(ValueError):
playlist_key = PlaylistKey(playlist_key)
self.playlists[playlist_key] = Playlist(
breakdown=breakdown, playlist_key=playlist_key, data=playlist
)
def _prepare_playlists(self, player_skills: List[Dict[str, Any]]) -> None:
for playlist in player_skills:
self.add_playlist(playlist)