bascenev1lib.game.keepaway

Defines a keep-away game type.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Defines a keep-away game type."""
  4
  5# ba_meta require api 8
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import logging
 11from enum import Enum
 12from typing import TYPE_CHECKING
 13
 14from bascenev1lib.actor.playerspaz import PlayerSpaz
 15from bascenev1lib.actor.scoreboard import Scoreboard
 16from bascenev1lib.actor.flag import (
 17    Flag,
 18    FlagDroppedMessage,
 19    FlagDiedMessage,
 20    FlagPickedUpMessage,
 21)
 22import bascenev1 as bs
 23
 24if TYPE_CHECKING:
 25    from typing import Any, Sequence
 26
 27
 28class FlagState(Enum):
 29    """States our single flag can be in."""
 30
 31    NEW = 0
 32    UNCONTESTED = 1
 33    CONTESTED = 2
 34    HELD = 3
 35
 36
 37class Player(bs.Player['Team']):
 38    """Our player type for this game."""
 39
 40
 41class Team(bs.Team[Player]):
 42    """Our team type for this game."""
 43
 44    def __init__(self, timeremaining: int) -> None:
 45        self.timeremaining = timeremaining
 46        self.holdingflag = False
 47
 48
 49# ba_meta export bascenev1.GameActivity
 50class KeepAwayGame(bs.TeamGameActivity[Player, Team]):
 51    """Game where you try to keep the flag away from your enemies."""
 52
 53    name = 'Keep Away'
 54    description = 'Carry the flag for a set length of time.'
 55    available_settings = [
 56        bs.IntSetting(
 57            'Hold Time',
 58            min_value=10,
 59            default=30,
 60            increment=10,
 61        ),
 62        bs.IntChoiceSetting(
 63            'Time Limit',
 64            choices=[
 65                ('None', 0),
 66                ('1 Minute', 60),
 67                ('2 Minutes', 120),
 68                ('5 Minutes', 300),
 69                ('10 Minutes', 600),
 70                ('20 Minutes', 1200),
 71            ],
 72            default=0,
 73        ),
 74        bs.FloatChoiceSetting(
 75            'Respawn Times',
 76            choices=[
 77                ('Shorter', 0.25),
 78                ('Short', 0.5),
 79                ('Normal', 1.0),
 80                ('Long', 2.0),
 81                ('Longer', 4.0),
 82            ],
 83            default=1.0,
 84        ),
 85        bs.BoolSetting('Epic Mode', default=False),
 86    ]
 87    scoreconfig = bs.ScoreConfig(label='Time Held')
 88
 89    @classmethod
 90    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
 91        return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
 92            sessiontype, bs.FreeForAllSession
 93        )
 94
 95    @classmethod
 96    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
 97        assert bs.app.classic is not None
 98        return bs.app.classic.getmaps('keep_away')
 99
100    def __init__(self, settings: dict):
101        super().__init__(settings)
102        self._scoreboard = Scoreboard()
103        self._swipsound = bs.getsound('swip')
104        self._tick_sound = bs.getsound('tick')
105        self._countdownsounds = {
106            10: bs.getsound('announceTen'),
107            9: bs.getsound('announceNine'),
108            8: bs.getsound('announceEight'),
109            7: bs.getsound('announceSeven'),
110            6: bs.getsound('announceSix'),
111            5: bs.getsound('announceFive'),
112            4: bs.getsound('announceFour'),
113            3: bs.getsound('announceThree'),
114            2: bs.getsound('announceTwo'),
115            1: bs.getsound('announceOne'),
116        }
117        self._flag_spawn_pos: Sequence[float] | None = None
118        self._update_timer: bs.Timer | None = None
119        self._holding_players: list[Player] = []
120        self._flag_state: FlagState | None = None
121        self._flag_light: bs.Node | None = None
122        self._scoring_team: Team | None = None
123        self._flag: Flag | None = None
124        self._hold_time = int(settings['Hold Time'])
125        self._time_limit = float(settings['Time Limit'])
126        self._epic_mode = bool(settings['Epic Mode'])
127        self.slow_motion = self._epic_mode
128        self.default_music = (
129            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.KEEP_AWAY
130        )
131
132    def get_instance_description(self) -> str | Sequence:
133        return 'Carry the flag for ${ARG1} seconds.', self._hold_time
134
135    def get_instance_description_short(self) -> str | Sequence:
136        return 'carry the flag for ${ARG1} seconds', self._hold_time
137
138    def create_team(self, sessionteam: bs.SessionTeam) -> Team:
139        return Team(timeremaining=self._hold_time)
140
141    def on_team_join(self, team: Team) -> None:
142        self._update_scoreboard()
143
144    def on_begin(self) -> None:
145        super().on_begin()
146        self.setup_standard_time_limit(self._time_limit)
147        self.setup_standard_powerup_drops()
148        self._flag_spawn_pos = self.map.get_flag_position(None)
149        self._spawn_flag()
150        self._update_timer = bs.Timer(1.0, call=self._tick, repeat=True)
151        self._update_flag_state()
152        Flag.project_stand(self._flag_spawn_pos)
153
154    def _tick(self) -> None:
155        self._update_flag_state()
156
157        # Award points to all living players holding the flag.
158        for player in self._holding_players:
159            if player:
160                self.stats.player_scored(
161                    player, 3, screenmessage=False, display=False
162                )
163
164        scoreteam = self._scoring_team
165
166        if scoreteam is not None:
167            if scoreteam.timeremaining > 0:
168                self._tick_sound.play()
169
170            scoreteam.timeremaining = max(0, scoreteam.timeremaining - 1)
171            self._update_scoreboard()
172            if scoreteam.timeremaining > 0:
173                assert self._flag is not None
174                self._flag.set_score_text(str(scoreteam.timeremaining))
175
176            # Announce numbers we have sounds for.
177            if scoreteam.timeremaining in self._countdownsounds:
178                self._countdownsounds[scoreteam.timeremaining].play()
179
180            # Winner.
181            if scoreteam.timeremaining <= 0:
182                self.end_game()
183
184    def end_game(self) -> None:
185        results = bs.GameResults()
186        for team in self.teams:
187            results.set_team_score(team, self._hold_time - team.timeremaining)
188        self.end(results=results, announce_delay=0)
189
190    def _update_flag_state(self) -> None:
191        for team in self.teams:
192            team.holdingflag = False
193        self._holding_players = []
194        for player in self.players:
195            holdingflag = False
196            try:
197                assert isinstance(player.actor, (PlayerSpaz, type(None)))
198                if (
199                    player.actor
200                    and player.actor.node
201                    and player.actor.node.hold_node
202                ):
203                    holdingflag = (
204                        player.actor.node.hold_node.getnodetype() == 'flag'
205                    )
206            except Exception:
207                logging.exception('Error checking hold flag.')
208            if holdingflag:
209                self._holding_players.append(player)
210                player.team.holdingflag = True
211
212        holdingteams = set(t for t in self.teams if t.holdingflag)
213        prevstate = self._flag_state
214        assert self._flag is not None
215        assert self._flag_light
216        assert self._flag.node
217        if len(holdingteams) > 1:
218            self._flag_state = FlagState.CONTESTED
219            self._scoring_team = None
220            self._flag_light.color = (0.6, 0.6, 0.1)
221            self._flag.node.color = (1.0, 1.0, 0.4)
222        elif len(holdingteams) == 1:
223            holdingteam = list(holdingteams)[0]
224            self._flag_state = FlagState.HELD
225            self._scoring_team = holdingteam
226            self._flag_light.color = bs.normalized_color(holdingteam.color)
227            self._flag.node.color = holdingteam.color
228        else:
229            self._flag_state = FlagState.UNCONTESTED
230            self._scoring_team = None
231            self._flag_light.color = (0.2, 0.2, 0.2)
232            self._flag.node.color = (1, 1, 1)
233
234        if self._flag_state != prevstate:
235            self._swipsound.play()
236
237    def _spawn_flag(self) -> None:
238        self._swipsound.play()
239        self._flash_flag_spawn()
240        assert self._flag_spawn_pos is not None
241        self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos)
242        self._flag_state = FlagState.NEW
243        self._flag_light = bs.newnode(
244            'light',
245            owner=self._flag.node,
246            attrs={'intensity': 0.2, 'radius': 0.3, 'color': (0.2, 0.2, 0.2)},
247        )
248        assert self._flag.node
249        self._flag.node.connectattr('position', self._flag_light, 'position')
250        self._update_flag_state()
251
252    def _flash_flag_spawn(self) -> None:
253        light = bs.newnode(
254            'light',
255            attrs={
256                'position': self._flag_spawn_pos,
257                'color': (1, 1, 1),
258                'radius': 0.3,
259                'height_attenuated': False,
260            },
261        )
262        bs.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
263        bs.timer(1.0, light.delete)
264
265    def _update_scoreboard(self) -> None:
266        for team in self.teams:
267            self._scoreboard.set_team_value(
268                team, team.timeremaining, self._hold_time, countdown=True
269            )
270
271    def handlemessage(self, msg: Any) -> Any:
272        if isinstance(msg, bs.PlayerDiedMessage):
273            # Augment standard behavior.
274            super().handlemessage(msg)
275            self.respawn_player(msg.getplayer(Player))
276        elif isinstance(msg, FlagDiedMessage):
277            self._spawn_flag()
278        elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)):
279            self._update_flag_state()
280        else:
281            super().handlemessage(msg)
class FlagState(enum.Enum):
29class FlagState(Enum):
30    """States our single flag can be in."""
31
32    NEW = 0
33    UNCONTESTED = 1
34    CONTESTED = 2
35    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')]):
38class Player(bs.Player['Team']):
39    """Our player type for this game."""

Our player type for this game.

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.keepaway.Player]):
42class Team(bs.Team[Player]):
43    """Our team type for this game."""
44
45    def __init__(self, timeremaining: int) -> None:
46        self.timeremaining = timeremaining
47        self.holdingflag = False

Our team type for this game.

Team(timeremaining: int)
45    def __init__(self, timeremaining: int) -> None:
46        self.timeremaining = timeremaining
47        self.holdingflag = False
timeremaining
holdingflag
Inherited Members
bascenev1._team.Team
players
id
name
color
manual_init
customdata
on_expire
sessionteam
class KeepAwayGame(bascenev1._teamgame.TeamGameActivity[bascenev1lib.game.keepaway.Player, bascenev1lib.game.keepaway.Team]):
 51class KeepAwayGame(bs.TeamGameActivity[Player, Team]):
 52    """Game where you try to keep the flag away from your enemies."""
 53
 54    name = 'Keep Away'
 55    description = 'Carry the flag for a set length of time.'
 56    available_settings = [
 57        bs.IntSetting(
 58            'Hold Time',
 59            min_value=10,
 60            default=30,
 61            increment=10,
 62        ),
 63        bs.IntChoiceSetting(
 64            'Time Limit',
 65            choices=[
 66                ('None', 0),
 67                ('1 Minute', 60),
 68                ('2 Minutes', 120),
 69                ('5 Minutes', 300),
 70                ('10 Minutes', 600),
 71                ('20 Minutes', 1200),
 72            ],
 73            default=0,
 74        ),
 75        bs.FloatChoiceSetting(
 76            'Respawn Times',
 77            choices=[
 78                ('Shorter', 0.25),
 79                ('Short', 0.5),
 80                ('Normal', 1.0),
 81                ('Long', 2.0),
 82                ('Longer', 4.0),
 83            ],
 84            default=1.0,
 85        ),
 86        bs.BoolSetting('Epic Mode', default=False),
 87    ]
 88    scoreconfig = bs.ScoreConfig(label='Time Held')
 89
 90    @classmethod
 91    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
 92        return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
 93            sessiontype, bs.FreeForAllSession
 94        )
 95
 96    @classmethod
 97    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
 98        assert bs.app.classic is not None
 99        return bs.app.classic.getmaps('keep_away')
100
101    def __init__(self, settings: dict):
102        super().__init__(settings)
103        self._scoreboard = Scoreboard()
104        self._swipsound = bs.getsound('swip')
105        self._tick_sound = bs.getsound('tick')
106        self._countdownsounds = {
107            10: bs.getsound('announceTen'),
108            9: bs.getsound('announceNine'),
109            8: bs.getsound('announceEight'),
110            7: bs.getsound('announceSeven'),
111            6: bs.getsound('announceSix'),
112            5: bs.getsound('announceFive'),
113            4: bs.getsound('announceFour'),
114            3: bs.getsound('announceThree'),
115            2: bs.getsound('announceTwo'),
116            1: bs.getsound('announceOne'),
117        }
118        self._flag_spawn_pos: Sequence[float] | None = None
119        self._update_timer: bs.Timer | None = None
120        self._holding_players: list[Player] = []
121        self._flag_state: FlagState | None = None
122        self._flag_light: bs.Node | None = None
123        self._scoring_team: Team | None = None
124        self._flag: Flag | None = None
125        self._hold_time = int(settings['Hold Time'])
126        self._time_limit = float(settings['Time Limit'])
127        self._epic_mode = bool(settings['Epic Mode'])
128        self.slow_motion = self._epic_mode
129        self.default_music = (
130            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.KEEP_AWAY
131        )
132
133    def get_instance_description(self) -> str | Sequence:
134        return 'Carry the flag for ${ARG1} seconds.', self._hold_time
135
136    def get_instance_description_short(self) -> str | Sequence:
137        return 'carry the flag for ${ARG1} seconds', self._hold_time
138
139    def create_team(self, sessionteam: bs.SessionTeam) -> Team:
140        return Team(timeremaining=self._hold_time)
141
142    def on_team_join(self, team: Team) -> None:
143        self._update_scoreboard()
144
145    def on_begin(self) -> None:
146        super().on_begin()
147        self.setup_standard_time_limit(self._time_limit)
148        self.setup_standard_powerup_drops()
149        self._flag_spawn_pos = self.map.get_flag_position(None)
150        self._spawn_flag()
151        self._update_timer = bs.Timer(1.0, call=self._tick, repeat=True)
152        self._update_flag_state()
153        Flag.project_stand(self._flag_spawn_pos)
154
155    def _tick(self) -> None:
156        self._update_flag_state()
157
158        # Award points to all living players holding the flag.
159        for player in self._holding_players:
160            if player:
161                self.stats.player_scored(
162                    player, 3, screenmessage=False, display=False
163                )
164
165        scoreteam = self._scoring_team
166
167        if scoreteam is not None:
168            if scoreteam.timeremaining > 0:
169                self._tick_sound.play()
170
171            scoreteam.timeremaining = max(0, scoreteam.timeremaining - 1)
172            self._update_scoreboard()
173            if scoreteam.timeremaining > 0:
174                assert self._flag is not None
175                self._flag.set_score_text(str(scoreteam.timeremaining))
176
177            # Announce numbers we have sounds for.
178            if scoreteam.timeremaining in self._countdownsounds:
179                self._countdownsounds[scoreteam.timeremaining].play()
180
181            # Winner.
182            if scoreteam.timeremaining <= 0:
183                self.end_game()
184
185    def end_game(self) -> None:
186        results = bs.GameResults()
187        for team in self.teams:
188            results.set_team_score(team, self._hold_time - team.timeremaining)
189        self.end(results=results, announce_delay=0)
190
191    def _update_flag_state(self) -> None:
192        for team in self.teams:
193            team.holdingflag = False
194        self._holding_players = []
195        for player in self.players:
196            holdingflag = False
197            try:
198                assert isinstance(player.actor, (PlayerSpaz, type(None)))
199                if (
200                    player.actor
201                    and player.actor.node
202                    and player.actor.node.hold_node
203                ):
204                    holdingflag = (
205                        player.actor.node.hold_node.getnodetype() == 'flag'
206                    )
207            except Exception:
208                logging.exception('Error checking hold flag.')
209            if holdingflag:
210                self._holding_players.append(player)
211                player.team.holdingflag = True
212
213        holdingteams = set(t for t in self.teams if t.holdingflag)
214        prevstate = self._flag_state
215        assert self._flag is not None
216        assert self._flag_light
217        assert self._flag.node
218        if len(holdingteams) > 1:
219            self._flag_state = FlagState.CONTESTED
220            self._scoring_team = None
221            self._flag_light.color = (0.6, 0.6, 0.1)
222            self._flag.node.color = (1.0, 1.0, 0.4)
223        elif len(holdingteams) == 1:
224            holdingteam = list(holdingteams)[0]
225            self._flag_state = FlagState.HELD
226            self._scoring_team = holdingteam
227            self._flag_light.color = bs.normalized_color(holdingteam.color)
228            self._flag.node.color = holdingteam.color
229        else:
230            self._flag_state = FlagState.UNCONTESTED
231            self._scoring_team = None
232            self._flag_light.color = (0.2, 0.2, 0.2)
233            self._flag.node.color = (1, 1, 1)
234
235        if self._flag_state != prevstate:
236            self._swipsound.play()
237
238    def _spawn_flag(self) -> None:
239        self._swipsound.play()
240        self._flash_flag_spawn()
241        assert self._flag_spawn_pos is not None
242        self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos)
243        self._flag_state = FlagState.NEW
244        self._flag_light = bs.newnode(
245            'light',
246            owner=self._flag.node,
247            attrs={'intensity': 0.2, 'radius': 0.3, 'color': (0.2, 0.2, 0.2)},
248        )
249        assert self._flag.node
250        self._flag.node.connectattr('position', self._flag_light, 'position')
251        self._update_flag_state()
252
253    def _flash_flag_spawn(self) -> None:
254        light = bs.newnode(
255            'light',
256            attrs={
257                'position': self._flag_spawn_pos,
258                'color': (1, 1, 1),
259                'radius': 0.3,
260                'height_attenuated': False,
261            },
262        )
263        bs.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
264        bs.timer(1.0, light.delete)
265
266    def _update_scoreboard(self) -> None:
267        for team in self.teams:
268            self._scoreboard.set_team_value(
269                team, team.timeremaining, self._hold_time, countdown=True
270            )
271
272    def handlemessage(self, msg: Any) -> Any:
273        if isinstance(msg, bs.PlayerDiedMessage):
274            # Augment standard behavior.
275            super().handlemessage(msg)
276            self.respawn_player(msg.getplayer(Player))
277        elif isinstance(msg, FlagDiedMessage):
278            self._spawn_flag()
279        elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)):
280            self._update_flag_state()
281        else:
282            super().handlemessage(msg)

Game where you try to keep the flag away from your enemies.

KeepAwayGame(settings: dict)
101    def __init__(self, settings: dict):
102        super().__init__(settings)
103        self._scoreboard = Scoreboard()
104        self._swipsound = bs.getsound('swip')
105        self._tick_sound = bs.getsound('tick')
106        self._countdownsounds = {
107            10: bs.getsound('announceTen'),
108            9: bs.getsound('announceNine'),
109            8: bs.getsound('announceEight'),
110            7: bs.getsound('announceSeven'),
111            6: bs.getsound('announceSix'),
112            5: bs.getsound('announceFive'),
113            4: bs.getsound('announceFour'),
114            3: bs.getsound('announceThree'),
115            2: bs.getsound('announceTwo'),
116            1: bs.getsound('announceOne'),
117        }
118        self._flag_spawn_pos: Sequence[float] | None = None
119        self._update_timer: bs.Timer | None = None
120        self._holding_players: list[Player] = []
121        self._flag_state: FlagState | None = None
122        self._flag_light: bs.Node | None = None
123        self._scoring_team: Team | None = None
124        self._flag: Flag | None = None
125        self._hold_time = int(settings['Hold Time'])
126        self._time_limit = float(settings['Time Limit'])
127        self._epic_mode = bool(settings['Epic Mode'])
128        self.slow_motion = self._epic_mode
129        self.default_music = (
130            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.KEEP_AWAY
131        )

Instantiate the Activity.

name = 'Keep Away'
description = 'Carry 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:
90    @classmethod
91    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
92        return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
93            sessiontype, bs.FreeForAllSession
94        )

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]:
96    @classmethod
97    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
98        assert bs.app.classic is not None
99        return bs.app.classic.getmaps('keep_away')

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

slow_motion = False

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

default_music = None
def get_instance_description(self) -> Union[str, Sequence]:
133    def get_instance_description(self) -> str | Sequence:
134        return 'Carry 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]:
136    def get_instance_description_short(self) -> str | Sequence:
137        return 'carry 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:
139    def create_team(self, sessionteam: bs.SessionTeam) -> Team:
140        return Team(timeremaining=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_team_join(self, team: Team) -> None:
142    def on_team_join(self, team: Team) -> None:
143        self._update_scoreboard()

Called when a new bascenev1.Team joins the Activity.

(including the initial set of Teams)

def on_begin(self) -> None:
145    def on_begin(self) -> None:
146        super().on_begin()
147        self.setup_standard_time_limit(self._time_limit)
148        self.setup_standard_powerup_drops()
149        self._flag_spawn_pos = self.map.get_flag_position(None)
150        self._spawn_flag()
151        self._update_timer = bs.Timer(1.0, call=self._tick, repeat=True)
152        self._update_flag_state()
153        Flag.project_stand(self._flag_spawn_pos)

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:
185    def end_game(self) -> None:
186        results = bs.GameResults()
187        for team in self.teams:
188            results.set_team_score(team, self._hold_time - team.timeremaining)
189        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:
272    def handlemessage(self, msg: Any) -> Any:
273        if isinstance(msg, bs.PlayerDiedMessage):
274            # Augment standard behavior.
275            super().handlemessage(msg)
276            self.respawn_player(msg.getplayer(Player))
277        elif isinstance(msg, FlagDiedMessage):
278            self._spawn_flag()
279        elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)):
280            self._update_flag_state()
281        else:
282            super().handlemessage(msg)

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_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