bascenev1lib.activity.freeforallvictory

Functionality related to the final screen in free-for-all games.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Functionality related to the final screen in free-for-all games."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING, override
  8
  9import bascenev1 as bs
 10
 11from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
 12
 13if TYPE_CHECKING:
 14    from typing import Any
 15
 16
 17class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
 18    """Score screen shown at after free-for-all rounds."""
 19
 20    def __init__(self, settings: dict):
 21        super().__init__(settings=settings)
 22
 23        # Keep prev activity alive while we fade in.
 24        self.transition_time = 0.5
 25        self._cymbal_sound = bs.getsound('cymbal')
 26
 27    @override
 28    def on_begin(self) -> None:
 29        # pylint: disable=too-many-locals
 30        # pylint: disable=too-many-statements
 31        from bascenev1lib.actor.text import Text
 32        from bascenev1lib.actor.image import Image
 33
 34        bs.set_analytics_screen('FreeForAll Score Screen')
 35        super().on_begin()
 36
 37        y_base = 100.0
 38        ts_h_offs = -305.0
 39        tdelay = 1.0
 40        scale = 1.2
 41        spacing = 37.0
 42
 43        # We include name and previous score in the sort to reduce the amount
 44        # of random jumping around the list we do in cases of ties.
 45        player_order_prev = list(self.players)
 46        player_order_prev.sort(
 47            reverse=True,
 48            key=lambda p: (
 49                p.team.sessionteam.customdata['previous_score'],
 50                p.getname(full=True),
 51            ),
 52        )
 53        player_order = list(self.players)
 54        player_order.sort(
 55            reverse=True,
 56            key=lambda p: (
 57                p.team.sessionteam.customdata['score'],
 58                p.team.sessionteam.customdata['score'],
 59                p.getname(full=True),
 60            ),
 61        )
 62
 63        v_offs = -74.0 + spacing * len(player_order_prev) * 0.5
 64        delay1 = 1.3 + 0.1
 65        delay2 = 2.9 + 0.1
 66        delay3 = 2.9 + 0.1
 67        order_change = player_order != player_order_prev
 68
 69        if order_change:
 70            delay3 += 1.5
 71
 72        bs.timer(0.3, self._score_display_sound.play)
 73        results = self.settings_raw['results']
 74        assert isinstance(results, bs.GameResults)
 75        self.show_player_scores(
 76            delay=0.001, results=results, scale=1.2, x_offset=-110.0
 77        )
 78
 79        sound_times: set[float] = set()
 80
 81        def _scoretxt(
 82            text: str,
 83            x_offs: float,
 84            y_offs: float,
 85            highlight: bool,
 86            delay: float,
 87            extrascale: float,
 88            flash: bool = False,
 89        ) -> Text:
 90            # pylint: disable=too-many-positional-arguments
 91            return Text(
 92                text,
 93                position=(
 94                    ts_h_offs + x_offs * scale,
 95                    y_base + (y_offs + v_offs + 2.0) * scale,
 96                ),
 97                scale=scale * extrascale,
 98                color=(
 99                    (1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7)
100                ),
101                h_align=Text.HAlign.RIGHT,
102                transition=Text.Transition.IN_LEFT,
103                transition_delay=tdelay + delay,
104                flash=flash,
105            ).autoretain()
106
107        v_offs -= spacing
108        slide_amt = 0.0
109        transtime = 0.250
110        transtime2 = 0.250
111
112        session = self.session
113        assert isinstance(session, bs.FreeForAllSession)
114        title = Text(
115            bs.Lstr(
116                resource='firstToSeriesText',
117                subs=[('${COUNT}', str(session.get_ffa_series_length()))],
118            ),
119            scale=1.05 * scale,
120            position=(
121                ts_h_offs - 0.0 * scale,
122                y_base + (v_offs + 50.0) * scale,
123            ),
124            h_align=Text.HAlign.CENTER,
125            color=(0.5, 0.5, 0.5, 0.5),
126            transition=Text.Transition.IN_LEFT,
127            transition_delay=tdelay,
128        ).autoretain()
129
130        v_offs -= 25
131        v_offs_start = v_offs
132
133        bs.timer(
134            tdelay + delay3,
135            bs.WeakCall(
136                self._safe_animate,
137                title.position_combine,
138                'input0',
139                {
140                    0.0: ts_h_offs - 0.0 * scale,
141                    transtime2: ts_h_offs - (0.0 + slide_amt) * scale,
142                },
143            ),
144        )
145
146        for i, player in enumerate(player_order_prev):
147            v_offs_2 = v_offs_start - spacing * (player_order.index(player))
148            bs.timer(tdelay + 0.3, self._score_display_sound_small.play)
149            if order_change:
150                bs.timer(tdelay + delay2 + 0.1, self._cymbal_sound.play)
151            img = Image(
152                player.get_icon(),
153                position=(
154                    ts_h_offs - 72.0 * scale,
155                    y_base + (v_offs + 15.0) * scale,
156                ),
157                scale=(30.0 * scale, 30.0 * scale),
158                transition=Image.Transition.IN_LEFT,
159                transition_delay=tdelay,
160            ).autoretain()
161            bs.timer(
162                tdelay + delay2,
163                bs.WeakCall(
164                    self._safe_animate,
165                    img.position_combine,
166                    'input1',
167                    {
168                        0: y_base + (v_offs + 15.0) * scale,
169                        transtime: y_base + (v_offs_2 + 15.0) * scale,
170                    },
171                ),
172            )
173            bs.timer(
174                tdelay + delay3,
175                bs.WeakCall(
176                    self._safe_animate,
177                    img.position_combine,
178                    'input0',
179                    {
180                        0: ts_h_offs - 72.0 * scale,
181                        transtime2: ts_h_offs - (72.0 + slide_amt) * scale,
182                    },
183                ),
184            )
185            txt = Text(
186                bs.Lstr(value=player.getname(full=True)),
187                maxwidth=130.0,
188                scale=0.75 * scale,
189                position=(
190                    ts_h_offs - 50.0 * scale,
191                    y_base + (v_offs + 15.0) * scale,
192                ),
193                h_align=Text.HAlign.LEFT,
194                v_align=Text.VAlign.CENTER,
195                color=bs.safecolor(player.team.color + (1,)),
196                transition=Text.Transition.IN_LEFT,
197                transition_delay=tdelay,
198            ).autoretain()
199            bs.timer(
200                tdelay + delay2,
201                bs.WeakCall(
202                    self._safe_animate,
203                    txt.position_combine,
204                    'input1',
205                    {
206                        0: y_base + (v_offs + 15.0) * scale,
207                        transtime: y_base + (v_offs_2 + 15.0) * scale,
208                    },
209                ),
210            )
211            bs.timer(
212                tdelay + delay3,
213                bs.WeakCall(
214                    self._safe_animate,
215                    txt.position_combine,
216                    'input0',
217                    {
218                        0: ts_h_offs - 50.0 * scale,
219                        transtime2: ts_h_offs - (50.0 + slide_amt) * scale,
220                    },
221                ),
222            )
223
224            txt_num = Text(
225                '#' + str(i + 1),
226                scale=0.55 * scale,
227                position=(
228                    ts_h_offs - 95.0 * scale,
229                    y_base + (v_offs + 8.0) * scale,
230                ),
231                h_align=Text.HAlign.RIGHT,
232                color=(0.6, 0.6, 0.6, 0.6),
233                transition=Text.Transition.IN_LEFT,
234                transition_delay=tdelay,
235            ).autoretain()
236            bs.timer(
237                tdelay + delay3,
238                bs.WeakCall(
239                    self._safe_animate,
240                    txt_num.position_combine,
241                    'input0',
242                    {
243                        0: ts_h_offs - 95.0 * scale,
244                        transtime2: ts_h_offs - (95.0 + slide_amt) * scale,
245                    },
246                ),
247            )
248
249            s_txt = _scoretxt(
250                str(player.team.sessionteam.customdata['previous_score']),
251                80,
252                0,
253                False,
254                0,
255                1.0,
256            )
257            bs.timer(
258                tdelay + delay2,
259                bs.WeakCall(
260                    self._safe_animate,
261                    s_txt.position_combine,
262                    'input1',
263                    {
264                        0: y_base + (v_offs + 2.0) * scale,
265                        transtime: y_base + (v_offs_2 + 2.0) * scale,
266                    },
267                ),
268            )
269            bs.timer(
270                tdelay + delay3,
271                bs.WeakCall(
272                    self._safe_animate,
273                    s_txt.position_combine,
274                    'input0',
275                    {
276                        0: ts_h_offs + 80.0 * scale,
277                        transtime2: ts_h_offs + (80.0 - slide_amt) * scale,
278                    },
279                ),
280            )
281
282            score_change = (
283                player.team.sessionteam.customdata['score']
284                - player.team.sessionteam.customdata['previous_score']
285            )
286            if score_change > 0:
287                xval = 113
288                yval = 3.0
289                s_txt_2 = _scoretxt(
290                    '+' + str(score_change),
291                    xval,
292                    yval,
293                    True,
294                    0,
295                    0.7,
296                    flash=True,
297                )
298                bs.timer(
299                    tdelay + delay2,
300                    bs.WeakCall(
301                        self._safe_animate,
302                        s_txt_2.position_combine,
303                        'input1',
304                        {
305                            0: y_base + (v_offs + yval + 2.0) * scale,
306                            transtime: y_base + (v_offs_2 + yval + 2.0) * scale,
307                        },
308                    ),
309                )
310                bs.timer(
311                    tdelay + delay3,
312                    bs.WeakCall(
313                        self._safe_animate,
314                        s_txt_2.position_combine,
315                        'input0',
316                        {
317                            0: ts_h_offs + xval * scale,
318                            transtime2: ts_h_offs + (xval - slide_amt) * scale,
319                        },
320                    ),
321                )
322
323                def _safesetattr(
324                    node: bs.Node | None, attr: str, value: Any
325                ) -> None:
326                    if node:
327                        setattr(node, attr, value)
328
329                bs.timer(
330                    tdelay + delay1,
331                    bs.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)),
332                )
333                for j in range(score_change):
334                    bs.timer(
335                        (tdelay + delay1 + 0.15 * j),
336                        bs.Call(
337                            _safesetattr,
338                            s_txt.node,
339                            'text',
340                            str(
341                                player.team.sessionteam.customdata[
342                                    'previous_score'
343                                ]
344                                + j
345                                + 1
346                            ),
347                        ),
348                    )
349                    tfin = tdelay + delay1 + 0.15 * j
350                    if tfin not in sound_times:
351                        sound_times.add(tfin)
352                        bs.timer(tfin, self._score_display_sound_small.play)
353            v_offs -= spacing
354
355    def _safe_animate(
356        self, node: bs.Node | None, attr: str, keys: dict[float, float]
357    ) -> None:
358        """Run an animation on a node if the node still exists."""
359        if node:
360            bs.animate(node, attr, keys)
class FreeForAllVictoryScoreScreenActivity(bascenev1._activity.Activity[bascenev1._player.EmptyPlayer, bascenev1._team.EmptyTeam]):
 18class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
 19    """Score screen shown at after free-for-all rounds."""
 20
 21    def __init__(self, settings: dict):
 22        super().__init__(settings=settings)
 23
 24        # Keep prev activity alive while we fade in.
 25        self.transition_time = 0.5
 26        self._cymbal_sound = bs.getsound('cymbal')
 27
 28    @override
 29    def on_begin(self) -> None:
 30        # pylint: disable=too-many-locals
 31        # pylint: disable=too-many-statements
 32        from bascenev1lib.actor.text import Text
 33        from bascenev1lib.actor.image import Image
 34
 35        bs.set_analytics_screen('FreeForAll Score Screen')
 36        super().on_begin()
 37
 38        y_base = 100.0
 39        ts_h_offs = -305.0
 40        tdelay = 1.0
 41        scale = 1.2
 42        spacing = 37.0
 43
 44        # We include name and previous score in the sort to reduce the amount
 45        # of random jumping around the list we do in cases of ties.
 46        player_order_prev = list(self.players)
 47        player_order_prev.sort(
 48            reverse=True,
 49            key=lambda p: (
 50                p.team.sessionteam.customdata['previous_score'],
 51                p.getname(full=True),
 52            ),
 53        )
 54        player_order = list(self.players)
 55        player_order.sort(
 56            reverse=True,
 57            key=lambda p: (
 58                p.team.sessionteam.customdata['score'],
 59                p.team.sessionteam.customdata['score'],
 60                p.getname(full=True),
 61            ),
 62        )
 63
 64        v_offs = -74.0 + spacing * len(player_order_prev) * 0.5
 65        delay1 = 1.3 + 0.1
 66        delay2 = 2.9 + 0.1
 67        delay3 = 2.9 + 0.1
 68        order_change = player_order != player_order_prev
 69
 70        if order_change:
 71            delay3 += 1.5
 72
 73        bs.timer(0.3, self._score_display_sound.play)
 74        results = self.settings_raw['results']
 75        assert isinstance(results, bs.GameResults)
 76        self.show_player_scores(
 77            delay=0.001, results=results, scale=1.2, x_offset=-110.0
 78        )
 79
 80        sound_times: set[float] = set()
 81
 82        def _scoretxt(
 83            text: str,
 84            x_offs: float,
 85            y_offs: float,
 86            highlight: bool,
 87            delay: float,
 88            extrascale: float,
 89            flash: bool = False,
 90        ) -> Text:
 91            # pylint: disable=too-many-positional-arguments
 92            return Text(
 93                text,
 94                position=(
 95                    ts_h_offs + x_offs * scale,
 96                    y_base + (y_offs + v_offs + 2.0) * scale,
 97                ),
 98                scale=scale * extrascale,
 99                color=(
100                    (1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7)
101                ),
102                h_align=Text.HAlign.RIGHT,
103                transition=Text.Transition.IN_LEFT,
104                transition_delay=tdelay + delay,
105                flash=flash,
106            ).autoretain()
107
108        v_offs -= spacing
109        slide_amt = 0.0
110        transtime = 0.250
111        transtime2 = 0.250
112
113        session = self.session
114        assert isinstance(session, bs.FreeForAllSession)
115        title = Text(
116            bs.Lstr(
117                resource='firstToSeriesText',
118                subs=[('${COUNT}', str(session.get_ffa_series_length()))],
119            ),
120            scale=1.05 * scale,
121            position=(
122                ts_h_offs - 0.0 * scale,
123                y_base + (v_offs + 50.0) * scale,
124            ),
125            h_align=Text.HAlign.CENTER,
126            color=(0.5, 0.5, 0.5, 0.5),
127            transition=Text.Transition.IN_LEFT,
128            transition_delay=tdelay,
129        ).autoretain()
130
131        v_offs -= 25
132        v_offs_start = v_offs
133
134        bs.timer(
135            tdelay + delay3,
136            bs.WeakCall(
137                self._safe_animate,
138                title.position_combine,
139                'input0',
140                {
141                    0.0: ts_h_offs - 0.0 * scale,
142                    transtime2: ts_h_offs - (0.0 + slide_amt) * scale,
143                },
144            ),
145        )
146
147        for i, player in enumerate(player_order_prev):
148            v_offs_2 = v_offs_start - spacing * (player_order.index(player))
149            bs.timer(tdelay + 0.3, self._score_display_sound_small.play)
150            if order_change:
151                bs.timer(tdelay + delay2 + 0.1, self._cymbal_sound.play)
152            img = Image(
153                player.get_icon(),
154                position=(
155                    ts_h_offs - 72.0 * scale,
156                    y_base + (v_offs + 15.0) * scale,
157                ),
158                scale=(30.0 * scale, 30.0 * scale),
159                transition=Image.Transition.IN_LEFT,
160                transition_delay=tdelay,
161            ).autoretain()
162            bs.timer(
163                tdelay + delay2,
164                bs.WeakCall(
165                    self._safe_animate,
166                    img.position_combine,
167                    'input1',
168                    {
169                        0: y_base + (v_offs + 15.0) * scale,
170                        transtime: y_base + (v_offs_2 + 15.0) * scale,
171                    },
172                ),
173            )
174            bs.timer(
175                tdelay + delay3,
176                bs.WeakCall(
177                    self._safe_animate,
178                    img.position_combine,
179                    'input0',
180                    {
181                        0: ts_h_offs - 72.0 * scale,
182                        transtime2: ts_h_offs - (72.0 + slide_amt) * scale,
183                    },
184                ),
185            )
186            txt = Text(
187                bs.Lstr(value=player.getname(full=True)),
188                maxwidth=130.0,
189                scale=0.75 * scale,
190                position=(
191                    ts_h_offs - 50.0 * scale,
192                    y_base + (v_offs + 15.0) * scale,
193                ),
194                h_align=Text.HAlign.LEFT,
195                v_align=Text.VAlign.CENTER,
196                color=bs.safecolor(player.team.color + (1,)),
197                transition=Text.Transition.IN_LEFT,
198                transition_delay=tdelay,
199            ).autoretain()
200            bs.timer(
201                tdelay + delay2,
202                bs.WeakCall(
203                    self._safe_animate,
204                    txt.position_combine,
205                    'input1',
206                    {
207                        0: y_base + (v_offs + 15.0) * scale,
208                        transtime: y_base + (v_offs_2 + 15.0) * scale,
209                    },
210                ),
211            )
212            bs.timer(
213                tdelay + delay3,
214                bs.WeakCall(
215                    self._safe_animate,
216                    txt.position_combine,
217                    'input0',
218                    {
219                        0: ts_h_offs - 50.0 * scale,
220                        transtime2: ts_h_offs - (50.0 + slide_amt) * scale,
221                    },
222                ),
223            )
224
225            txt_num = Text(
226                '#' + str(i + 1),
227                scale=0.55 * scale,
228                position=(
229                    ts_h_offs - 95.0 * scale,
230                    y_base + (v_offs + 8.0) * scale,
231                ),
232                h_align=Text.HAlign.RIGHT,
233                color=(0.6, 0.6, 0.6, 0.6),
234                transition=Text.Transition.IN_LEFT,
235                transition_delay=tdelay,
236            ).autoretain()
237            bs.timer(
238                tdelay + delay3,
239                bs.WeakCall(
240                    self._safe_animate,
241                    txt_num.position_combine,
242                    'input0',
243                    {
244                        0: ts_h_offs - 95.0 * scale,
245                        transtime2: ts_h_offs - (95.0 + slide_amt) * scale,
246                    },
247                ),
248            )
249
250            s_txt = _scoretxt(
251                str(player.team.sessionteam.customdata['previous_score']),
252                80,
253                0,
254                False,
255                0,
256                1.0,
257            )
258            bs.timer(
259                tdelay + delay2,
260                bs.WeakCall(
261                    self._safe_animate,
262                    s_txt.position_combine,
263                    'input1',
264                    {
265                        0: y_base + (v_offs + 2.0) * scale,
266                        transtime: y_base + (v_offs_2 + 2.0) * scale,
267                    },
268                ),
269            )
270            bs.timer(
271                tdelay + delay3,
272                bs.WeakCall(
273                    self._safe_animate,
274                    s_txt.position_combine,
275                    'input0',
276                    {
277                        0: ts_h_offs + 80.0 * scale,
278                        transtime2: ts_h_offs + (80.0 - slide_amt) * scale,
279                    },
280                ),
281            )
282
283            score_change = (
284                player.team.sessionteam.customdata['score']
285                - player.team.sessionteam.customdata['previous_score']
286            )
287            if score_change > 0:
288                xval = 113
289                yval = 3.0
290                s_txt_2 = _scoretxt(
291                    '+' + str(score_change),
292                    xval,
293                    yval,
294                    True,
295                    0,
296                    0.7,
297                    flash=True,
298                )
299                bs.timer(
300                    tdelay + delay2,
301                    bs.WeakCall(
302                        self._safe_animate,
303                        s_txt_2.position_combine,
304                        'input1',
305                        {
306                            0: y_base + (v_offs + yval + 2.0) * scale,
307                            transtime: y_base + (v_offs_2 + yval + 2.0) * scale,
308                        },
309                    ),
310                )
311                bs.timer(
312                    tdelay + delay3,
313                    bs.WeakCall(
314                        self._safe_animate,
315                        s_txt_2.position_combine,
316                        'input0',
317                        {
318                            0: ts_h_offs + xval * scale,
319                            transtime2: ts_h_offs + (xval - slide_amt) * scale,
320                        },
321                    ),
322                )
323
324                def _safesetattr(
325                    node: bs.Node | None, attr: str, value: Any
326                ) -> None:
327                    if node:
328                        setattr(node, attr, value)
329
330                bs.timer(
331                    tdelay + delay1,
332                    bs.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)),
333                )
334                for j in range(score_change):
335                    bs.timer(
336                        (tdelay + delay1 + 0.15 * j),
337                        bs.Call(
338                            _safesetattr,
339                            s_txt.node,
340                            'text',
341                            str(
342                                player.team.sessionteam.customdata[
343                                    'previous_score'
344                                ]
345                                + j
346                                + 1
347                            ),
348                        ),
349                    )
350                    tfin = tdelay + delay1 + 0.15 * j
351                    if tfin not in sound_times:
352                        sound_times.add(tfin)
353                        bs.timer(tfin, self._score_display_sound_small.play)
354            v_offs -= spacing
355
356    def _safe_animate(
357        self, node: bs.Node | None, attr: str, keys: dict[float, float]
358    ) -> None:
359        """Run an animation on a node if the node still exists."""
360        if node:
361            bs.animate(node, attr, keys)

Score screen shown at after free-for-all rounds.

FreeForAllVictoryScoreScreenActivity(settings: dict)
21    def __init__(self, settings: dict):
22        super().__init__(settings=settings)
23
24        # Keep prev activity alive while we fade in.
25        self.transition_time = 0.5
26        self._cymbal_sound = bs.getsound('cymbal')

Creates an Activity in the current bascenev1.Session.

The activity will not be actually run until bascenev1.Session.setactivity is called. 'settings' should be a dict of key/value pairs specific to the activity.

Activities should preload as much of their media/etc as possible in their constructor, but none of it should actually be used until they are transitioned in.

transition_time = 0.5

If the activity fades or transitions in, it should set the length of time here so that previous activities will be kept alive for that long (avoiding 'holes' in the screen) This value is given in real-time seconds.

@override
def on_begin(self) -> None:
 28    @override
 29    def on_begin(self) -> None:
 30        # pylint: disable=too-many-locals
 31        # pylint: disable=too-many-statements
 32        from bascenev1lib.actor.text import Text
 33        from bascenev1lib.actor.image import Image
 34
 35        bs.set_analytics_screen('FreeForAll Score Screen')
 36        super().on_begin()
 37
 38        y_base = 100.0
 39        ts_h_offs = -305.0
 40        tdelay = 1.0
 41        scale = 1.2
 42        spacing = 37.0
 43
 44        # We include name and previous score in the sort to reduce the amount
 45        # of random jumping around the list we do in cases of ties.
 46        player_order_prev = list(self.players)
 47        player_order_prev.sort(
 48            reverse=True,
 49            key=lambda p: (
 50                p.team.sessionteam.customdata['previous_score'],
 51                p.getname(full=True),
 52            ),
 53        )
 54        player_order = list(self.players)
 55        player_order.sort(
 56            reverse=True,
 57            key=lambda p: (
 58                p.team.sessionteam.customdata['score'],
 59                p.team.sessionteam.customdata['score'],
 60                p.getname(full=True),
 61            ),
 62        )
 63
 64        v_offs = -74.0 + spacing * len(player_order_prev) * 0.5
 65        delay1 = 1.3 + 0.1
 66        delay2 = 2.9 + 0.1
 67        delay3 = 2.9 + 0.1
 68        order_change = player_order != player_order_prev
 69
 70        if order_change:
 71            delay3 += 1.5
 72
 73        bs.timer(0.3, self._score_display_sound.play)
 74        results = self.settings_raw['results']
 75        assert isinstance(results, bs.GameResults)
 76        self.show_player_scores(
 77            delay=0.001, results=results, scale=1.2, x_offset=-110.0
 78        )
 79
 80        sound_times: set[float] = set()
 81
 82        def _scoretxt(
 83            text: str,
 84            x_offs: float,
 85            y_offs: float,
 86            highlight: bool,
 87            delay: float,
 88            extrascale: float,
 89            flash: bool = False,
 90        ) -> Text:
 91            # pylint: disable=too-many-positional-arguments
 92            return Text(
 93                text,
 94                position=(
 95                    ts_h_offs + x_offs * scale,
 96                    y_base + (y_offs + v_offs + 2.0) * scale,
 97                ),
 98                scale=scale * extrascale,
 99                color=(
100                    (1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7)
101                ),
102                h_align=Text.HAlign.RIGHT,
103                transition=Text.Transition.IN_LEFT,
104                transition_delay=tdelay + delay,
105                flash=flash,
106            ).autoretain()
107
108        v_offs -= spacing
109        slide_amt = 0.0
110        transtime = 0.250
111        transtime2 = 0.250
112
113        session = self.session
114        assert isinstance(session, bs.FreeForAllSession)
115        title = Text(
116            bs.Lstr(
117                resource='firstToSeriesText',
118                subs=[('${COUNT}', str(session.get_ffa_series_length()))],
119            ),
120            scale=1.05 * scale,
121            position=(
122                ts_h_offs - 0.0 * scale,
123                y_base + (v_offs + 50.0) * scale,
124            ),
125            h_align=Text.HAlign.CENTER,
126            color=(0.5, 0.5, 0.5, 0.5),
127            transition=Text.Transition.IN_LEFT,
128            transition_delay=tdelay,
129        ).autoretain()
130
131        v_offs -= 25
132        v_offs_start = v_offs
133
134        bs.timer(
135            tdelay + delay3,
136            bs.WeakCall(
137                self._safe_animate,
138                title.position_combine,
139                'input0',
140                {
141                    0.0: ts_h_offs - 0.0 * scale,
142                    transtime2: ts_h_offs - (0.0 + slide_amt) * scale,
143                },
144            ),
145        )
146
147        for i, player in enumerate(player_order_prev):
148            v_offs_2 = v_offs_start - spacing * (player_order.index(player))
149            bs.timer(tdelay + 0.3, self._score_display_sound_small.play)
150            if order_change:
151                bs.timer(tdelay + delay2 + 0.1, self._cymbal_sound.play)
152            img = Image(
153                player.get_icon(),
154                position=(
155                    ts_h_offs - 72.0 * scale,
156                    y_base + (v_offs + 15.0) * scale,
157                ),
158                scale=(30.0 * scale, 30.0 * scale),
159                transition=Image.Transition.IN_LEFT,
160                transition_delay=tdelay,
161            ).autoretain()
162            bs.timer(
163                tdelay + delay2,
164                bs.WeakCall(
165                    self._safe_animate,
166                    img.position_combine,
167                    'input1',
168                    {
169                        0: y_base + (v_offs + 15.0) * scale,
170                        transtime: y_base + (v_offs_2 + 15.0) * scale,
171                    },
172                ),
173            )
174            bs.timer(
175                tdelay + delay3,
176                bs.WeakCall(
177                    self._safe_animate,
178                    img.position_combine,
179                    'input0',
180                    {
181                        0: ts_h_offs - 72.0 * scale,
182                        transtime2: ts_h_offs - (72.0 + slide_amt) * scale,
183                    },
184                ),
185            )
186            txt = Text(
187                bs.Lstr(value=player.getname(full=True)),
188                maxwidth=130.0,
189                scale=0.75 * scale,
190                position=(
191                    ts_h_offs - 50.0 * scale,
192                    y_base + (v_offs + 15.0) * scale,
193                ),
194                h_align=Text.HAlign.LEFT,
195                v_align=Text.VAlign.CENTER,
196                color=bs.safecolor(player.team.color + (1,)),
197                transition=Text.Transition.IN_LEFT,
198                transition_delay=tdelay,
199            ).autoretain()
200            bs.timer(
201                tdelay + delay2,
202                bs.WeakCall(
203                    self._safe_animate,
204                    txt.position_combine,
205                    'input1',
206                    {
207                        0: y_base + (v_offs + 15.0) * scale,
208                        transtime: y_base + (v_offs_2 + 15.0) * scale,
209                    },
210                ),
211            )
212            bs.timer(
213                tdelay + delay3,
214                bs.WeakCall(
215                    self._safe_animate,
216                    txt.position_combine,
217                    'input0',
218                    {
219                        0: ts_h_offs - 50.0 * scale,
220                        transtime2: ts_h_offs - (50.0 + slide_amt) * scale,
221                    },
222                ),
223            )
224
225            txt_num = Text(
226                '#' + str(i + 1),
227                scale=0.55 * scale,
228                position=(
229                    ts_h_offs - 95.0 * scale,
230                    y_base + (v_offs + 8.0) * scale,
231                ),
232                h_align=Text.HAlign.RIGHT,
233                color=(0.6, 0.6, 0.6, 0.6),
234                transition=Text.Transition.IN_LEFT,
235                transition_delay=tdelay,
236            ).autoretain()
237            bs.timer(
238                tdelay + delay3,
239                bs.WeakCall(
240                    self._safe_animate,
241                    txt_num.position_combine,
242                    'input0',
243                    {
244                        0: ts_h_offs - 95.0 * scale,
245                        transtime2: ts_h_offs - (95.0 + slide_amt) * scale,
246                    },
247                ),
248            )
249
250            s_txt = _scoretxt(
251                str(player.team.sessionteam.customdata['previous_score']),
252                80,
253                0,
254                False,
255                0,
256                1.0,
257            )
258            bs.timer(
259                tdelay + delay2,
260                bs.WeakCall(
261                    self._safe_animate,
262                    s_txt.position_combine,
263                    'input1',
264                    {
265                        0: y_base + (v_offs + 2.0) * scale,
266                        transtime: y_base + (v_offs_2 + 2.0) * scale,
267                    },
268                ),
269            )
270            bs.timer(
271                tdelay + delay3,
272                bs.WeakCall(
273                    self._safe_animate,
274                    s_txt.position_combine,
275                    'input0',
276                    {
277                        0: ts_h_offs + 80.0 * scale,
278                        transtime2: ts_h_offs + (80.0 - slide_amt) * scale,
279                    },
280                ),
281            )
282
283            score_change = (
284                player.team.sessionteam.customdata['score']
285                - player.team.sessionteam.customdata['previous_score']
286            )
287            if score_change > 0:
288                xval = 113
289                yval = 3.0
290                s_txt_2 = _scoretxt(
291                    '+' + str(score_change),
292                    xval,
293                    yval,
294                    True,
295                    0,
296                    0.7,
297                    flash=True,
298                )
299                bs.timer(
300                    tdelay + delay2,
301                    bs.WeakCall(
302                        self._safe_animate,
303                        s_txt_2.position_combine,
304                        'input1',
305                        {
306                            0: y_base + (v_offs + yval + 2.0) * scale,
307                            transtime: y_base + (v_offs_2 + yval + 2.0) * scale,
308                        },
309                    ),
310                )
311                bs.timer(
312                    tdelay + delay3,
313                    bs.WeakCall(
314                        self._safe_animate,
315                        s_txt_2.position_combine,
316                        'input0',
317                        {
318                            0: ts_h_offs + xval * scale,
319                            transtime2: ts_h_offs + (xval - slide_amt) * scale,
320                        },
321                    ),
322                )
323
324                def _safesetattr(
325                    node: bs.Node | None, attr: str, value: Any
326                ) -> None:
327                    if node:
328                        setattr(node, attr, value)
329
330                bs.timer(
331                    tdelay + delay1,
332                    bs.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)),
333                )
334                for j in range(score_change):
335                    bs.timer(
336                        (tdelay + delay1 + 0.15 * j),
337                        bs.Call(
338                            _safesetattr,
339                            s_txt.node,
340                            'text',
341                            str(
342                                player.team.sessionteam.customdata[
343                                    'previous_score'
344                                ]
345                                + j
346                                + 1
347                            ),
348                        ),
349                    )
350                    tfin = tdelay + delay1 + 0.15 * j
351                    if tfin not in sound_times:
352                        sound_times.add(tfin)
353                        bs.timer(tfin, self._score_display_sound_small.play)
354            v_offs -= spacing

Called once the previous Activity has finished transitioning out.

At this point the activity's initial players and teams are filled in and it should begin its actual game logic.