mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
217 lines
7.6 KiB
Python
217 lines
7.6 KiB
Python
# Released under the MIT License. See LICENSE for details.
|
|
#
|
|
"""Functionality related to game results."""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import weakref
|
|
from dataclasses import dataclass
|
|
from typing import TYPE_CHECKING
|
|
|
|
from efro.util import asserttype
|
|
from ba._team import Team, SessionTeam
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Sequence
|
|
import ba
|
|
|
|
|
|
@dataclass
|
|
class WinnerGroup:
|
|
"""Entry for a winning team or teams calculated by game-results."""
|
|
|
|
score: int | None
|
|
teams: Sequence[ba.SessionTeam]
|
|
|
|
|
|
class GameResults:
|
|
"""
|
|
Results for a completed game.
|
|
|
|
Category: **Gameplay Classes**
|
|
|
|
Upon completion, a game should fill one of these out and pass it to its
|
|
ba.Activity.end call.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self._game_set = False
|
|
self._scores: dict[
|
|
int, tuple[weakref.ref[ba.SessionTeam], int | None]
|
|
] = {}
|
|
self._sessionteams: list[weakref.ref[ba.SessionTeam]] | None = None
|
|
self._playerinfos: list[ba.PlayerInfo] | None = None
|
|
self._lower_is_better: bool | None = None
|
|
self._score_label: str | None = None
|
|
self._none_is_winner: bool | None = None
|
|
self._scoretype: ba.ScoreType | None = None
|
|
|
|
def set_game(self, game: ba.GameActivity) -> None:
|
|
"""Set the game instance these results are applying to."""
|
|
if self._game_set:
|
|
raise RuntimeError('Game set twice for GameResults.')
|
|
self._game_set = True
|
|
self._sessionteams = [
|
|
weakref.ref(team.sessionteam) for team in game.teams
|
|
]
|
|
scoreconfig = game.getscoreconfig()
|
|
self._playerinfos = copy.deepcopy(game.initialplayerinfos)
|
|
self._lower_is_better = scoreconfig.lower_is_better
|
|
self._score_label = scoreconfig.label
|
|
self._none_is_winner = scoreconfig.none_is_winner
|
|
self._scoretype = scoreconfig.scoretype
|
|
|
|
def set_team_score(self, team: ba.Team, score: int | None) -> None:
|
|
"""Set the score for a given team.
|
|
|
|
This can be a number or None.
|
|
(see the none_is_winner arg in the constructor)
|
|
"""
|
|
assert isinstance(team, Team)
|
|
sessionteam = team.sessionteam
|
|
self._scores[sessionteam.id] = (weakref.ref(sessionteam), score)
|
|
|
|
def get_sessionteam_score(self, sessionteam: ba.SessionTeam) -> int | None:
|
|
"""Return the score for a given ba.SessionTeam."""
|
|
assert isinstance(sessionteam, SessionTeam)
|
|
for score in list(self._scores.values()):
|
|
if score[0]() is sessionteam:
|
|
return score[1]
|
|
|
|
# If we have no score value, assume None.
|
|
return None
|
|
|
|
@property
|
|
def sessionteams(self) -> list[ba.SessionTeam]:
|
|
"""Return all ba.SessionTeams in the results."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get teams until game is set.")
|
|
teams = []
|
|
assert self._sessionteams is not None
|
|
for team_ref in self._sessionteams:
|
|
team = team_ref()
|
|
if team is not None:
|
|
teams.append(team)
|
|
return teams
|
|
|
|
def has_score_for_sessionteam(self, sessionteam: ba.SessionTeam) -> bool:
|
|
"""Return whether there is a score for a given session-team."""
|
|
return any(s[0]() is sessionteam for s in self._scores.values())
|
|
|
|
def get_sessionteam_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr:
|
|
"""Return the score for the given session-team as an Lstr.
|
|
|
|
(properly formatted for the score type.)
|
|
"""
|
|
from ba._gameutils import timestring
|
|
from ba._language import Lstr
|
|
from ba._generated.enums import TimeFormat
|
|
from ba._score import ScoreType
|
|
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get team-score-str until game is set.")
|
|
for score in list(self._scores.values()):
|
|
if score[0]() is sessionteam:
|
|
if score[1] is None:
|
|
return Lstr(value='-')
|
|
if self._scoretype is ScoreType.SECONDS:
|
|
return timestring(
|
|
score[1] * 1000,
|
|
centi=False,
|
|
timeformat=TimeFormat.MILLISECONDS,
|
|
)
|
|
if self._scoretype is ScoreType.MILLISECONDS:
|
|
return timestring(
|
|
score[1], centi=True, timeformat=TimeFormat.MILLISECONDS
|
|
)
|
|
return Lstr(value=str(score[1]))
|
|
return Lstr(value='-')
|
|
|
|
@property
|
|
def playerinfos(self) -> list[ba.PlayerInfo]:
|
|
"""Get info about the players represented by the results."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get player-info until game is set.")
|
|
assert self._playerinfos is not None
|
|
return self._playerinfos
|
|
|
|
@property
|
|
def scoretype(self) -> ba.ScoreType:
|
|
"""The type of score."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get score-type until game is set.")
|
|
assert self._scoretype is not None
|
|
return self._scoretype
|
|
|
|
@property
|
|
def score_label(self) -> str:
|
|
"""The label associated with scores ('points', etc)."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get score-label until game is set.")
|
|
assert self._score_label is not None
|
|
return self._score_label
|
|
|
|
@property
|
|
def lower_is_better(self) -> bool:
|
|
"""Whether lower scores are better."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get lower-is-better until game is set.")
|
|
assert self._lower_is_better is not None
|
|
return self._lower_is_better
|
|
|
|
@property
|
|
def winning_sessionteam(self) -> ba.SessionTeam | None:
|
|
"""The winning ba.SessionTeam if there is exactly one, or else None."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get winners until game is set.")
|
|
winners = self.winnergroups
|
|
if winners and len(winners[0].teams) == 1:
|
|
return winners[0].teams[0]
|
|
return None
|
|
|
|
@property
|
|
def winnergroups(self) -> list[WinnerGroup]:
|
|
"""Get an ordered list of winner groups."""
|
|
if not self._game_set:
|
|
raise RuntimeError("Can't get winners until game is set.")
|
|
|
|
# Group by best scoring teams.
|
|
winners: dict[int, list[ba.SessionTeam]] = {}
|
|
scores = [
|
|
score
|
|
for score in self._scores.values()
|
|
if score[0]() is not None and score[1] is not None
|
|
]
|
|
for score in scores:
|
|
assert score[1] is not None
|
|
sval = winners.setdefault(score[1], [])
|
|
team = score[0]()
|
|
assert team is not None
|
|
sval.append(team)
|
|
results: list[tuple[int | None, list[ba.SessionTeam]]] = list(
|
|
winners.items()
|
|
)
|
|
results.sort(
|
|
reverse=not self._lower_is_better,
|
|
key=lambda x: asserttype(x[0], int),
|
|
)
|
|
|
|
# Also group the 'None' scores.
|
|
none_sessionteams: list[ba.SessionTeam] = []
|
|
for score in self._scores.values():
|
|
scoreteam = score[0]()
|
|
if scoreteam is not None and score[1] is None:
|
|
none_sessionteams.append(scoreteam)
|
|
|
|
# Add the Nones to the list (either as winners or losers
|
|
# depending on the rules).
|
|
if none_sessionteams:
|
|
nones: list[tuple[int | None, list[ba.SessionTeam]]] = [
|
|
(None, none_sessionteams)
|
|
]
|
|
if self._none_is_winner:
|
|
results = nones + results
|
|
else:
|
|
results = results + nones
|
|
|
|
return [WinnerGroup(score, team) for score, team in results]
|