bascenev1lib.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 8
  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
 14from bascenev1lib.actor.flag import Flag
 15from bascenev1lib.actor.playerspaz import PlayerSpaz
 16from bascenev1lib.actor.scoreboard import Scoreboard
 17from bascenev1lib.gameutils import SharedObjects
 18import bascenev1 as bs
 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(bs.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(bs.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 bascenev1.GameActivity
 48class KingOfTheHillGame(bs.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        bs.IntSetting(
 55            'Hold Time',
 56            min_value=10,
 57            default=30,
 58            increment=10,
 59        ),
 60        bs.IntChoiceSetting(
 61            'Time Limit',
 62            choices=[
 63                ('None', 0),
 64                ('1 Minute', 60),
 65                ('2 Minutes', 120),
 66                ('5 Minutes', 300),
 67                ('10 Minutes', 600),
 68                ('20 Minutes', 1200),
 69            ],
 70            default=0,
 71        ),
 72        bs.FloatChoiceSetting(
 73            'Respawn Times',
 74            choices=[
 75                ('Shorter', 0.25),
 76                ('Short', 0.5),
 77                ('Normal', 1.0),
 78                ('Long', 2.0),
 79                ('Longer', 4.0),
 80            ],
 81            default=1.0,
 82        ),
 83        bs.BoolSetting('Epic Mode', default=False),
 84    ]
 85    scoreconfig = bs.ScoreConfig(label='Time Held')
 86
 87    @classmethod
 88    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
 89        return issubclass(sessiontype, bs.MultiTeamSession)
 90
 91    @classmethod
 92    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
 93        assert bs.app.classic is not None
 94        return bs.app.classic.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 = bs.getsound('swip')
101        self._tick_sound = bs.getsound('tick')
102        self._countdownsounds = {
103            10: bs.getsound('announceTen'),
104            9: bs.getsound('announceNine'),
105            8: bs.getsound('announceEight'),
106            7: bs.getsound('announceSeven'),
107            6: bs.getsound('announceSix'),
108            5: bs.getsound('announceFive'),
109            4: bs.getsound('announceFour'),
110            3: bs.getsound('announceThree'),
111            2: bs.getsound('announceTwo'),
112            1: bs.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: bs.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 = bs.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                    bs.Call(self._handle_player_flag_region_collide, True),
132                ),
133                (
134                    'call',
135                    'at_disconnect',
136                    bs.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            bs.MusicType.EPIC if self._epic_mode else bs.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: bs.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        bs.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 = bs.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        bs.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_scoreboard()
190        self._update_flag_state()
191
192    def _tick(self) -> None:
193        self._update_flag_state()
194
195        # Give holding players points.
196        for player in self.players:
197            if player.time_at_flag > 0:
198                self.stats.player_scored(
199                    player, 3, screenmessage=False, display=False
200                )
201        if self._scoring_team is None:
202            scoring_team = None
203        else:
204            scoring_team = self._scoring_team()
205        if scoring_team:
206            if scoring_team.time_remaining > 0:
207                self._tick_sound.play()
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                numsound.play()
221
222            # winner
223            if scoring_team.time_remaining <= 0:
224                self.end_game()
225
226    def end_game(self) -> None:
227        results = bs.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 = bs.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            self._swipsound.play()
258
259    def _handle_player_flag_region_collide(self, colliding: bool) -> None:
260        try:
261            spaz = bs.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
262        except bs.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, bs.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)
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(bascenev1._player.Player[ForwardRef('Team')]):
34class Player(bs.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.

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

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

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

Instantiate the Activity.

name = 'King of the Hill'
description = 'Secure the flag for a set length of time.'
available_settings = [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)]), BoolSetting(name='Epic Mode', default=False)]
scoreconfig = ScoreConfig(label='Time Held', scoretype=<ScoreType.POINTS: 'p'>, lower_is_better=False, none_is_winner=False, version='')
@classmethod
def supports_session_type(cls, sessiontype: type[bascenev1._session.Session]) -> bool:
88    @classmethod
89    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
90        return issubclass(sessiontype, bs.MultiTeamSession)

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

@classmethod
def get_supported_maps(cls, sessiontype: type[bascenev1._session.Session]) -> list[str]:
92    @classmethod
93    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
94        assert bs.app.classic is not None
95        return bs.app.classic.getmaps('king_of_the_hill')

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

slow_motion = False

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

default_music = None
def get_instance_description(self) -> Union[str, Sequence]:
148    def get_instance_description(self) -> str | Sequence:
149        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]:
151    def get_instance_description_short(self) -> str | Sequence:
152        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: bascenev1._team.SessionTeam) -> Team:
154    def create_team(self, sessionteam: bs.SessionTeam) -> Team:
155        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:
157    def on_begin(self) -> None:
158        super().on_begin()
159        shared = SharedObjects.get()
160        self.setup_standard_time_limit(self._time_limit)
161        self.setup_standard_powerup_drops()
162        self._flag_pos = self.map.get_flag_position(None)
163        bs.timer(1.0, self._tick, repeat=True)
164        self._flag_state = FlagState.NEW
165        Flag.project_stand(self._flag_pos)
166        self._flag = Flag(
167            position=self._flag_pos, touchable=False, color=(1, 1, 1)
168        )
169        self._flag_light = bs.newnode(
170            'light',
171            attrs={
172                'position': self._flag_pos,
173                'intensity': 0.2,
174                'height_attenuated': False,
175                'radius': 0.4,
176                'color': (0.2, 0.2, 0.2),
177            },
178        )
179        # Flag region.
180        flagmats = [self._flag_region_material, shared.region_material]
181        bs.newnode(
182            'region',
183            attrs={
184                'position': self._flag_pos,
185                'scale': (1.8, 1.8, 1.8),
186                'type': 'sphere',
187                'materials': flagmats,
188            },
189        )
190        self._update_scoreboard()
191        self._update_flag_state()

Called once the previous Activity has finished transitioning out.

At this point the activity's initial players and teams are filled in and it should begin its actual game logic.

def end_game(self) -> None:
227    def end_game(self) -> None:
228        results = bs.GameResults()
229        for team in self.teams:
230            results.set_team_score(team, self._hold_time - team.time_remaining)
231        self.end(results=results, announce_delay=0)

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

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

def handlemessage(self, msg: Any) -> Any:
287    def handlemessage(self, msg: Any) -> Any:
288        if isinstance(msg, bs.PlayerDiedMessage):
289            super().handlemessage(msg)  # Augment default.
290
291            # No longer can count as time_at_flag once dead.
292            player = msg.getplayer(Player)
293            player.time_at_flag = 0
294            self._update_flag_state()
295            self.respawn_player(player)

General message handling; can be passed any message object.

Inherited Members
bascenev1._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
bascenev1._gameactivity.GameActivity
tips
allow_pausing
allow_kick_idle_players
show_kill_points
create_settings_ui
getscoreconfig
getname
get_display_string
get_team_display_string
get_description
get_description_display_string
get_available_settings
get_settings_display_string
initialplayerinfos
map
get_instance_display_string
get_instance_scoreboard_display_string
on_continue
is_waiting_for_continue
continue_or_end_game
on_player_join
respawn_player
spawn_player_if_exists
spawn_player
setup_standard_powerup_drops
setup_standard_time_limit
show_zoom_message
bascenev1._activity.Activity
settings_raw
teams
players
announce_player_deaths
is_joining_activity
use_fixed_vr_overlay
inherits_slow_motion
inherits_music
inherits_vr_camera_offset
inherits_vr_overlay_center
inherits_tint
allow_mid_activity_joins
transition_time
can_show_ad_on_death
paused_text
preloads
lobby
context
globalsnode
stats
on_expire
customdata
expired
playertype
teamtype
retain_actor
add_actor_weak_ref
session
on_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
bascenev1._dependency.DependencyComponent
dep_is_present
get_dynamic_deps