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

Our player type for this game.

Player()
37    def __init__(self) -> None:
38        self.time_at_flag = 0
Inherited Members
ba._player.Player
actor
on_expire
team
customdata
sessionplayer
node
position
exists
getname
is_alive
get_icon
assigninput
resetinput
class Team(ba._team.Team[bastd.game.kingofthehill.Player]):
41class Team(ba.Team[Player]):
42    """Our team type for this game."""
43
44    def __init__(self, time_remaining: int) -> None:
45        self.time_remaining = time_remaining

Our team type for this game.

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

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

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

Instantiate the Activity.

@classmethod
def supports_session_type(cls, sessiontype: type[ba._session.Session]) -> bool:
88    @classmethod
89    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
90        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]:
92    @classmethod
93    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
94        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.

slow_motion = False

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

def get_instance_description(self) -> Union[str, Sequence]:
147    def get_instance_description(self) -> str | Sequence:
148        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]:
150    def get_instance_description_short(self) -> str | Sequence:
151        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:
153    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
154        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:
156    def on_begin(self) -> None:
157        super().on_begin()
158        shared = SharedObjects.get()
159        self.setup_standard_time_limit(self._time_limit)
160        self.setup_standard_powerup_drops()
161        self._flag_pos = self.map.get_flag_position(None)
162        ba.timer(1.0, self._tick, repeat=True)
163        self._flag_state = FlagState.NEW
164        Flag.project_stand(self._flag_pos)
165        self._flag = Flag(
166            position=self._flag_pos, touchable=False, color=(1, 1, 1)
167        )
168        self._flag_light = ba.newnode(
169            'light',
170            attrs={
171                'position': self._flag_pos,
172                'intensity': 0.2,
173                'height_attenuated': False,
174                'radius': 0.4,
175                'color': (0.2, 0.2, 0.2),
176            },
177        )
178        # Flag region.
179        flagmats = [self._flag_region_material, shared.region_material]
180        ba.newnode(
181            'region',
182            attrs={
183                'position': self._flag_pos,
184                'scale': (1.8, 1.8, 1.8),
185                'type': 'sphere',
186                'materials': flagmats,
187            },
188        )
189        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:
226    def end_game(self) -> None:
227        results = ba.GameResults()
228        for team in self.teams:
229            results.set_team_score(team, self._hold_time - team.time_remaining)
230        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:
286    def handlemessage(self, msg: Any) -> Any:
287        if isinstance(msg, ba.PlayerDiedMessage):
288            super().handlemessage(msg)  # Augment default.
289
290            # No longer can count as time_at_flag once dead.
291            player = msg.getplayer(Player)
292            player.time_at_flag = 0
293            self._update_flag_state()
294            self.respawn_player(player)

General message handling; can be passed any message object.

Inherited Members
ba._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
ba._gameactivity.GameActivity
allow_pausing
allow_kick_idle_players
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._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
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