bastd.game.kingofthehill

Defines the King of the Hill game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Defines the King of the Hill game."""
  4
  5# ba_meta require api 7
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import weakref
 11from enum import Enum
 12from typing import TYPE_CHECKING
 13
 14import ba
 15from bastd.actor.flag import Flag
 16from bastd.actor.playerspaz import PlayerSpaz
 17from bastd.actor.scoreboard import Scoreboard
 18from bastd.gameutils import SharedObjects
 19
 20if TYPE_CHECKING:
 21    from typing import Any, Sequence
 22
 23
 24class FlagState(Enum):
 25    """States our single flag can be in."""
 26    NEW = 0
 27    UNCONTESTED = 1
 28    CONTESTED = 2
 29    HELD = 3
 30
 31
 32class Player(ba.Player['Team']):
 33    """Our player type for this game."""
 34
 35    def __init__(self) -> None:
 36        self.time_at_flag = 0
 37
 38
 39class Team(ba.Team[Player]):
 40    """Our team type for this game."""
 41
 42    def __init__(self, time_remaining: int) -> None:
 43        self.time_remaining = time_remaining
 44
 45
 46# ba_meta export game
 47class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
 48    """Game where a team wins by holding a 'hill' for a set amount of time."""
 49
 50    name = 'King of the Hill'
 51    description = 'Secure the flag for a set length of time.'
 52    available_settings = [
 53        ba.IntSetting(
 54            'Hold Time',
 55            min_value=10,
 56            default=30,
 57            increment=10,
 58        ),
 59        ba.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        ba.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    ]
 83    scoreconfig = ba.ScoreConfig(label='Time Held')
 84
 85    @classmethod
 86    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 87        return issubclass(sessiontype, ba.MultiTeamSession)
 88
 89    @classmethod
 90    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 91        return ba.getmaps('king_of_the_hill')
 92
 93    def __init__(self, settings: dict):
 94        super().__init__(settings)
 95        shared = SharedObjects.get()
 96        self._scoreboard = Scoreboard()
 97        self._swipsound = ba.getsound('swip')
 98        self._tick_sound = ba.getsound('tick')
 99        self._countdownsounds = {
100            10: ba.getsound('announceTen'),
101            9: ba.getsound('announceNine'),
102            8: ba.getsound('announceEight'),
103            7: ba.getsound('announceSeven'),
104            6: ba.getsound('announceSix'),
105            5: ba.getsound('announceFive'),
106            4: ba.getsound('announceFour'),
107            3: ba.getsound('announceThree'),
108            2: ba.getsound('announceTwo'),
109            1: ba.getsound('announceOne')
110        }
111        self._flag_pos: Sequence[float] | None = None
112        self._flag_state: FlagState | None = None
113        self._flag: Flag | None = None
114        self._flag_light: ba.Node | None = None
115        self._scoring_team: weakref.ref[Team] | None = None
116        self._hold_time = int(settings['Hold Time'])
117        self._time_limit = float(settings['Time Limit'])
118        self._flag_region_material = ba.Material()
119        self._flag_region_material.add_actions(
120            conditions=('they_have_material', shared.player_material),
121            actions=(
122                ('modify_part_collision', 'collide', True),
123                ('modify_part_collision', 'physical', False),
124                ('call', 'at_connect',
125                 ba.Call(self._handle_player_flag_region_collide, True)),
126                ('call', 'at_disconnect',
127                 ba.Call(self._handle_player_flag_region_collide, False)),
128            ))
129
130        # Base class overrides.
131        self.default_music = ba.MusicType.SCARY
132
133    def get_instance_description(self) -> str | Sequence:
134        return 'Secure the flag for ${ARG1} seconds.', self._hold_time
135
136    def get_instance_description_short(self) -> str | Sequence:
137        return 'secure the flag for ${ARG1} seconds', self._hold_time
138
139    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
140        return Team(time_remaining=self._hold_time)
141
142    def on_begin(self) -> None:
143        super().on_begin()
144        shared = SharedObjects.get()
145        self.setup_standard_time_limit(self._time_limit)
146        self.setup_standard_powerup_drops()
147        self._flag_pos = self.map.get_flag_position(None)
148        ba.timer(1.0, self._tick, repeat=True)
149        self._flag_state = FlagState.NEW
150        Flag.project_stand(self._flag_pos)
151        self._flag = Flag(position=self._flag_pos,
152                          touchable=False,
153                          color=(1, 1, 1))
154        self._flag_light = ba.newnode('light',
155                                      attrs={
156                                          'position': self._flag_pos,
157                                          'intensity': 0.2,
158                                          'height_attenuated': False,
159                                          'radius': 0.4,
160                                          'color': (0.2, 0.2, 0.2)
161                                      })
162        # Flag region.
163        flagmats = [self._flag_region_material, shared.region_material]
164        ba.newnode('region',
165                   attrs={
166                       'position': self._flag_pos,
167                       'scale': (1.8, 1.8, 1.8),
168                       'type': 'sphere',
169                       'materials': flagmats
170                   })
171        self._update_flag_state()
172
173    def _tick(self) -> None:
174        self._update_flag_state()
175
176        # Give holding players points.
177        for player in self.players:
178            if player.time_at_flag > 0:
179                self.stats.player_scored(player,
180                                         3,
181                                         screenmessage=False,
182                                         display=False)
183        if self._scoring_team is None:
184            scoring_team = None
185        else:
186            scoring_team = self._scoring_team()
187        if scoring_team:
188
189            if scoring_team.time_remaining > 0:
190                ba.playsound(self._tick_sound)
191
192            scoring_team.time_remaining = max(0,
193                                              scoring_team.time_remaining - 1)
194            self._update_scoreboard()
195            if scoring_team.time_remaining > 0:
196                assert self._flag is not None
197                self._flag.set_score_text(str(scoring_team.time_remaining))
198
199            # Announce numbers we have sounds for.
200            numsound = self._countdownsounds.get(scoring_team.time_remaining)
201            if numsound is not None:
202                ba.playsound(numsound)
203
204            # winner
205            if scoring_team.time_remaining <= 0:
206                self.end_game()
207
208    def end_game(self) -> None:
209        results = ba.GameResults()
210        for team in self.teams:
211            results.set_team_score(team, self._hold_time - team.time_remaining)
212        self.end(results=results, announce_delay=0)
213
214    def _update_flag_state(self) -> None:
215        holding_teams = set(player.team for player in self.players
216                            if player.time_at_flag)
217        prev_state = self._flag_state
218        assert self._flag_light
219        assert self._flag is not None
220        assert self._flag.node
221        if len(holding_teams) > 1:
222            self._flag_state = FlagState.CONTESTED
223            self._scoring_team = None
224            self._flag_light.color = (0.6, 0.6, 0.1)
225            self._flag.node.color = (1.0, 1.0, 0.4)
226        elif len(holding_teams) == 1:
227            holding_team = list(holding_teams)[0]
228            self._flag_state = FlagState.HELD
229            self._scoring_team = weakref.ref(holding_team)
230            self._flag_light.color = ba.normalized_color(holding_team.color)
231            self._flag.node.color = holding_team.color
232        else:
233            self._flag_state = FlagState.UNCONTESTED
234            self._scoring_team = None
235            self._flag_light.color = (0.2, 0.2, 0.2)
236            self._flag.node.color = (1, 1, 1)
237        if self._flag_state != prev_state:
238            ba.playsound(self._swipsound)
239
240    def _handle_player_flag_region_collide(self, colliding: bool) -> None:
241        try:
242            spaz = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
243        except ba.NotFoundError:
244            return
245
246        if not spaz.is_alive():
247            return
248
249        player = spaz.getplayer(Player, True)
250
251        # Different parts of us can collide so a single value isn't enough
252        # also don't count it if we're dead (flying heads shouldn't be able to
253        # win the game :-)
254        if colliding and player.is_alive():
255            player.time_at_flag += 1
256        else:
257            player.time_at_flag = max(0, player.time_at_flag - 1)
258
259        self._update_flag_state()
260
261    def _update_scoreboard(self) -> None:
262        for team in self.teams:
263            self._scoreboard.set_team_value(team,
264                                            team.time_remaining,
265                                            self._hold_time,
266                                            countdown=True)
267
268    def handlemessage(self, msg: Any) -> Any:
269        if isinstance(msg, ba.PlayerDiedMessage):
270            super().handlemessage(msg)  # Augment default.
271
272            # No longer can count as time_at_flag once dead.
273            player = msg.getplayer(Player)
274            player.time_at_flag = 0
275            self._update_flag_state()
276            self.respawn_player(player)
class FlagState(enum.Enum):
25class FlagState(Enum):
26    """States our single flag can be in."""
27    NEW = 0
28    UNCONTESTED = 1
29    CONTESTED = 2
30    HELD = 3

States our single flag can be in.

NEW = <FlagState.NEW: 0>
UNCONTESTED = <FlagState.UNCONTESTED: 1>
CONTESTED = <FlagState.CONTESTED: 2>
HELD = <FlagState.HELD: 3>
Inherited Members
enum.Enum
name
value
class Player(ba._player.Player[ForwardRef('Team')]):
33class Player(ba.Player['Team']):
34    """Our player type for this game."""
35
36    def __init__(self) -> None:
37        self.time_at_flag = 0

Our player type for this game.

Player()
36    def __init__(self) -> None:
37        self.time_at_flag = 0
class Team(ba._team.Team[bastd.game.kingofthehill.Player]):
40class Team(ba.Team[Player]):
41    """Our team type for this game."""
42
43    def __init__(self, time_remaining: int) -> None:
44        self.time_remaining = time_remaining

Our team type for this game.

Team(time_remaining: int)
43    def __init__(self, time_remaining: int) -> None:
44        self.time_remaining = time_remaining
Inherited Members
ba._team.Team
manual_init
customdata
on_expire
sessionteam
class KingOfTheHillGame(ba._teamgame.TeamGameActivity[bastd.game.kingofthehill.Player, bastd.game.kingofthehill.Team]):
 48class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
 49    """Game where a team wins by holding a 'hill' for a set amount of time."""
 50
 51    name = 'King of the Hill'
 52    description = 'Secure the flag for a set length of time.'
 53    available_settings = [
 54        ba.IntSetting(
 55            'Hold Time',
 56            min_value=10,
 57            default=30,
 58            increment=10,
 59        ),
 60        ba.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        ba.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    ]
 84    scoreconfig = ba.ScoreConfig(label='Time Held')
 85
 86    @classmethod
 87    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 88        return issubclass(sessiontype, ba.MultiTeamSession)
 89
 90    @classmethod
 91    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 92        return ba.getmaps('king_of_the_hill')
 93
 94    def __init__(self, settings: dict):
 95        super().__init__(settings)
 96        shared = SharedObjects.get()
 97        self._scoreboard = Scoreboard()
 98        self._swipsound = ba.getsound('swip')
 99        self._tick_sound = ba.getsound('tick')
100        self._countdownsounds = {
101            10: ba.getsound('announceTen'),
102            9: ba.getsound('announceNine'),
103            8: ba.getsound('announceEight'),
104            7: ba.getsound('announceSeven'),
105            6: ba.getsound('announceSix'),
106            5: ba.getsound('announceFive'),
107            4: ba.getsound('announceFour'),
108            3: ba.getsound('announceThree'),
109            2: ba.getsound('announceTwo'),
110            1: ba.getsound('announceOne')
111        }
112        self._flag_pos: Sequence[float] | None = None
113        self._flag_state: FlagState | None = None
114        self._flag: Flag | None = None
115        self._flag_light: ba.Node | None = None
116        self._scoring_team: weakref.ref[Team] | None = None
117        self._hold_time = int(settings['Hold Time'])
118        self._time_limit = float(settings['Time Limit'])
119        self._flag_region_material = ba.Material()
120        self._flag_region_material.add_actions(
121            conditions=('they_have_material', shared.player_material),
122            actions=(
123                ('modify_part_collision', 'collide', True),
124                ('modify_part_collision', 'physical', False),
125                ('call', 'at_connect',
126                 ba.Call(self._handle_player_flag_region_collide, True)),
127                ('call', 'at_disconnect',
128                 ba.Call(self._handle_player_flag_region_collide, False)),
129            ))
130
131        # Base class overrides.
132        self.default_music = ba.MusicType.SCARY
133
134    def get_instance_description(self) -> str | Sequence:
135        return 'Secure the flag for ${ARG1} seconds.', self._hold_time
136
137    def get_instance_description_short(self) -> str | Sequence:
138        return 'secure the flag for ${ARG1} seconds', self._hold_time
139
140    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
141        return Team(time_remaining=self._hold_time)
142
143    def on_begin(self) -> None:
144        super().on_begin()
145        shared = SharedObjects.get()
146        self.setup_standard_time_limit(self._time_limit)
147        self.setup_standard_powerup_drops()
148        self._flag_pos = self.map.get_flag_position(None)
149        ba.timer(1.0, self._tick, repeat=True)
150        self._flag_state = FlagState.NEW
151        Flag.project_stand(self._flag_pos)
152        self._flag = Flag(position=self._flag_pos,
153                          touchable=False,
154                          color=(1, 1, 1))
155        self._flag_light = ba.newnode('light',
156                                      attrs={
157                                          'position': self._flag_pos,
158                                          'intensity': 0.2,
159                                          'height_attenuated': False,
160                                          'radius': 0.4,
161                                          'color': (0.2, 0.2, 0.2)
162                                      })
163        # Flag region.
164        flagmats = [self._flag_region_material, shared.region_material]
165        ba.newnode('region',
166                   attrs={
167                       'position': self._flag_pos,
168                       'scale': (1.8, 1.8, 1.8),
169                       'type': 'sphere',
170                       'materials': flagmats
171                   })
172        self._update_flag_state()
173
174    def _tick(self) -> None:
175        self._update_flag_state()
176
177        # Give holding players points.
178        for player in self.players:
179            if player.time_at_flag > 0:
180                self.stats.player_scored(player,
181                                         3,
182                                         screenmessage=False,
183                                         display=False)
184        if self._scoring_team is None:
185            scoring_team = None
186        else:
187            scoring_team = self._scoring_team()
188        if scoring_team:
189
190            if scoring_team.time_remaining > 0:
191                ba.playsound(self._tick_sound)
192
193            scoring_team.time_remaining = max(0,
194                                              scoring_team.time_remaining - 1)
195            self._update_scoreboard()
196            if scoring_team.time_remaining > 0:
197                assert self._flag is not None
198                self._flag.set_score_text(str(scoring_team.time_remaining))
199
200            # Announce numbers we have sounds for.
201            numsound = self._countdownsounds.get(scoring_team.time_remaining)
202            if numsound is not None:
203                ba.playsound(numsound)
204
205            # winner
206            if scoring_team.time_remaining <= 0:
207                self.end_game()
208
209    def end_game(self) -> None:
210        results = ba.GameResults()
211        for team in self.teams:
212            results.set_team_score(team, self._hold_time - team.time_remaining)
213        self.end(results=results, announce_delay=0)
214
215    def _update_flag_state(self) -> None:
216        holding_teams = set(player.team for player in self.players
217                            if player.time_at_flag)
218        prev_state = self._flag_state
219        assert self._flag_light
220        assert self._flag is not None
221        assert self._flag.node
222        if len(holding_teams) > 1:
223            self._flag_state = FlagState.CONTESTED
224            self._scoring_team = None
225            self._flag_light.color = (0.6, 0.6, 0.1)
226            self._flag.node.color = (1.0, 1.0, 0.4)
227        elif len(holding_teams) == 1:
228            holding_team = list(holding_teams)[0]
229            self._flag_state = FlagState.HELD
230            self._scoring_team = weakref.ref(holding_team)
231            self._flag_light.color = ba.normalized_color(holding_team.color)
232            self._flag.node.color = holding_team.color
233        else:
234            self._flag_state = FlagState.UNCONTESTED
235            self._scoring_team = None
236            self._flag_light.color = (0.2, 0.2, 0.2)
237            self._flag.node.color = (1, 1, 1)
238        if self._flag_state != prev_state:
239            ba.playsound(self._swipsound)
240
241    def _handle_player_flag_region_collide(self, colliding: bool) -> None:
242        try:
243            spaz = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
244        except ba.NotFoundError:
245            return
246
247        if not spaz.is_alive():
248            return
249
250        player = spaz.getplayer(Player, True)
251
252        # Different parts of us can collide so a single value isn't enough
253        # also don't count it if we're dead (flying heads shouldn't be able to
254        # win the game :-)
255        if colliding and player.is_alive():
256            player.time_at_flag += 1
257        else:
258            player.time_at_flag = max(0, player.time_at_flag - 1)
259
260        self._update_flag_state()
261
262    def _update_scoreboard(self) -> None:
263        for team in self.teams:
264            self._scoreboard.set_team_value(team,
265                                            team.time_remaining,
266                                            self._hold_time,
267                                            countdown=True)
268
269    def handlemessage(self, msg: Any) -> Any:
270        if isinstance(msg, ba.PlayerDiedMessage):
271            super().handlemessage(msg)  # Augment default.
272
273            # No longer can count as time_at_flag once dead.
274            player = msg.getplayer(Player)
275            player.time_at_flag = 0
276            self._update_flag_state()
277            self.respawn_player(player)

Game where a team wins by holding a 'hill' for a set amount of time.

KingOfTheHillGame(settings: dict)
 94    def __init__(self, settings: dict):
 95        super().__init__(settings)
 96        shared = SharedObjects.get()
 97        self._scoreboard = Scoreboard()
 98        self._swipsound = ba.getsound('swip')
 99        self._tick_sound = ba.getsound('tick')
100        self._countdownsounds = {
101            10: ba.getsound('announceTen'),
102            9: ba.getsound('announceNine'),
103            8: ba.getsound('announceEight'),
104            7: ba.getsound('announceSeven'),
105            6: ba.getsound('announceSix'),
106            5: ba.getsound('announceFive'),
107            4: ba.getsound('announceFour'),
108            3: ba.getsound('announceThree'),
109            2: ba.getsound('announceTwo'),
110            1: ba.getsound('announceOne')
111        }
112        self._flag_pos: Sequence[float] | None = None
113        self._flag_state: FlagState | None = None
114        self._flag: Flag | None = None
115        self._flag_light: ba.Node | None = None
116        self._scoring_team: weakref.ref[Team] | None = None
117        self._hold_time = int(settings['Hold Time'])
118        self._time_limit = float(settings['Time Limit'])
119        self._flag_region_material = ba.Material()
120        self._flag_region_material.add_actions(
121            conditions=('they_have_material', shared.player_material),
122            actions=(
123                ('modify_part_collision', 'collide', True),
124                ('modify_part_collision', 'physical', False),
125                ('call', 'at_connect',
126                 ba.Call(self._handle_player_flag_region_collide, True)),
127                ('call', 'at_disconnect',
128                 ba.Call(self._handle_player_flag_region_collide, False)),
129            ))
130
131        # Base class overrides.
132        self.default_music = ba.MusicType.SCARY

Instantiate the Activity.

name: str | None = 'King of the Hill'
description: str | None = 'Secure the flag for a set length of time.'
available_settings: list[ba._settings.Setting] | None = [IntSetting(name='Hold Time', default=30, min_value=10, max_value=9999, increment=10), 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)])]
scoreconfig: ba._score.ScoreConfig | None = ScoreConfig(label='Time Held', scoretype=<ScoreType.POINTS: 'p'>, lower_is_better=False, none_is_winner=False, version='')
@classmethod
def supports_session_type(cls, sessiontype: type[ba._session.Session]) -> bool:
86    @classmethod
87    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
88        return issubclass(sessiontype, ba.MultiTeamSession)

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

@classmethod
def get_supported_maps(cls, sessiontype: type[ba._session.Session]) -> list[str]:
90    @classmethod
91    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
92        return ba.getmaps('king_of_the_hill')

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]:
134    def get_instance_description(self) -> str | Sequence:
135        return 'Secure the flag for ${ARG1} seconds.', self._hold_time

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 get_instance_description_short(self) -> Union[str, Sequence]:
137    def get_instance_description_short(self) -> str | Sequence:
138        return 'secure the flag for ${ARG1} seconds', self._hold_time

Return a short description for this game instance in English.

This description is used above the game scoreboard in the corner of the screen, so it should be as concise as possible. It should be lowercase and should not contain periods or other punctuation.

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.kingofthehill.Team:
140    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
141        return Team(time_remaining=self._hold_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_begin(self) -> None:
143    def on_begin(self) -> None:
144        super().on_begin()
145        shared = SharedObjects.get()
146        self.setup_standard_time_limit(self._time_limit)
147        self.setup_standard_powerup_drops()
148        self._flag_pos = self.map.get_flag_position(None)
149        ba.timer(1.0, self._tick, repeat=True)
150        self._flag_state = FlagState.NEW
151        Flag.project_stand(self._flag_pos)
152        self._flag = Flag(position=self._flag_pos,
153                          touchable=False,
154                          color=(1, 1, 1))
155        self._flag_light = ba.newnode('light',
156                                      attrs={
157                                          'position': self._flag_pos,
158                                          'intensity': 0.2,
159                                          'height_attenuated': False,
160                                          'radius': 0.4,
161                                          'color': (0.2, 0.2, 0.2)
162                                      })
163        # Flag region.
164        flagmats = [self._flag_region_material, shared.region_material]
165        ba.newnode('region',
166                   attrs={
167                       'position': self._flag_pos,
168                       'scale': (1.8, 1.8, 1.8),
169                       'type': 'sphere',
170                       'materials': flagmats
171                   })
172        self._update_flag_state()

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:
209    def end_game(self) -> None:
210        results = ba.GameResults()
211        for team in self.teams:
212            results.set_team_score(team, self._hold_time - team.time_remaining)
213        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:
269    def handlemessage(self, msg: Any) -> Any:
270        if isinstance(msg, ba.PlayerDiedMessage):
271            super().handlemessage(msg)  # Augment default.
272
273            # No longer can count as time_at_flag once dead.
274            player = msg.getplayer(Player)
275            player.time_at_flag = 0
276            self._update_flag_state()
277            self.respawn_player(player)

General message handling; can be passed any message object.

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
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
on_transition_in
spawn_player_spaz
end
ba._activity.Activity
settings_raw
teams
players
announce_player_deaths
is_joining_activity
use_fixed_vr_overlay
slow_motion
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_player_leave
on_team_join
on_team_leave
on_transition_out
has_transitioned_in
has_begun
has_ended
is_transitioning_out
transition_out
create_player
ba._dependency.DependencyComponent
dep_is_present
get_dynamic_deps