# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to teams mode score screen."""
from __future__ import annotations
from typing import override
import bascenev1 as bs
from bascenev1lib.actor.text import Text
from bascenev1lib.actor.image import Image
[docs]
class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity):
"""Base class for score screens."""
def __init__(self, settings: dict):
super().__init__(settings=settings)
self._score_display_sound = bs.getsound('scoreHit01')
self._score_display_sound_small = bs.getsound('scoreHit02')
self._show_up_next: bool = True
[docs]
@override
def on_begin(self) -> None:
super().on_begin()
session = self.session
if self._show_up_next and isinstance(session, bs.MultiTeamSession):
txt = bs.Lstr(
value='${A} ${B}',
subs=[
(
'${A}',
bs.Lstr(
resource='upNextText',
subs=[
('${COUNT}', str(session.get_game_number() + 1))
],
),
),
('${B}', session.get_next_game_description()),
],
)
Text(
txt,
maxwidth=900,
h_attach=Text.HAttach.CENTER,
v_attach=Text.VAttach.BOTTOM,
h_align=Text.HAlign.CENTER,
v_align=Text.VAlign.CENTER,
position=(0, 53),
flash=False,
color=(0.3, 0.3, 0.35, 1.0),
transition=Text.Transition.FADE_IN,
transition_delay=2.0,
).autoretain()
[docs]
def show_player_scores(
self,
*,
delay: float = 2.5,
results: bs.GameResults | None = None,
scale: float = 1.0,
x_offset: float = 0.0,
y_offset: float = 0.0,
) -> None:
"""Show scores for individual players."""
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
ts_v_offset = 150.0 + y_offset
ts_h_offs = 80.0 + x_offset
tdelay = delay
spacing = 40
is_free_for_all = isinstance(self.session, bs.FreeForAllSession)
def _get_prec_score(p_rec: bs.PlayerRecord) -> int | None:
if is_free_for_all and results is not None:
assert isinstance(results, bs.GameResults)
assert p_rec.team.activityteam is not None
val = results.get_sessionteam_score(p_rec.team)
return val
return p_rec.accumscore
def _get_prec_score_str(p_rec: bs.PlayerRecord) -> str | bs.Lstr:
if is_free_for_all and results is not None:
assert isinstance(results, bs.GameResults)
assert p_rec.team.activityteam is not None
val = results.get_sessionteam_score_str(p_rec.team)
assert val is not None
return val
return str(p_rec.accumscore)
# stats.get_records() can return players that are no longer in
# the game.. if we're using results we have to filter those out
# (since they're not in results and that's where we pull their
# scores from)
if results is not None:
assert isinstance(results, bs.GameResults)
player_records = []
valid_players = list(self.stats.get_records().items())
# noinspection PyUnresolvedReferences
def _get_player_score_set_entry(
player: bs.SessionPlayer,
) -> bs.PlayerRecord | None:
for p_rec in valid_players:
if p_rec[1].player is player:
return p_rec[1]
return None
# Results is already sorted; just convert it into a list of
# score-set-entries.
for winnergroup in results.winnergroups:
for team in winnergroup.teams:
if len(team.players) == 1:
player_entry = _get_player_score_set_entry(
team.players[0]
)
if player_entry is not None:
player_records.append(player_entry)
else:
player_records = []
player_records_scores = [
(_get_prec_score(p), name, p)
for name, p in list(self.stats.get_records().items())
]
player_records_scores.sort(reverse=True)
player_records = [p[2] for p in player_records_scores]
voffs = -140.0 + spacing * len(player_records) * 0.5
def _txt(
xoffs: float,
yoffs: float,
text: bs.Lstr,
*,
h_align: Text.HAlign = Text.HAlign.RIGHT,
extrascale: float = 1.0,
maxwidth: float | None = 120.0,
) -> None:
Text(
text,
color=(0.5, 0.5, 0.6, 0.5),
position=(
ts_h_offs + xoffs * scale,
ts_v_offset + (voffs + yoffs + 4.0) * scale,
),
h_align=h_align,
v_align=Text.VAlign.CENTER,
scale=0.8 * scale * extrascale,
maxwidth=maxwidth,
transition=Text.Transition.IN_LEFT,
transition_delay=tdelay,
).autoretain()
session = self.session
assert isinstance(session, bs.MultiTeamSession)
tval = bs.Lstr(
resource='gameLeadersText',
subs=[('${COUNT}', str(session.get_game_number()))],
)
_txt(
180,
43,
tval,
h_align=Text.HAlign.CENTER,
extrascale=1.4,
maxwidth=None,
)
_txt(-15, 4, bs.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
_txt(180, 4, bs.Lstr(resource='killsText'))
_txt(280, 4, bs.Lstr(resource='deathsText'), maxwidth=100)
score_label = 'Score' if results is None else results.score_label
translated = bs.Lstr(translate=('scoreNames', score_label))
_txt(390, 0, translated)
topkillcount = 0
topkilledcount = 99999
top_score = (
0 if not player_records else _get_prec_score(player_records[0])
)
for prec in player_records:
topkillcount = max(topkillcount, prec.accum_kill_count)
topkilledcount = min(topkilledcount, prec.accum_killed_count)
def _scoretxt(
text: str | bs.Lstr,
x_offs: float,
highlight: bool,
delay2: float,
maxwidth: float = 70.0,
) -> None:
Text(
text,
position=(
ts_h_offs + x_offs * scale,
ts_v_offset + (voffs + 15) * scale,
),
scale=scale,
color=(
(1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5)
),
h_align=Text.HAlign.RIGHT,
v_align=Text.VAlign.CENTER,
maxwidth=maxwidth,
transition=Text.Transition.IN_LEFT,
transition_delay=tdelay + delay2,
).autoretain()
for playerrec in player_records:
tdelay += 0.05
voffs -= spacing
Image(
playerrec.get_icon(),
position=(
ts_h_offs - 12 * scale,
ts_v_offset + (voffs + 15.0) * scale,
),
scale=(30.0 * scale, 30.0 * scale),
transition=Image.Transition.IN_LEFT,
transition_delay=tdelay,
).autoretain()
Text(
bs.Lstr(value=playerrec.getname(full=True)),
maxwidth=160,
scale=0.75 * scale,
position=(
ts_h_offs + 10.0 * scale,
ts_v_offset + (voffs + 15) * scale,
),
h_align=Text.HAlign.LEFT,
v_align=Text.VAlign.CENTER,
color=bs.safecolor(playerrec.team.color + (1,)),
transition=Text.Transition.IN_LEFT,
transition_delay=tdelay,
).autoretain()
_scoretxt(
str(playerrec.accum_kill_count),
180,
playerrec.accum_kill_count == topkillcount,
0.1,
)
_scoretxt(
str(playerrec.accum_killed_count),
280,
playerrec.accum_killed_count == topkilledcount,
0.1,
)
_scoretxt(
_get_prec_score_str(playerrec),
390,
_get_prec_score(playerrec) == top_score,
0.2,
)
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum