bastd.game.chosenone

Provides the chosen-one mini-game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides the chosen-one mini-game."""
  4
  5# ba_meta require api 7
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10from typing import TYPE_CHECKING
 11
 12import ba
 13from bastd.actor.flag import Flag
 14from bastd.actor.playerspaz import PlayerSpaz
 15from bastd.actor.scoreboard import Scoreboard
 16from bastd.gameutils import SharedObjects
 17
 18if TYPE_CHECKING:
 19    from typing import Any, Sequence
 20
 21
 22class Player(ba.Player['Team']):
 23    """Our player type for this game."""
 24
 25    def __init__(self) -> None:
 26        self.chosen_light: ba.NodeActor | None = None
 27
 28
 29class Team(ba.Team[Player]):
 30    """Our team type for this game."""
 31
 32    def __init__(self, time_remaining: int) -> None:
 33        self.time_remaining = time_remaining
 34
 35
 36# ba_meta export game
 37class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
 38    """
 39    Game involving trying to remain the one 'chosen one'
 40    for a set length of time while everyone else tries to
 41    kill you and become the chosen one themselves.
 42    """
 43
 44    name = 'Chosen One'
 45    description = ('Be the chosen one for a length of time to win.\n'
 46                   'Kill the chosen one to become it.')
 47    available_settings = [
 48        ba.IntSetting(
 49            'Chosen One Time',
 50            min_value=10,
 51            default=30,
 52            increment=10,
 53        ),
 54        ba.BoolSetting('Chosen One Gets Gloves', default=True),
 55        ba.BoolSetting('Chosen One Gets Shield', default=False),
 56        ba.IntChoiceSetting(
 57            'Time Limit',
 58            choices=[
 59                ('None', 0),
 60                ('1 Minute', 60),
 61                ('2 Minutes', 120),
 62                ('5 Minutes', 300),
 63                ('10 Minutes', 600),
 64                ('20 Minutes', 1200),
 65            ],
 66            default=0,
 67        ),
 68        ba.FloatChoiceSetting(
 69            'Respawn Times',
 70            choices=[
 71                ('Shorter', 0.25),
 72                ('Short', 0.5),
 73                ('Normal', 1.0),
 74                ('Long', 2.0),
 75                ('Longer', 4.0),
 76            ],
 77            default=1.0,
 78        ),
 79        ba.BoolSetting('Epic Mode', default=False),
 80    ]
 81    scoreconfig = ba.ScoreConfig(label='Time Held')
 82
 83    @classmethod
 84    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 85        return ba.getmaps('keep_away')
 86
 87    def __init__(self, settings: dict):
 88        super().__init__(settings)
 89        self._scoreboard = Scoreboard()
 90        self._chosen_one_player: Player | None = None
 91        self._swipsound = ba.getsound('swip')
 92        self._countdownsounds: dict[int, ba.Sound] = {
 93            10: ba.getsound('announceTen'),
 94            9: ba.getsound('announceNine'),
 95            8: ba.getsound('announceEight'),
 96            7: ba.getsound('announceSeven'),
 97            6: ba.getsound('announceSix'),
 98            5: ba.getsound('announceFive'),
 99            4: ba.getsound('announceFour'),
100            3: ba.getsound('announceThree'),
101            2: ba.getsound('announceTwo'),
102            1: ba.getsound('announceOne')
103        }
104        self._flag_spawn_pos: Sequence[float] | None = None
105        self._reset_region_material: ba.Material | None = None
106        self._flag: Flag | None = None
107        self._reset_region: ba.Node | None = None
108        self._epic_mode = bool(settings['Epic Mode'])
109        self._chosen_one_time = int(settings['Chosen One Time'])
110        self._time_limit = float(settings['Time Limit'])
111        self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield'])
112        self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves'])
113
114        # Base class overrides
115        self.slow_motion = self._epic_mode
116        self.default_music = (ba.MusicType.EPIC
117                              if self._epic_mode else ba.MusicType.CHOSEN_ONE)
118
119    def get_instance_description(self) -> str | Sequence:
120        return 'There can be only one.'
121
122    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
123        return Team(time_remaining=self._chosen_one_time)
124
125    def on_team_join(self, team: Team) -> None:
126        self._update_scoreboard()
127
128    def on_player_leave(self, player: Player) -> None:
129        super().on_player_leave(player)
130        if self._get_chosen_one_player() is player:
131            self._set_chosen_one_player(None)
132
133    def on_begin(self) -> None:
134        super().on_begin()
135        shared = SharedObjects.get()
136        self.setup_standard_time_limit(self._time_limit)
137        self.setup_standard_powerup_drops()
138        self._flag_spawn_pos = self.map.get_flag_position(None)
139        Flag.project_stand(self._flag_spawn_pos)
140        ba.timer(1.0, call=self._tick, repeat=True)
141
142        mat = self._reset_region_material = ba.Material()
143        mat.add_actions(
144            conditions=(
145                'they_have_material',
146                shared.player_material,
147            ),
148            actions=(
149                ('modify_part_collision', 'collide', True),
150                ('modify_part_collision', 'physical', False),
151                ('call', 'at_connect',
152                 ba.WeakCall(self._handle_reset_collide)),
153            ),
154        )
155
156        self._set_chosen_one_player(None)
157
158    def _create_reset_region(self) -> None:
159        assert self._reset_region_material is not None
160        assert self._flag_spawn_pos is not None
161        pos = self._flag_spawn_pos
162        self._reset_region = ba.newnode(
163            'region',
164            attrs={
165                'position': (pos[0], pos[1] + 0.75, pos[2]),
166                'scale': (0.5, 0.5, 0.5),
167                'type': 'sphere',
168                'materials': [self._reset_region_material]
169            })
170
171    def _get_chosen_one_player(self) -> Player | None:
172        # Should never return invalid references; return None in that case.
173        if self._chosen_one_player:
174            return self._chosen_one_player
175        return None
176
177    def _handle_reset_collide(self) -> None:
178        # If we have a chosen one, ignore these.
179        if self._get_chosen_one_player() is not None:
180            return
181
182        # Attempt to get a Actor that we hit.
183        try:
184            spaz = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
185            player = spaz.getplayer(Player, True)
186        except ba.NotFoundError:
187            return
188
189        if spaz.is_alive():
190            self._set_chosen_one_player(player)
191
192    def _flash_flag_spawn(self) -> None:
193        light = ba.newnode('light',
194                           attrs={
195                               'position': self._flag_spawn_pos,
196                               'color': (1, 1, 1),
197                               'radius': 0.3,
198                               'height_attenuated': False
199                           })
200        ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
201        ba.timer(1.0, light.delete)
202
203    def _tick(self) -> None:
204
205        # Give the chosen one points.
206        player = self._get_chosen_one_player()
207        if player is not None:
208
209            # This shouldn't happen, but just in case.
210            if not player.is_alive():
211                ba.print_error('got dead player as chosen one in _tick')
212                self._set_chosen_one_player(None)
213            else:
214                scoring_team = player.team
215                assert self.stats
216                self.stats.player_scored(player,
217                                         3,
218                                         screenmessage=False,
219                                         display=False)
220
221                scoring_team.time_remaining = max(
222                    0, scoring_team.time_remaining - 1)
223
224                # Show the count over their head
225                if scoring_team.time_remaining > 0:
226                    if isinstance(player.actor, PlayerSpaz) and player.actor:
227                        player.actor.set_score_text(
228                            str(scoring_team.time_remaining))
229
230                self._update_scoreboard()
231
232                # announce numbers we have sounds for
233                if scoring_team.time_remaining in self._countdownsounds:
234                    ba.playsound(
235                        self._countdownsounds[scoring_team.time_remaining])
236
237                # Winner!
238                if scoring_team.time_remaining <= 0:
239                    self.end_game()
240
241        else:
242            # (player is None)
243            # This shouldn't happen, but just in case.
244            # (Chosen-one player ceasing to exist should
245            # trigger on_player_leave which resets chosen-one)
246            if self._chosen_one_player is not None:
247                ba.print_error('got nonexistent player as chosen one in _tick')
248                self._set_chosen_one_player(None)
249
250    def end_game(self) -> None:
251        results = ba.GameResults()
252        for team in self.teams:
253            results.set_team_score(team,
254                                   self._chosen_one_time - team.time_remaining)
255        self.end(results=results, announce_delay=0)
256
257    def _set_chosen_one_player(self, player: Player | None) -> None:
258        existing = self._get_chosen_one_player()
259        if existing:
260            existing.chosen_light = None
261        ba.playsound(self._swipsound)
262        if not player:
263            assert self._flag_spawn_pos is not None
264            self._flag = Flag(color=(1, 0.9, 0.2),
265                              position=self._flag_spawn_pos,
266                              touchable=False)
267            self._chosen_one_player = None
268
269            # Create a light to highlight the flag;
270            # this will go away when the flag dies.
271            ba.newnode('light',
272                       owner=self._flag.node,
273                       attrs={
274                           'position': self._flag_spawn_pos,
275                           'intensity': 0.6,
276                           'height_attenuated': False,
277                           'volume_intensity_scale': 0.1,
278                           'radius': 0.1,
279                           'color': (1.2, 1.2, 0.4)
280                       })
281
282            # Also an extra momentary flash.
283            self._flash_flag_spawn()
284
285            # Re-create our flag region in case if someone is waiting for
286            # flag right there:
287            self._create_reset_region()
288        else:
289            if player.actor:
290                self._flag = None
291                self._chosen_one_player = player
292
293                if self._chosen_one_gets_shield:
294                    player.actor.handlemessage(ba.PowerupMessage('shield'))
295                if self._chosen_one_gets_gloves:
296                    player.actor.handlemessage(ba.PowerupMessage('punch'))
297
298                # Use a color that's partway between their team color
299                # and white.
300                color = [
301                    0.3 + c * 0.7
302                    for c in ba.normalized_color(player.team.color)
303                ]
304                light = player.chosen_light = ba.NodeActor(
305                    ba.newnode('light',
306                               attrs={
307                                   'intensity': 0.6,
308                                   'height_attenuated': False,
309                                   'volume_intensity_scale': 0.1,
310                                   'radius': 0.13,
311                                   'color': color
312                               }))
313
314                assert light.node
315                ba.animate(light.node,
316                           'intensity', {
317                               0: 1.0,
318                               0.2: 0.4,
319                               0.4: 1.0
320                           },
321                           loop=True)
322                assert isinstance(player.actor, PlayerSpaz)
323                player.actor.node.connectattr('position', light.node,
324                                              'position')
325
326    def handlemessage(self, msg: Any) -> Any:
327        if isinstance(msg, ba.PlayerDiedMessage):
328            # Augment standard behavior.
329            super().handlemessage(msg)
330            player = msg.getplayer(Player)
331            if player is self._get_chosen_one_player():
332                killerplayer = msg.getkillerplayer(Player)
333                self._set_chosen_one_player(None if (
334                    killerplayer is None or killerplayer is player
335                    or not killerplayer.is_alive()) else killerplayer)
336            self.respawn_player(player)
337        else:
338            super().handlemessage(msg)
339
340    def _update_scoreboard(self) -> None:
341        for team in self.teams:
342            self._scoreboard.set_team_value(team,
343                                            team.time_remaining,
344                                            self._chosen_one_time,
345                                            countdown=True)
class Player(ba._player.Player[ForwardRef('Team')]):
23class Player(ba.Player['Team']):
24    """Our player type for this game."""
25
26    def __init__(self) -> None:
27        self.chosen_light: ba.NodeActor | None = None

Our player type for this game.

Player()
26    def __init__(self) -> None:
27        self.chosen_light: ba.NodeActor | None = None
class Team(ba._team.Team[bastd.game.chosenone.Player]):
30class Team(ba.Team[Player]):
31    """Our team type for this game."""
32
33    def __init__(self, time_remaining: int) -> None:
34        self.time_remaining = time_remaining

Our team type for this game.

Team(time_remaining: int)
33    def __init__(self, time_remaining: int) -> None:
34        self.time_remaining = time_remaining
Inherited Members
ba._team.Team
manual_init
customdata
on_expire
sessionteam
class ChosenOneGame(ba._teamgame.TeamGameActivity[bastd.game.chosenone.Player, bastd.game.chosenone.Team]):
 38class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
 39    """
 40    Game involving trying to remain the one 'chosen one'
 41    for a set length of time while everyone else tries to
 42    kill you and become the chosen one themselves.
 43    """
 44
 45    name = 'Chosen One'
 46    description = ('Be the chosen one for a length of time to win.\n'
 47                   'Kill the chosen one to become it.')
 48    available_settings = [
 49        ba.IntSetting(
 50            'Chosen One Time',
 51            min_value=10,
 52            default=30,
 53            increment=10,
 54        ),
 55        ba.BoolSetting('Chosen One Gets Gloves', default=True),
 56        ba.BoolSetting('Chosen One Gets Shield', default=False),
 57        ba.IntChoiceSetting(
 58            'Time Limit',
 59            choices=[
 60                ('None', 0),
 61                ('1 Minute', 60),
 62                ('2 Minutes', 120),
 63                ('5 Minutes', 300),
 64                ('10 Minutes', 600),
 65                ('20 Minutes', 1200),
 66            ],
 67            default=0,
 68        ),
 69        ba.FloatChoiceSetting(
 70            'Respawn Times',
 71            choices=[
 72                ('Shorter', 0.25),
 73                ('Short', 0.5),
 74                ('Normal', 1.0),
 75                ('Long', 2.0),
 76                ('Longer', 4.0),
 77            ],
 78            default=1.0,
 79        ),
 80        ba.BoolSetting('Epic Mode', default=False),
 81    ]
 82    scoreconfig = ba.ScoreConfig(label='Time Held')
 83
 84    @classmethod
 85    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 86        return ba.getmaps('keep_away')
 87
 88    def __init__(self, settings: dict):
 89        super().__init__(settings)
 90        self._scoreboard = Scoreboard()
 91        self._chosen_one_player: Player | None = None
 92        self._swipsound = ba.getsound('swip')
 93        self._countdownsounds: dict[int, ba.Sound] = {
 94            10: ba.getsound('announceTen'),
 95            9: ba.getsound('announceNine'),
 96            8: ba.getsound('announceEight'),
 97            7: ba.getsound('announceSeven'),
 98            6: ba.getsound('announceSix'),
 99            5: ba.getsound('announceFive'),
100            4: ba.getsound('announceFour'),
101            3: ba.getsound('announceThree'),
102            2: ba.getsound('announceTwo'),
103            1: ba.getsound('announceOne')
104        }
105        self._flag_spawn_pos: Sequence[float] | None = None
106        self._reset_region_material: ba.Material | None = None
107        self._flag: Flag | None = None
108        self._reset_region: ba.Node | None = None
109        self._epic_mode = bool(settings['Epic Mode'])
110        self._chosen_one_time = int(settings['Chosen One Time'])
111        self._time_limit = float(settings['Time Limit'])
112        self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield'])
113        self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves'])
114
115        # Base class overrides
116        self.slow_motion = self._epic_mode
117        self.default_music = (ba.MusicType.EPIC
118                              if self._epic_mode else ba.MusicType.CHOSEN_ONE)
119
120    def get_instance_description(self) -> str | Sequence:
121        return 'There can be only one.'
122
123    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
124        return Team(time_remaining=self._chosen_one_time)
125
126    def on_team_join(self, team: Team) -> None:
127        self._update_scoreboard()
128
129    def on_player_leave(self, player: Player) -> None:
130        super().on_player_leave(player)
131        if self._get_chosen_one_player() is player:
132            self._set_chosen_one_player(None)
133
134    def on_begin(self) -> None:
135        super().on_begin()
136        shared = SharedObjects.get()
137        self.setup_standard_time_limit(self._time_limit)
138        self.setup_standard_powerup_drops()
139        self._flag_spawn_pos = self.map.get_flag_position(None)
140        Flag.project_stand(self._flag_spawn_pos)
141        ba.timer(1.0, call=self._tick, repeat=True)
142
143        mat = self._reset_region_material = ba.Material()
144        mat.add_actions(
145            conditions=(
146                'they_have_material',
147                shared.player_material,
148            ),
149            actions=(
150                ('modify_part_collision', 'collide', True),
151                ('modify_part_collision', 'physical', False),
152                ('call', 'at_connect',
153                 ba.WeakCall(self._handle_reset_collide)),
154            ),
155        )
156
157        self._set_chosen_one_player(None)
158
159    def _create_reset_region(self) -> None:
160        assert self._reset_region_material is not None
161        assert self._flag_spawn_pos is not None
162        pos = self._flag_spawn_pos
163        self._reset_region = ba.newnode(
164            'region',
165            attrs={
166                'position': (pos[0], pos[1] + 0.75, pos[2]),
167                'scale': (0.5, 0.5, 0.5),
168                'type': 'sphere',
169                'materials': [self._reset_region_material]
170            })
171
172    def _get_chosen_one_player(self) -> Player | None:
173        # Should never return invalid references; return None in that case.
174        if self._chosen_one_player:
175            return self._chosen_one_player
176        return None
177
178    def _handle_reset_collide(self) -> None:
179        # If we have a chosen one, ignore these.
180        if self._get_chosen_one_player() is not None:
181            return
182
183        # Attempt to get a Actor that we hit.
184        try:
185            spaz = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
186            player = spaz.getplayer(Player, True)
187        except ba.NotFoundError:
188            return
189
190        if spaz.is_alive():
191            self._set_chosen_one_player(player)
192
193    def _flash_flag_spawn(self) -> None:
194        light = ba.newnode('light',
195                           attrs={
196                               'position': self._flag_spawn_pos,
197                               'color': (1, 1, 1),
198                               'radius': 0.3,
199                               'height_attenuated': False
200                           })
201        ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
202        ba.timer(1.0, light.delete)
203
204    def _tick(self) -> None:
205
206        # Give the chosen one points.
207        player = self._get_chosen_one_player()
208        if player is not None:
209
210            # This shouldn't happen, but just in case.
211            if not player.is_alive():
212                ba.print_error('got dead player as chosen one in _tick')
213                self._set_chosen_one_player(None)
214            else:
215                scoring_team = player.team
216                assert self.stats
217                self.stats.player_scored(player,
218                                         3,
219                                         screenmessage=False,
220                                         display=False)
221
222                scoring_team.time_remaining = max(
223                    0, scoring_team.time_remaining - 1)
224
225                # Show the count over their head
226                if scoring_team.time_remaining > 0:
227                    if isinstance(player.actor, PlayerSpaz) and player.actor:
228                        player.actor.set_score_text(
229                            str(scoring_team.time_remaining))
230
231                self._update_scoreboard()
232
233                # announce numbers we have sounds for
234                if scoring_team.time_remaining in self._countdownsounds:
235                    ba.playsound(
236                        self._countdownsounds[scoring_team.time_remaining])
237
238                # Winner!
239                if scoring_team.time_remaining <= 0:
240                    self.end_game()
241
242        else:
243            # (player is None)
244            # This shouldn't happen, but just in case.
245            # (Chosen-one player ceasing to exist should
246            # trigger on_player_leave which resets chosen-one)
247            if self._chosen_one_player is not None:
248                ba.print_error('got nonexistent player as chosen one in _tick')
249                self._set_chosen_one_player(None)
250
251    def end_game(self) -> None:
252        results = ba.GameResults()
253        for team in self.teams:
254            results.set_team_score(team,
255                                   self._chosen_one_time - team.time_remaining)
256        self.end(results=results, announce_delay=0)
257
258    def _set_chosen_one_player(self, player: Player | None) -> None:
259        existing = self._get_chosen_one_player()
260        if existing:
261            existing.chosen_light = None
262        ba.playsound(self._swipsound)
263        if not player:
264            assert self._flag_spawn_pos is not None
265            self._flag = Flag(color=(1, 0.9, 0.2),
266                              position=self._flag_spawn_pos,
267                              touchable=False)
268            self._chosen_one_player = None
269
270            # Create a light to highlight the flag;
271            # this will go away when the flag dies.
272            ba.newnode('light',
273                       owner=self._flag.node,
274                       attrs={
275                           'position': self._flag_spawn_pos,
276                           'intensity': 0.6,
277                           'height_attenuated': False,
278                           'volume_intensity_scale': 0.1,
279                           'radius': 0.1,
280                           'color': (1.2, 1.2, 0.4)
281                       })
282
283            # Also an extra momentary flash.
284            self._flash_flag_spawn()
285
286            # Re-create our flag region in case if someone is waiting for
287            # flag right there:
288            self._create_reset_region()
289        else:
290            if player.actor:
291                self._flag = None
292                self._chosen_one_player = player
293
294                if self._chosen_one_gets_shield:
295                    player.actor.handlemessage(ba.PowerupMessage('shield'))
296                if self._chosen_one_gets_gloves:
297                    player.actor.handlemessage(ba.PowerupMessage('punch'))
298
299                # Use a color that's partway between their team color
300                # and white.
301                color = [
302                    0.3 + c * 0.7
303                    for c in ba.normalized_color(player.team.color)
304                ]
305                light = player.chosen_light = ba.NodeActor(
306                    ba.newnode('light',
307                               attrs={
308                                   'intensity': 0.6,
309                                   'height_attenuated': False,
310                                   'volume_intensity_scale': 0.1,
311                                   'radius': 0.13,
312                                   'color': color
313                               }))
314
315                assert light.node
316                ba.animate(light.node,
317                           'intensity', {
318                               0: 1.0,
319                               0.2: 0.4,
320                               0.4: 1.0
321                           },
322                           loop=True)
323                assert isinstance(player.actor, PlayerSpaz)
324                player.actor.node.connectattr('position', light.node,
325                                              'position')
326
327    def handlemessage(self, msg: Any) -> Any:
328        if isinstance(msg, ba.PlayerDiedMessage):
329            # Augment standard behavior.
330            super().handlemessage(msg)
331            player = msg.getplayer(Player)
332            if player is self._get_chosen_one_player():
333                killerplayer = msg.getkillerplayer(Player)
334                self._set_chosen_one_player(None if (
335                    killerplayer is None or killerplayer is player
336                    or not killerplayer.is_alive()) else killerplayer)
337            self.respawn_player(player)
338        else:
339            super().handlemessage(msg)
340
341    def _update_scoreboard(self) -> None:
342        for team in self.teams:
343            self._scoreboard.set_team_value(team,
344                                            team.time_remaining,
345                                            self._chosen_one_time,
346                                            countdown=True)

Game involving trying to remain the one 'chosen one' for a set length of time while everyone else tries to kill you and become the chosen one themselves.

ChosenOneGame(settings: dict)
 88    def __init__(self, settings: dict):
 89        super().__init__(settings)
 90        self._scoreboard = Scoreboard()
 91        self._chosen_one_player: Player | None = None
 92        self._swipsound = ba.getsound('swip')
 93        self._countdownsounds: dict[int, ba.Sound] = {
 94            10: ba.getsound('announceTen'),
 95            9: ba.getsound('announceNine'),
 96            8: ba.getsound('announceEight'),
 97            7: ba.getsound('announceSeven'),
 98            6: ba.getsound('announceSix'),
 99            5: ba.getsound('announceFive'),
100            4: ba.getsound('announceFour'),
101            3: ba.getsound('announceThree'),
102            2: ba.getsound('announceTwo'),
103            1: ba.getsound('announceOne')
104        }
105        self._flag_spawn_pos: Sequence[float] | None = None
106        self._reset_region_material: ba.Material | None = None
107        self._flag: Flag | None = None
108        self._reset_region: ba.Node | None = None
109        self._epic_mode = bool(settings['Epic Mode'])
110        self._chosen_one_time = int(settings['Chosen One Time'])
111        self._time_limit = float(settings['Time Limit'])
112        self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield'])
113        self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves'])
114
115        # Base class overrides
116        self.slow_motion = self._epic_mode
117        self.default_music = (ba.MusicType.EPIC
118                              if self._epic_mode else ba.MusicType.CHOSEN_ONE)

Instantiate the Activity.

name: str | None = 'Chosen One'
description: str | None = 'Be the chosen one for a length of time to win.\nKill the chosen one to become it.'
available_settings: list[ba._settings.Setting] | None = [IntSetting(name='Chosen One Time', default=30, min_value=10, max_value=9999, increment=10), BoolSetting(name='Chosen One Gets Gloves', default=True), BoolSetting(name='Chosen One Gets Shield', default=False), IntChoiceSetting(name='Time Limit', default=0, choices=[('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300), ('10 Minutes', 600), ('20 Minutes', 1200)]), FloatChoiceSetting(name='Respawn Times', default=1.0, choices=[('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0)]), BoolSetting(name='Epic Mode', default=False)]
scoreconfig: ba._score.ScoreConfig | None = ScoreConfig(label='Time Held', scoretype=<ScoreType.POINTS: 'p'>, lower_is_better=False, none_is_winner=False, version='')
@classmethod
def get_supported_maps(cls, sessiontype: type[ba._session.Session]) -> list[str]:
84    @classmethod
85    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
86        return ba.getmaps('keep_away')

Called by the default ba.GameActivity.create_settings_ui() implementation; should return a list of map names valid for this game-type for the given ba.Session type.

def get_instance_description(self) -> Union[str, Sequence]:
120    def get_instance_description(self) -> str | Sequence:
121        return 'There can be only one.'

Return a description for this game instance, in English.

This is shown in the center of the screen below the game name at the start of a game. It should start with a capital letter and end with a period, and can be a bit more verbose than the version returned by get_instance_description_short().

Note that translation is applied by looking up the specific returned value as a key, so the number of returned variations should be limited; ideally just one or two. To include arbitrary values in the description, you can return a sequence of values in the following form instead of just a string:

This will give us something like 'Score 3 goals.' in English

and can properly translate to 'Anota 3 goles.' in Spanish.

If we just returned the string 'Score 3 Goals' here, there would

have to be a translation entry for each specific number. ew.

return ['Score ${ARG1} goals.', self.settings_raw['Score to Win']]

This way the first string can be consistently translated, with any arg values then substituted into the result. ${ARG1} will be replaced with the first value, ${ARG2} with the second, etc.

def create_team(self, sessionteam: ba._team.SessionTeam) -> bastd.game.chosenone.Team:
123    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
124        return Team(time_remaining=self._chosen_one_time)

Create the Team instance for this Activity.

Subclasses can override this if the activity's team class requires a custom constructor; otherwise it will be called with no args. Note that the team object should not be used at this point as it is not yet fully wired up; wait for on_team_join() for that.

def on_team_join(self, team: bastd.game.chosenone.Team) -> None:
126    def on_team_join(self, team: Team) -> None:
127        self._update_scoreboard()

Called when a new ba.Team joins the Activity.

(including the initial set of Teams)

def on_player_leave(self, player: bastd.game.chosenone.Player) -> None:
129    def on_player_leave(self, player: Player) -> None:
130        super().on_player_leave(player)
131        if self._get_chosen_one_player() is player:
132            self._set_chosen_one_player(None)

Called when a ba.Player is leaving the Activity.

def on_begin(self) -> None:
134    def on_begin(self) -> None:
135        super().on_begin()
136        shared = SharedObjects.get()
137        self.setup_standard_time_limit(self._time_limit)
138        self.setup_standard_powerup_drops()
139        self._flag_spawn_pos = self.map.get_flag_position(None)
140        Flag.project_stand(self._flag_spawn_pos)
141        ba.timer(1.0, call=self._tick, repeat=True)
142
143        mat = self._reset_region_material = ba.Material()
144        mat.add_actions(
145            conditions=(
146                'they_have_material',
147                shared.player_material,
148            ),
149            actions=(
150                ('modify_part_collision', 'collide', True),
151                ('modify_part_collision', 'physical', False),
152                ('call', 'at_connect',
153                 ba.WeakCall(self._handle_reset_collide)),
154            ),
155        )
156
157        self._set_chosen_one_player(None)

Called once the previous ba.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.

def end_game(self) -> None:
251    def end_game(self) -> None:
252        results = ba.GameResults()
253        for team in self.teams:
254            results.set_team_score(team,
255                                   self._chosen_one_time - team.time_remaining)
256        self.end(results=results, announce_delay=0)

Tell the game to wrap up and call ba.Activity.end() immediately.

This method should be overridden by subclasses. A game should always be prepared to end and deliver results, even if there is no 'winner' yet; this way things like the standard time-limit (ba.GameActivity.setup_standard_time_limit()) will work with the game.

def handlemessage(self, msg: Any) -> Any:
327    def handlemessage(self, msg: Any) -> Any:
328        if isinstance(msg, ba.PlayerDiedMessage):
329            # Augment standard behavior.
330            super().handlemessage(msg)
331            player = msg.getplayer(Player)
332            if player is self._get_chosen_one_player():
333                killerplayer = msg.getkillerplayer(Player)
334                self._set_chosen_one_player(None if (
335                    killerplayer is None or killerplayer is player
336                    or not killerplayer.is_alive()) else killerplayer)
337            self.respawn_player(player)
338        else:
339            super().handlemessage(msg)

General message handling; can be passed any message object.

Inherited Members
ba._activity.Activity
slow_motion
settings_raw
teams
players
announce_player_deaths
is_joining_activity
use_fixed_vr_overlay
inherits_slow_motion
inherits_music
inherits_vr_camera_offset
inherits_vr_overlay_center
inherits_tint
allow_mid_activity_joins
transition_time
can_show_ad_on_death
globalsnode
stats
on_expire
customdata
expired
playertype
teamtype
retain_actor
add_actor_weak_ref
session
on_team_leave
on_transition_out
has_transitioned_in
has_begun
has_ended
is_transitioning_out
transition_out
create_player
ba._gameactivity.GameActivity
default_music
tips
allow_pausing
allow_kick_idle_players
show_kill_points
create_settings_ui
getscoreconfig
getname
get_display_string
get_team_display_string
get_description
get_description_display_string
get_available_settings
get_settings_display_string
map
get_instance_display_string
get_instance_scoreboard_display_string
get_instance_description_short
on_continue
is_waiting_for_continue
continue_or_end_game
on_player_join
respawn_player
spawn_player_if_exists
spawn_player
setup_standard_powerup_drops
setup_standard_time_limit
show_zoom_message
ba._teamgame.TeamGameActivity
supports_session_type
on_transition_in
spawn_player_spaz
end
ba._dependency.DependencyComponent
dep_is_present
get_dynamic_deps