bastd.game.meteorshower

Defines a bomb-dodging mini-game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Defines a bomb-dodging mini-game."""
  4
  5# ba_meta require api 7
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import random
 11from typing import TYPE_CHECKING
 12
 13import ba
 14from bastd.actor.bomb import Bomb
 15from bastd.actor.onscreentimer import OnScreenTimer
 16
 17if TYPE_CHECKING:
 18    from typing import Any, Sequence
 19
 20
 21class Player(ba.Player['Team']):
 22    """Our player type for this game."""
 23
 24    def __init__(self) -> None:
 25        super().__init__()
 26        self.death_time: float | None = None
 27
 28
 29class Team(ba.Team[Player]):
 30    """Our team type for this game."""
 31
 32
 33# ba_meta export game
 34class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
 35    """Minigame involving dodging falling bombs."""
 36
 37    name = 'Meteor Shower'
 38    description = 'Dodge the falling bombs.'
 39    available_settings = [ba.BoolSetting('Epic Mode', default=False)]
 40    scoreconfig = ba.ScoreConfig(label='Survived',
 41                                 scoretype=ba.ScoreType.MILLISECONDS,
 42                                 version='B')
 43
 44    # Print messages when players die (since its meaningful in this game).
 45    announce_player_deaths = True
 46
 47    # Don't allow joining after we start
 48    # (would enable leave/rejoin tomfoolery).
 49    allow_mid_activity_joins = False
 50
 51    # We're currently hard-coded for one map.
 52    @classmethod
 53    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 54        return ['Rampage']
 55
 56    # We support teams, free-for-all, and co-op sessions.
 57    @classmethod
 58    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 59        return (issubclass(sessiontype, ba.DualTeamSession)
 60                or issubclass(sessiontype, ba.FreeForAllSession)
 61                or issubclass(sessiontype, ba.CoopSession))
 62
 63    def __init__(self, settings: dict):
 64        super().__init__(settings)
 65
 66        self._epic_mode = settings.get('Epic Mode', False)
 67        self._last_player_death_time: float | None = None
 68        self._meteor_time = 2.0
 69        self._timer: OnScreenTimer | None = None
 70
 71        # Some base class overrides:
 72        self.default_music = (ba.MusicType.EPIC
 73                              if self._epic_mode else ba.MusicType.SURVIVAL)
 74        if self._epic_mode:
 75            self.slow_motion = True
 76
 77    def on_begin(self) -> None:
 78        super().on_begin()
 79
 80        # Drop a wave every few seconds.. and every so often drop the time
 81        # between waves ..lets have things increase faster if we have fewer
 82        # players.
 83        delay = 5.0 if len(self.players) > 2 else 2.5
 84        if self._epic_mode:
 85            delay *= 0.25
 86        ba.timer(delay, self._decrement_meteor_time, repeat=True)
 87
 88        # Kick off the first wave in a few seconds.
 89        delay = 3.0
 90        if self._epic_mode:
 91            delay *= 0.25
 92        ba.timer(delay, self._set_meteor_timer)
 93
 94        self._timer = OnScreenTimer()
 95        self._timer.start()
 96
 97        # Check for immediate end (if we've only got 1 player, etc).
 98        ba.timer(5.0, self._check_end_game)
 99
100    def on_player_leave(self, player: Player) -> None:
101        # Augment default behavior.
102        super().on_player_leave(player)
103
104        # A departing player may trigger game-over.
105        self._check_end_game()
106
107    # overriding the default character spawning..
108    def spawn_player(self, player: Player) -> ba.Actor:
109        spaz = self.spawn_player_spaz(player)
110
111        # Let's reconnect this player's controls to this
112        # spaz but *without* the ability to attack or pick stuff up.
113        spaz.connect_controls_to_player(enable_punch=False,
114                                        enable_bomb=False,
115                                        enable_pickup=False)
116
117        # Also lets have them make some noise when they die.
118        spaz.play_big_death_sound = True
119        return spaz
120
121    # Various high-level game events come through this method.
122    def handlemessage(self, msg: Any) -> Any:
123        if isinstance(msg, ba.PlayerDiedMessage):
124
125            # Augment standard behavior.
126            super().handlemessage(msg)
127
128            curtime = ba.time()
129
130            # Record the player's moment of death.
131            # assert isinstance(msg.spaz.player
132            msg.getplayer(Player).death_time = curtime
133
134            # In co-op mode, end the game the instant everyone dies
135            # (more accurate looking).
136            # In teams/ffa, allow a one-second fudge-factor so we can
137            # get more draws if players die basically at the same time.
138            if isinstance(self.session, ba.CoopSession):
139                # Teams will still show up if we check now.. check in
140                # the next cycle.
141                ba.pushcall(self._check_end_game)
142
143                # Also record this for a final setting of the clock.
144                self._last_player_death_time = curtime
145            else:
146                ba.timer(1.0, self._check_end_game)
147
148        else:
149            # Default handler:
150            return super().handlemessage(msg)
151        return None
152
153    def _check_end_game(self) -> None:
154        living_team_count = 0
155        for team in self.teams:
156            for player in team.players:
157                if player.is_alive():
158                    living_team_count += 1
159                    break
160
161        # In co-op, we go till everyone is dead.. otherwise we go
162        # until one team remains.
163        if isinstance(self.session, ba.CoopSession):
164            if living_team_count <= 0:
165                self.end_game()
166        else:
167            if living_team_count <= 1:
168                self.end_game()
169
170    def _set_meteor_timer(self) -> None:
171        ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
172                 self._drop_bomb_cluster)
173
174    def _drop_bomb_cluster(self) -> None:
175
176        # Random note: code like this is a handy way to plot out extents
177        # and debug things.
178        loc_test = False
179        if loc_test:
180            ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
181            ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
182            ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
183            ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
184
185        # Drop several bombs in series.
186        delay = 0.0
187        for _i in range(random.randrange(1, 3)):
188            # Drop them somewhere within our bounds with velocity pointing
189            # toward the opposite side.
190            pos = (-7.3 + 15.3 * random.random(), 11,
191                   -5.57 + 2.1 * random.random())
192            dropdir = (-1.0 if pos[0] > 0 else 1.0)
193            vel = ((-5.0 + random.random() * 30.0) * dropdir,
194                   random.uniform(-3.066, -4.12), 0)
195            ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
196            delay += 0.1
197        self._set_meteor_timer()
198
199    def _drop_bomb(self, position: Sequence[float],
200                   velocity: Sequence[float]) -> None:
201        Bomb(position=position, velocity=velocity).autoretain()
202
203    def _decrement_meteor_time(self) -> None:
204        self._meteor_time = max(0.01, self._meteor_time * 0.9)
205
206    def end_game(self) -> None:
207        cur_time = ba.time()
208        assert self._timer is not None
209        start_time = self._timer.getstarttime()
210
211        # Mark death-time as now for any still-living players
212        # and award players points for how long they lasted.
213        # (these per-player scores are only meaningful in team-games)
214        for team in self.teams:
215            for player in team.players:
216                survived = False
217
218                # Throw an extra fudge factor in so teams that
219                # didn't die come out ahead of teams that did.
220                if player.death_time is None:
221                    survived = True
222                    player.death_time = cur_time + 1
223
224                # Award a per-player score depending on how many seconds
225                # they lasted (per-player scores only affect teams mode;
226                # everywhere else just looks at the per-team score).
227                score = int(player.death_time - self._timer.getstarttime())
228                if survived:
229                    score += 50  # A bit extra for survivors.
230                self.stats.player_scored(player, score, screenmessage=False)
231
232        # Stop updating our time text, and set the final time to match
233        # exactly when our last guy died.
234        self._timer.stop(endtime=self._last_player_death_time)
235
236        # Ok now calc game results: set a score for each team and then tell
237        # the game to end.
238        results = ba.GameResults()
239
240        # Remember that 'free-for-all' mode is simply a special form
241        # of 'teams' mode where each player gets their own team, so we can
242        # just always deal in teams and have all cases covered.
243        for team in self.teams:
244
245            # Set the team score to the max time survived by any player on
246            # that team.
247            longest_life = 0.0
248            for player in team.players:
249                assert player.death_time is not None
250                longest_life = max(longest_life,
251                                   player.death_time - start_time)
252
253            # Submit the score value in milliseconds.
254            results.set_team_score(team, int(1000.0 * longest_life))
255
256        self.end(results=results)
class Player(ba._player.Player[ForwardRef('Team')]):
22class Player(ba.Player['Team']):
23    """Our player type for this game."""
24
25    def __init__(self) -> None:
26        super().__init__()
27        self.death_time: float | None = None

Our player type for this game.

Player()
25    def __init__(self) -> None:
26        super().__init__()
27        self.death_time: float | None = None
class Team(ba._team.Team[bastd.game.meteorshower.Player]):
30class Team(ba.Team[Player]):
31    """Our team type for this game."""

Our team type for this game.

Team()
Inherited Members
ba._team.Team
manual_init
customdata
on_expire
sessionteam
class MeteorShowerGame(ba._teamgame.TeamGameActivity[bastd.game.meteorshower.Player, bastd.game.meteorshower.Team]):
 35class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
 36    """Minigame involving dodging falling bombs."""
 37
 38    name = 'Meteor Shower'
 39    description = 'Dodge the falling bombs.'
 40    available_settings = [ba.BoolSetting('Epic Mode', default=False)]
 41    scoreconfig = ba.ScoreConfig(label='Survived',
 42                                 scoretype=ba.ScoreType.MILLISECONDS,
 43                                 version='B')
 44
 45    # Print messages when players die (since its meaningful in this game).
 46    announce_player_deaths = True
 47
 48    # Don't allow joining after we start
 49    # (would enable leave/rejoin tomfoolery).
 50    allow_mid_activity_joins = False
 51
 52    # We're currently hard-coded for one map.
 53    @classmethod
 54    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 55        return ['Rampage']
 56
 57    # We support teams, free-for-all, and co-op sessions.
 58    @classmethod
 59    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 60        return (issubclass(sessiontype, ba.DualTeamSession)
 61                or issubclass(sessiontype, ba.FreeForAllSession)
 62                or issubclass(sessiontype, ba.CoopSession))
 63
 64    def __init__(self, settings: dict):
 65        super().__init__(settings)
 66
 67        self._epic_mode = settings.get('Epic Mode', False)
 68        self._last_player_death_time: float | None = None
 69        self._meteor_time = 2.0
 70        self._timer: OnScreenTimer | None = None
 71
 72        # Some base class overrides:
 73        self.default_music = (ba.MusicType.EPIC
 74                              if self._epic_mode else ba.MusicType.SURVIVAL)
 75        if self._epic_mode:
 76            self.slow_motion = True
 77
 78    def on_begin(self) -> None:
 79        super().on_begin()
 80
 81        # Drop a wave every few seconds.. and every so often drop the time
 82        # between waves ..lets have things increase faster if we have fewer
 83        # players.
 84        delay = 5.0 if len(self.players) > 2 else 2.5
 85        if self._epic_mode:
 86            delay *= 0.25
 87        ba.timer(delay, self._decrement_meteor_time, repeat=True)
 88
 89        # Kick off the first wave in a few seconds.
 90        delay = 3.0
 91        if self._epic_mode:
 92            delay *= 0.25
 93        ba.timer(delay, self._set_meteor_timer)
 94
 95        self._timer = OnScreenTimer()
 96        self._timer.start()
 97
 98        # Check for immediate end (if we've only got 1 player, etc).
 99        ba.timer(5.0, self._check_end_game)
100
101    def on_player_leave(self, player: Player) -> None:
102        # Augment default behavior.
103        super().on_player_leave(player)
104
105        # A departing player may trigger game-over.
106        self._check_end_game()
107
108    # overriding the default character spawning..
109    def spawn_player(self, player: Player) -> ba.Actor:
110        spaz = self.spawn_player_spaz(player)
111
112        # Let's reconnect this player's controls to this
113        # spaz but *without* the ability to attack or pick stuff up.
114        spaz.connect_controls_to_player(enable_punch=False,
115                                        enable_bomb=False,
116                                        enable_pickup=False)
117
118        # Also lets have them make some noise when they die.
119        spaz.play_big_death_sound = True
120        return spaz
121
122    # Various high-level game events come through this method.
123    def handlemessage(self, msg: Any) -> Any:
124        if isinstance(msg, ba.PlayerDiedMessage):
125
126            # Augment standard behavior.
127            super().handlemessage(msg)
128
129            curtime = ba.time()
130
131            # Record the player's moment of death.
132            # assert isinstance(msg.spaz.player
133            msg.getplayer(Player).death_time = curtime
134
135            # In co-op mode, end the game the instant everyone dies
136            # (more accurate looking).
137            # In teams/ffa, allow a one-second fudge-factor so we can
138            # get more draws if players die basically at the same time.
139            if isinstance(self.session, ba.CoopSession):
140                # Teams will still show up if we check now.. check in
141                # the next cycle.
142                ba.pushcall(self._check_end_game)
143
144                # Also record this for a final setting of the clock.
145                self._last_player_death_time = curtime
146            else:
147                ba.timer(1.0, self._check_end_game)
148
149        else:
150            # Default handler:
151            return super().handlemessage(msg)
152        return None
153
154    def _check_end_game(self) -> None:
155        living_team_count = 0
156        for team in self.teams:
157            for player in team.players:
158                if player.is_alive():
159                    living_team_count += 1
160                    break
161
162        # In co-op, we go till everyone is dead.. otherwise we go
163        # until one team remains.
164        if isinstance(self.session, ba.CoopSession):
165            if living_team_count <= 0:
166                self.end_game()
167        else:
168            if living_team_count <= 1:
169                self.end_game()
170
171    def _set_meteor_timer(self) -> None:
172        ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
173                 self._drop_bomb_cluster)
174
175    def _drop_bomb_cluster(self) -> None:
176
177        # Random note: code like this is a handy way to plot out extents
178        # and debug things.
179        loc_test = False
180        if loc_test:
181            ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
182            ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
183            ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
184            ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
185
186        # Drop several bombs in series.
187        delay = 0.0
188        for _i in range(random.randrange(1, 3)):
189            # Drop them somewhere within our bounds with velocity pointing
190            # toward the opposite side.
191            pos = (-7.3 + 15.3 * random.random(), 11,
192                   -5.57 + 2.1 * random.random())
193            dropdir = (-1.0 if pos[0] > 0 else 1.0)
194            vel = ((-5.0 + random.random() * 30.0) * dropdir,
195                   random.uniform(-3.066, -4.12), 0)
196            ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
197            delay += 0.1
198        self._set_meteor_timer()
199
200    def _drop_bomb(self, position: Sequence[float],
201                   velocity: Sequence[float]) -> None:
202        Bomb(position=position, velocity=velocity).autoretain()
203
204    def _decrement_meteor_time(self) -> None:
205        self._meteor_time = max(0.01, self._meteor_time * 0.9)
206
207    def end_game(self) -> None:
208        cur_time = ba.time()
209        assert self._timer is not None
210        start_time = self._timer.getstarttime()
211
212        # Mark death-time as now for any still-living players
213        # and award players points for how long they lasted.
214        # (these per-player scores are only meaningful in team-games)
215        for team in self.teams:
216            for player in team.players:
217                survived = False
218
219                # Throw an extra fudge factor in so teams that
220                # didn't die come out ahead of teams that did.
221                if player.death_time is None:
222                    survived = True
223                    player.death_time = cur_time + 1
224
225                # Award a per-player score depending on how many seconds
226                # they lasted (per-player scores only affect teams mode;
227                # everywhere else just looks at the per-team score).
228                score = int(player.death_time - self._timer.getstarttime())
229                if survived:
230                    score += 50  # A bit extra for survivors.
231                self.stats.player_scored(player, score, screenmessage=False)
232
233        # Stop updating our time text, and set the final time to match
234        # exactly when our last guy died.
235        self._timer.stop(endtime=self._last_player_death_time)
236
237        # Ok now calc game results: set a score for each team and then tell
238        # the game to end.
239        results = ba.GameResults()
240
241        # Remember that 'free-for-all' mode is simply a special form
242        # of 'teams' mode where each player gets their own team, so we can
243        # just always deal in teams and have all cases covered.
244        for team in self.teams:
245
246            # Set the team score to the max time survived by any player on
247            # that team.
248            longest_life = 0.0
249            for player in team.players:
250                assert player.death_time is not None
251                longest_life = max(longest_life,
252                                   player.death_time - start_time)
253
254            # Submit the score value in milliseconds.
255            results.set_team_score(team, int(1000.0 * longest_life))
256
257        self.end(results=results)

Minigame involving dodging falling bombs.

MeteorShowerGame(settings: dict)
64    def __init__(self, settings: dict):
65        super().__init__(settings)
66
67        self._epic_mode = settings.get('Epic Mode', False)
68        self._last_player_death_time: float | None = None
69        self._meteor_time = 2.0
70        self._timer: OnScreenTimer | None = None
71
72        # Some base class overrides:
73        self.default_music = (ba.MusicType.EPIC
74                              if self._epic_mode else ba.MusicType.SURVIVAL)
75        if self._epic_mode:
76            self.slow_motion = True

Instantiate the Activity.

name: str | None = 'Meteor Shower'
description: str | None = 'Dodge the falling bombs.'
available_settings: list[ba._settings.Setting] | None = [BoolSetting(name='Epic Mode', default=False)]
scoreconfig: ba._score.ScoreConfig | None = ScoreConfig(label='Survived', scoretype=<ScoreType.MILLISECONDS: 'ms'>, lower_is_better=False, none_is_winner=False, version='B')
announce_player_deaths = True

Whether to print every time a player dies. This can be pertinent in games such as Death-Match but can be annoying in games where it doesn't matter.

allow_mid_activity_joins: bool = False

Whether players should be allowed to join in the middle of this activity. Note that Sessions may not allow mid-activity-joins even if the activity says its ok.

@classmethod
def get_supported_maps(cls, sessiontype: type[ba._session.Session]) -> list[str]:
53    @classmethod
54    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
55        return ['Rampage']

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.

@classmethod
def supports_session_type(cls, sessiontype: type[ba._session.Session]) -> bool:
58    @classmethod
59    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
60        return (issubclass(sessiontype, ba.DualTeamSession)
61                or issubclass(sessiontype, ba.FreeForAllSession)
62                or issubclass(sessiontype, ba.CoopSession))

Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.

def on_begin(self) -> None:
78    def on_begin(self) -> None:
79        super().on_begin()
80
81        # Drop a wave every few seconds.. and every so often drop the time
82        # between waves ..lets have things increase faster if we have fewer
83        # players.
84        delay = 5.0 if len(self.players) > 2 else 2.5
85        if self._epic_mode:
86            delay *= 0.25
87        ba.timer(delay, self._decrement_meteor_time, repeat=True)
88
89        # Kick off the first wave in a few seconds.
90        delay = 3.0
91        if self._epic_mode:
92            delay *= 0.25
93        ba.timer(delay, self._set_meteor_timer)
94
95        self._timer = OnScreenTimer()
96        self._timer.start()
97
98        # Check for immediate end (if we've only got 1 player, etc).
99        ba.timer(5.0, self._check_end_game)

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 on_player_leave(self, player: bastd.game.meteorshower.Player) -> None:
101    def on_player_leave(self, player: Player) -> None:
102        # Augment default behavior.
103        super().on_player_leave(player)
104
105        # A departing player may trigger game-over.
106        self._check_end_game()

Called when a ba.Player is leaving the Activity.

def spawn_player(self, player: bastd.game.meteorshower.Player) -> ba._actor.Actor:
109    def spawn_player(self, player: Player) -> ba.Actor:
110        spaz = self.spawn_player_spaz(player)
111
112        # Let's reconnect this player's controls to this
113        # spaz but *without* the ability to attack or pick stuff up.
114        spaz.connect_controls_to_player(enable_punch=False,
115                                        enable_bomb=False,
116                                        enable_pickup=False)
117
118        # Also lets have them make some noise when they die.
119        spaz.play_big_death_sound = True
120        return spaz

Spawn something for the provided ba.Player.

The default implementation simply calls spawn_player_spaz().

def handlemessage(self, msg: Any) -> Any:
123    def handlemessage(self, msg: Any) -> Any:
124        if isinstance(msg, ba.PlayerDiedMessage):
125
126            # Augment standard behavior.
127            super().handlemessage(msg)
128
129            curtime = ba.time()
130
131            # Record the player's moment of death.
132            # assert isinstance(msg.spaz.player
133            msg.getplayer(Player).death_time = curtime
134
135            # In co-op mode, end the game the instant everyone dies
136            # (more accurate looking).
137            # In teams/ffa, allow a one-second fudge-factor so we can
138            # get more draws if players die basically at the same time.
139            if isinstance(self.session, ba.CoopSession):
140                # Teams will still show up if we check now.. check in
141                # the next cycle.
142                ba.pushcall(self._check_end_game)
143
144                # Also record this for a final setting of the clock.
145                self._last_player_death_time = curtime
146            else:
147                ba.timer(1.0, self._check_end_game)
148
149        else:
150            # Default handler:
151            return super().handlemessage(msg)
152        return None

General message handling; can be passed any message object.

def end_game(self) -> None:
207    def end_game(self) -> None:
208        cur_time = ba.time()
209        assert self._timer is not None
210        start_time = self._timer.getstarttime()
211
212        # Mark death-time as now for any still-living players
213        # and award players points for how long they lasted.
214        # (these per-player scores are only meaningful in team-games)
215        for team in self.teams:
216            for player in team.players:
217                survived = False
218
219                # Throw an extra fudge factor in so teams that
220                # didn't die come out ahead of teams that did.
221                if player.death_time is None:
222                    survived = True
223                    player.death_time = cur_time + 1
224
225                # Award a per-player score depending on how many seconds
226                # they lasted (per-player scores only affect teams mode;
227                # everywhere else just looks at the per-team score).
228                score = int(player.death_time - self._timer.getstarttime())
229                if survived:
230                    score += 50  # A bit extra for survivors.
231                self.stats.player_scored(player, score, screenmessage=False)
232
233        # Stop updating our time text, and set the final time to match
234        # exactly when our last guy died.
235        self._timer.stop(endtime=self._last_player_death_time)
236
237        # Ok now calc game results: set a score for each team and then tell
238        # the game to end.
239        results = ba.GameResults()
240
241        # Remember that 'free-for-all' mode is simply a special form
242        # of 'teams' mode where each player gets their own team, so we can
243        # just always deal in teams and have all cases covered.
244        for team in self.teams:
245
246            # Set the team score to the max time survived by any player on
247            # that team.
248            longest_life = 0.0
249            for player in team.players:
250                assert player.death_time is not None
251                longest_life = max(longest_life,
252                                   player.death_time - start_time)
253
254            # Submit the score value in milliseconds.
255            results.set_team_score(team, int(1000.0 * longest_life))
256
257        self.end(results=results)

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.

Inherited Members
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
get_instance_description_short
on_continue
is_waiting_for_continue
continue_or_end_game
on_player_join
respawn_player
spawn_player_if_exists
setup_standard_powerup_drops
setup_standard_time_limit
show_zoom_message
ba._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
ba._activity.Activity
settings_raw
teams
players
is_joining_activity
use_fixed_vr_overlay
slow_motion
inherits_slow_motion
inherits_music
inherits_vr_camera_offset
inherits_vr_overlay_center
inherits_tint
transition_time
can_show_ad_on_death
globalsnode
stats
on_expire
customdata
expired
playertype
teamtype
retain_actor
add_actor_weak_ref
session
on_team_join
on_team_leave
on_transition_out
has_transitioned_in
has_begun
has_ended
is_transitioning_out
transition_out
create_player
create_team
ba._dependency.DependencyComponent
dep_is_present
get_dynamic_deps