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

Our player type for this game.

chosen_light: bascenev1._nodeactor.NodeActor | None
Inherited Members
bascenev1._player.Player
character
actor
color
highlight
on_expire
team
customdata
sessionplayer
node
position
exists
getname
is_alive
get_icon
assigninput
resetinput
class Team(bascenev1._team.Team[bascenev1lib.game.chosenone.Player]):
31class Team(bs.Team[Player]):
32    """Our team type for this game."""
33
34    def __init__(self, time_remaining: int) -> None:
35        self.time_remaining = time_remaining

Our team type for this game.

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

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

Instantiate the Activity.

name = 'Chosen One'
description = 'Be the chosen one for a length of time to win.\nKill the chosen one to become it.'
available_settings = [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 = 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[bascenev1._session.Session]) -> list[str]:
87    @classmethod
88    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
89        assert bs.app.classic is not None
90        return bs.app.classic.getmaps('keep_away')

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

slow_motion = False

If True, runs in slow motion and turns down sound pitch.

default_music = None
def get_instance_description(self) -> Union[str, Sequence]:
125    def get_instance_description(self) -> str | Sequence:
126        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: bascenev1._team.SessionTeam) -> Team:
128    def create_team(self, sessionteam: bs.SessionTeam) -> Team:
129        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: Team) -> None:
131    def on_team_join(self, team: Team) -> None:
132        self._update_scoreboard()

Called when a new bascenev1.Team joins the Activity.

(including the initial set of Teams)

def on_player_leave(self, player: Player) -> None:
134    def on_player_leave(self, player: Player) -> None:
135        super().on_player_leave(player)
136        if self._get_chosen_one_player() is player:
137            self._set_chosen_one_player(None)

Called when a bascenev1.Player is leaving the Activity.

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

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.

def end_game(self) -> None:
255    def end_game(self) -> None:
256        results = bs.GameResults()
257        for team in self.teams:
258            results.set_team_score(
259                team, self._chosen_one_time - team.time_remaining
260            )
261        self.end(results=results, announce_delay=0)

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

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 (bascenev1.GameActivity.setup_standard_time_limit()) will work with the game.

def handlemessage(self, msg: Any) -> Any:
339    def handlemessage(self, msg: Any) -> Any:
340        if isinstance(msg, bs.PlayerDiedMessage):
341            # Augment standard behavior.
342            super().handlemessage(msg)
343            player = msg.getplayer(Player)
344            if player is self._get_chosen_one_player():
345                killerplayer = msg.getkillerplayer(Player)
346                self._set_chosen_one_player(
347                    None
348                    if (
349                        killerplayer is None
350                        or killerplayer is player
351                        or not killerplayer.is_alive()
352                    )
353                    else killerplayer
354                )
355            self.respawn_player(player)
356        else:
357            super().handlemessage(msg)

General message handling; can be passed any message object.

Inherited Members
bascenev1._teamgame.TeamGameActivity
supports_session_type
on_transition_in
spawn_player_spaz
end
bascenev1._gameactivity.GameActivity
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
initialplayerinfos
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
bascenev1._activity.Activity
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
paused_text
preloads
lobby
context
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
bascenev1._dependency.DependencyComponent
dep_is_present
get_dynamic_deps