bascenev1lib.game.conquest

Provides the Conquest game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides the Conquest game."""
  4
  5# ba_meta require api 8
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import random
 11from typing import TYPE_CHECKING
 12
 13from bascenev1lib.actor.flag import Flag
 14from bascenev1lib.actor.scoreboard import Scoreboard
 15from bascenev1lib.actor.playerspaz import PlayerSpaz
 16from bascenev1lib.gameutils import SharedObjects
 17import bascenev1 as bs
 18
 19if TYPE_CHECKING:
 20    from typing import Any, Sequence
 21
 22    from bascenev1lib.actor.respawnicon import RespawnIcon
 23
 24
 25class ConquestFlag(Flag):
 26    """A custom flag for use with Conquest games."""
 27
 28    def __init__(self, *args: Any, **keywds: Any):
 29        super().__init__(*args, **keywds)
 30        self._team: Team | None = None
 31        self.light: bs.Node | None = None
 32
 33    @property
 34    def team(self) -> Team | None:
 35        """The team that owns this flag."""
 36        return self._team
 37
 38    @team.setter
 39    def team(self, team: Team) -> None:
 40        """Set the team that owns this flag."""
 41        self._team = team
 42
 43
 44class Player(bs.Player['Team']):
 45    """Our player type for this game."""
 46
 47    # FIXME: We shouldn't be using customdata here
 48    # (but need to update respawn funcs accordingly first).
 49    @property
 50    def respawn_timer(self) -> bs.Timer | None:
 51        """Type safe access to standard respawn timer."""
 52        return self.customdata.get('respawn_timer', None)
 53
 54    @respawn_timer.setter
 55    def respawn_timer(self, value: bs.Timer | None) -> None:
 56        self.customdata['respawn_timer'] = value
 57
 58    @property
 59    def respawn_icon(self) -> RespawnIcon | None:
 60        """Type safe access to standard respawn icon."""
 61        return self.customdata.get('respawn_icon', None)
 62
 63    @respawn_icon.setter
 64    def respawn_icon(self, value: RespawnIcon | None) -> None:
 65        self.customdata['respawn_icon'] = value
 66
 67
 68class Team(bs.Team[Player]):
 69    """Our team type for this game."""
 70
 71    def __init__(self) -> None:
 72        self.flags_held = 0
 73
 74
 75# ba_meta export bascenev1.GameActivity
 76class ConquestGame(bs.TeamGameActivity[Player, Team]):
 77    """A game where teams try to claim all flags on the map."""
 78
 79    name = 'Conquest'
 80    description = 'Secure all flags on the map to win.'
 81    available_settings = [
 82        bs.IntChoiceSetting(
 83            'Time Limit',
 84            choices=[
 85                ('None', 0),
 86                ('1 Minute', 60),
 87                ('2 Minutes', 120),
 88                ('5 Minutes', 300),
 89                ('10 Minutes', 600),
 90                ('20 Minutes', 1200),
 91            ],
 92            default=0,
 93        ),
 94        bs.FloatChoiceSetting(
 95            'Respawn Times',
 96            choices=[
 97                ('Shorter', 0.25),
 98                ('Short', 0.5),
 99                ('Normal', 1.0),
100                ('Long', 2.0),
101                ('Longer', 4.0),
102            ],
103            default=1.0,
104        ),
105        bs.BoolSetting('Epic Mode', default=False),
106    ]
107
108    @classmethod
109    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
110        return issubclass(sessiontype, bs.DualTeamSession)
111
112    @classmethod
113    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
114        assert bs.app.classic is not None
115        return bs.app.classic.getmaps('conquest')
116
117    def __init__(self, settings: dict):
118        super().__init__(settings)
119        shared = SharedObjects.get()
120        self._scoreboard = Scoreboard()
121        self._score_sound = bs.getsound('score')
122        self._swipsound = bs.getsound('swip')
123        self._extraflagmat = bs.Material()
124        self._flags: list[ConquestFlag] = []
125        self._epic_mode = bool(settings['Epic Mode'])
126        self._time_limit = float(settings['Time Limit'])
127
128        # Base class overrides.
129        self.slow_motion = self._epic_mode
130        self.default_music = (
131            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.GRAND_ROMP
132        )
133
134        # We want flags to tell us they've been hit but not react physically.
135        self._extraflagmat.add_actions(
136            conditions=('they_have_material', shared.player_material),
137            actions=(
138                ('modify_part_collision', 'collide', True),
139                ('call', 'at_connect', self._handle_flag_player_collide),
140            ),
141        )
142
143    def get_instance_description(self) -> str | Sequence:
144        return 'Secure all ${ARG1} flags.', len(self.map.flag_points)
145
146    def get_instance_description_short(self) -> str | Sequence:
147        return 'secure all ${ARG1} flags', len(self.map.flag_points)
148
149    def on_team_join(self, team: Team) -> None:
150        if self.has_begun():
151            self._update_scores()
152
153    def on_player_join(self, player: Player) -> None:
154        player.respawn_timer = None
155
156        # Only spawn if this player's team has a flag currently.
157        if player.team.flags_held > 0:
158            self.spawn_player(player)
159
160    def on_begin(self) -> None:
161        super().on_begin()
162        self.setup_standard_time_limit(self._time_limit)
163        self.setup_standard_powerup_drops()
164
165        # Set up flags with marker lights.
166        for i, flag_point in enumerate(self.map.flag_points):
167            point = flag_point
168            flag = ConquestFlag(
169                position=point, touchable=False, materials=[self._extraflagmat]
170            )
171            self._flags.append(flag)
172            Flag.project_stand(point)
173            flag.light = bs.newnode(
174                'light',
175                owner=flag.node,
176                attrs={
177                    'position': point,
178                    'intensity': 0.25,
179                    'height_attenuated': False,
180                    'radius': 0.3,
181                    'color': (1, 1, 1),
182                },
183            )
184
185        # Give teams a flag to start with.
186        for i, team in enumerate(self.teams):
187            self._flags[i].team = team
188            light = self._flags[i].light
189            assert light
190            node = self._flags[i].node
191            assert node
192            light.color = team.color
193            node.color = team.color
194
195        self._update_scores()
196
197        # Initial joiners didn't spawn due to no flags being owned yet;
198        # spawn them now.
199        for player in self.players:
200            self.spawn_player(player)
201
202    def _update_scores(self) -> None:
203        for team in self.teams:
204            team.flags_held = 0
205        for flag in self._flags:
206            if flag.team is not None:
207                flag.team.flags_held += 1
208        for team in self.teams:
209            # If a team finds themselves with no flags, cancel all
210            # outstanding spawn-timers.
211            if team.flags_held == 0:
212                for player in team.players:
213                    player.respawn_timer = None
214                    player.respawn_icon = None
215            if team.flags_held == len(self._flags):
216                self.end_game()
217            self._scoreboard.set_team_value(
218                team, team.flags_held, len(self._flags)
219            )
220
221    def end_game(self) -> None:
222        results = bs.GameResults()
223        for team in self.teams:
224            results.set_team_score(team, team.flags_held)
225        self.end(results=results)
226
227    def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None:
228        assert flag.node
229        assert flag.light
230        light = bs.newnode(
231            'light',
232            attrs={
233                'position': flag.node.position,
234                'height_attenuated': False,
235                'color': flag.light.color,
236            },
237        )
238        bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True)
239        bs.timer(length, light.delete)
240
241    def _handle_flag_player_collide(self) -> None:
242        collision = bs.getcollision()
243        try:
244            flag = collision.sourcenode.getdelegate(ConquestFlag, True)
245            player = collision.opposingnode.getdelegate(
246                PlayerSpaz, True
247            ).getplayer(Player, True)
248        except bs.NotFoundError:
249            return
250        assert flag.light
251
252        if flag.team is not player.team:
253            flag.team = player.team
254            flag.light.color = player.team.color
255            flag.node.color = player.team.color
256            self.stats.player_scored(player, 10, screenmessage=False)
257            self._swipsound.play()
258            self._flash_flag(flag)
259            self._update_scores()
260
261            # Respawn any players on this team that were in limbo due to the
262            # lack of a flag for their team.
263            for otherplayer in self.players:
264                if (
265                    otherplayer.team is flag.team
266                    and otherplayer.actor is not None
267                    and not otherplayer.is_alive()
268                    and otherplayer.respawn_timer is None
269                ):
270                    self.spawn_player(otherplayer)
271
272    def handlemessage(self, msg: Any) -> Any:
273        if isinstance(msg, bs.PlayerDiedMessage):
274            # Augment standard behavior.
275            super().handlemessage(msg)
276
277            # Respawn only if this team has a flag.
278            player = msg.getplayer(Player)
279            if player.team.flags_held > 0:
280                self.respawn_player(player)
281            else:
282                player.respawn_timer = None
283
284        else:
285            super().handlemessage(msg)
286
287    def spawn_player(self, player: Player) -> bs.Actor:
288        # We spawn players at different places based on what flags are held.
289        return self.spawn_player_spaz(
290            player, self._get_player_spawn_position(player)
291        )
292
293    def _get_player_spawn_position(self, player: Player) -> Sequence[float]:
294        # Iterate until we find a spawn owned by this team.
295        spawn_count = len(self.map.spawn_by_flag_points)
296
297        # Get all spawns owned by this team.
298        spawns = [
299            i for i in range(spawn_count) if self._flags[i].team is player.team
300        ]
301
302        closest_spawn = 0
303        closest_distance = 9999.0
304
305        # Now find the spawn that's closest to a spawn not owned by us;
306        # we'll use that one.
307        for spawn in spawns:
308            spt = self.map.spawn_by_flag_points[spawn]
309            our_pt = bs.Vec3(spt[0], spt[1], spt[2])
310            for otherspawn in [
311                i
312                for i in range(spawn_count)
313                if self._flags[i].team is not player.team
314            ]:
315                spt = self.map.spawn_by_flag_points[otherspawn]
316                their_pt = bs.Vec3(spt[0], spt[1], spt[2])
317                dist = (their_pt - our_pt).length()
318                if dist < closest_distance:
319                    closest_distance = dist
320                    closest_spawn = spawn
321
322        pos = self.map.spawn_by_flag_points[closest_spawn]
323        x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3])
324        z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5])
325        pos = (
326            pos[0] + random.uniform(*x_range),
327            pos[1],
328            pos[2] + random.uniform(*z_range),
329        )
330        return pos
class ConquestFlag(bascenev1lib.actor.flag.Flag):
26class ConquestFlag(Flag):
27    """A custom flag for use with Conquest games."""
28
29    def __init__(self, *args: Any, **keywds: Any):
30        super().__init__(*args, **keywds)
31        self._team: Team | None = None
32        self.light: bs.Node | None = None
33
34    @property
35    def team(self) -> Team | None:
36        """The team that owns this flag."""
37        return self._team
38
39    @team.setter
40    def team(self, team: Team) -> None:
41        """Set the team that owns this flag."""
42        self._team = team

A custom flag for use with Conquest games.

ConquestFlag(*args: Any, **keywds: Any)
29    def __init__(self, *args: Any, **keywds: Any):
30        super().__init__(*args, **keywds)
31        self._team: Team | None = None
32        self.light: bs.Node | None = None

Instantiate a flag.

If 'touchable' is False, the flag will only touch terrain; useful for things like king-of-the-hill where players should not be moving the flag around.

'materials can be a list of extra bs.Materials to apply to the flag.

If 'dropped_timeout' is provided (in seconds), the flag will die after remaining untouched for that long once it has been moved from its initial position.

light: _bascenev1.Node | None
team: Team | None

The team that owns this flag.

Inherited Members
bascenev1lib.actor.flag.Flag
node
set_score_text
handlemessage
project_stand
bascenev1._actor.Actor
autoretain
on_expire
expired
exists
is_alive
activity
getactivity
class Player(bascenev1._player.Player[ForwardRef('Team')]):
45class Player(bs.Player['Team']):
46    """Our player type for this game."""
47
48    # FIXME: We shouldn't be using customdata here
49    # (but need to update respawn funcs accordingly first).
50    @property
51    def respawn_timer(self) -> bs.Timer | None:
52        """Type safe access to standard respawn timer."""
53        return self.customdata.get('respawn_timer', None)
54
55    @respawn_timer.setter
56    def respawn_timer(self, value: bs.Timer | None) -> None:
57        self.customdata['respawn_timer'] = value
58
59    @property
60    def respawn_icon(self) -> RespawnIcon | None:
61        """Type safe access to standard respawn icon."""
62        return self.customdata.get('respawn_icon', None)
63
64    @respawn_icon.setter
65    def respawn_icon(self, value: RespawnIcon | None) -> None:
66        self.customdata['respawn_icon'] = value

Our player type for this game.

respawn_timer: _bascenev1.Timer | None

Type safe access to standard respawn timer.

Type safe access to standard respawn icon.

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.conquest.Player]):
69class Team(bs.Team[Player]):
70    """Our team type for this game."""
71
72    def __init__(self) -> None:
73        self.flags_held = 0

Our team type for this game.

flags_held
Inherited Members
bascenev1._team.Team
players
id
name
color
manual_init
customdata
on_expire
sessionteam
class ConquestGame(bascenev1._teamgame.TeamGameActivity[bascenev1lib.game.conquest.Player, bascenev1lib.game.conquest.Team]):
 77class ConquestGame(bs.TeamGameActivity[Player, Team]):
 78    """A game where teams try to claim all flags on the map."""
 79
 80    name = 'Conquest'
 81    description = 'Secure all flags on the map to win.'
 82    available_settings = [
 83        bs.IntChoiceSetting(
 84            'Time Limit',
 85            choices=[
 86                ('None', 0),
 87                ('1 Minute', 60),
 88                ('2 Minutes', 120),
 89                ('5 Minutes', 300),
 90                ('10 Minutes', 600),
 91                ('20 Minutes', 1200),
 92            ],
 93            default=0,
 94        ),
 95        bs.FloatChoiceSetting(
 96            'Respawn Times',
 97            choices=[
 98                ('Shorter', 0.25),
 99                ('Short', 0.5),
100                ('Normal', 1.0),
101                ('Long', 2.0),
102                ('Longer', 4.0),
103            ],
104            default=1.0,
105        ),
106        bs.BoolSetting('Epic Mode', default=False),
107    ]
108
109    @classmethod
110    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
111        return issubclass(sessiontype, bs.DualTeamSession)
112
113    @classmethod
114    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
115        assert bs.app.classic is not None
116        return bs.app.classic.getmaps('conquest')
117
118    def __init__(self, settings: dict):
119        super().__init__(settings)
120        shared = SharedObjects.get()
121        self._scoreboard = Scoreboard()
122        self._score_sound = bs.getsound('score')
123        self._swipsound = bs.getsound('swip')
124        self._extraflagmat = bs.Material()
125        self._flags: list[ConquestFlag] = []
126        self._epic_mode = bool(settings['Epic Mode'])
127        self._time_limit = float(settings['Time Limit'])
128
129        # Base class overrides.
130        self.slow_motion = self._epic_mode
131        self.default_music = (
132            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.GRAND_ROMP
133        )
134
135        # We want flags to tell us they've been hit but not react physically.
136        self._extraflagmat.add_actions(
137            conditions=('they_have_material', shared.player_material),
138            actions=(
139                ('modify_part_collision', 'collide', True),
140                ('call', 'at_connect', self._handle_flag_player_collide),
141            ),
142        )
143
144    def get_instance_description(self) -> str | Sequence:
145        return 'Secure all ${ARG1} flags.', len(self.map.flag_points)
146
147    def get_instance_description_short(self) -> str | Sequence:
148        return 'secure all ${ARG1} flags', len(self.map.flag_points)
149
150    def on_team_join(self, team: Team) -> None:
151        if self.has_begun():
152            self._update_scores()
153
154    def on_player_join(self, player: Player) -> None:
155        player.respawn_timer = None
156
157        # Only spawn if this player's team has a flag currently.
158        if player.team.flags_held > 0:
159            self.spawn_player(player)
160
161    def on_begin(self) -> None:
162        super().on_begin()
163        self.setup_standard_time_limit(self._time_limit)
164        self.setup_standard_powerup_drops()
165
166        # Set up flags with marker lights.
167        for i, flag_point in enumerate(self.map.flag_points):
168            point = flag_point
169            flag = ConquestFlag(
170                position=point, touchable=False, materials=[self._extraflagmat]
171            )
172            self._flags.append(flag)
173            Flag.project_stand(point)
174            flag.light = bs.newnode(
175                'light',
176                owner=flag.node,
177                attrs={
178                    'position': point,
179                    'intensity': 0.25,
180                    'height_attenuated': False,
181                    'radius': 0.3,
182                    'color': (1, 1, 1),
183                },
184            )
185
186        # Give teams a flag to start with.
187        for i, team in enumerate(self.teams):
188            self._flags[i].team = team
189            light = self._flags[i].light
190            assert light
191            node = self._flags[i].node
192            assert node
193            light.color = team.color
194            node.color = team.color
195
196        self._update_scores()
197
198        # Initial joiners didn't spawn due to no flags being owned yet;
199        # spawn them now.
200        for player in self.players:
201            self.spawn_player(player)
202
203    def _update_scores(self) -> None:
204        for team in self.teams:
205            team.flags_held = 0
206        for flag in self._flags:
207            if flag.team is not None:
208                flag.team.flags_held += 1
209        for team in self.teams:
210            # If a team finds themselves with no flags, cancel all
211            # outstanding spawn-timers.
212            if team.flags_held == 0:
213                for player in team.players:
214                    player.respawn_timer = None
215                    player.respawn_icon = None
216            if team.flags_held == len(self._flags):
217                self.end_game()
218            self._scoreboard.set_team_value(
219                team, team.flags_held, len(self._flags)
220            )
221
222    def end_game(self) -> None:
223        results = bs.GameResults()
224        for team in self.teams:
225            results.set_team_score(team, team.flags_held)
226        self.end(results=results)
227
228    def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None:
229        assert flag.node
230        assert flag.light
231        light = bs.newnode(
232            'light',
233            attrs={
234                'position': flag.node.position,
235                'height_attenuated': False,
236                'color': flag.light.color,
237            },
238        )
239        bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True)
240        bs.timer(length, light.delete)
241
242    def _handle_flag_player_collide(self) -> None:
243        collision = bs.getcollision()
244        try:
245            flag = collision.sourcenode.getdelegate(ConquestFlag, True)
246            player = collision.opposingnode.getdelegate(
247                PlayerSpaz, True
248            ).getplayer(Player, True)
249        except bs.NotFoundError:
250            return
251        assert flag.light
252
253        if flag.team is not player.team:
254            flag.team = player.team
255            flag.light.color = player.team.color
256            flag.node.color = player.team.color
257            self.stats.player_scored(player, 10, screenmessage=False)
258            self._swipsound.play()
259            self._flash_flag(flag)
260            self._update_scores()
261
262            # Respawn any players on this team that were in limbo due to the
263            # lack of a flag for their team.
264            for otherplayer in self.players:
265                if (
266                    otherplayer.team is flag.team
267                    and otherplayer.actor is not None
268                    and not otherplayer.is_alive()
269                    and otherplayer.respawn_timer is None
270                ):
271                    self.spawn_player(otherplayer)
272
273    def handlemessage(self, msg: Any) -> Any:
274        if isinstance(msg, bs.PlayerDiedMessage):
275            # Augment standard behavior.
276            super().handlemessage(msg)
277
278            # Respawn only if this team has a flag.
279            player = msg.getplayer(Player)
280            if player.team.flags_held > 0:
281                self.respawn_player(player)
282            else:
283                player.respawn_timer = None
284
285        else:
286            super().handlemessage(msg)
287
288    def spawn_player(self, player: Player) -> bs.Actor:
289        # We spawn players at different places based on what flags are held.
290        return self.spawn_player_spaz(
291            player, self._get_player_spawn_position(player)
292        )
293
294    def _get_player_spawn_position(self, player: Player) -> Sequence[float]:
295        # Iterate until we find a spawn owned by this team.
296        spawn_count = len(self.map.spawn_by_flag_points)
297
298        # Get all spawns owned by this team.
299        spawns = [
300            i for i in range(spawn_count) if self._flags[i].team is player.team
301        ]
302
303        closest_spawn = 0
304        closest_distance = 9999.0
305
306        # Now find the spawn that's closest to a spawn not owned by us;
307        # we'll use that one.
308        for spawn in spawns:
309            spt = self.map.spawn_by_flag_points[spawn]
310            our_pt = bs.Vec3(spt[0], spt[1], spt[2])
311            for otherspawn in [
312                i
313                for i in range(spawn_count)
314                if self._flags[i].team is not player.team
315            ]:
316                spt = self.map.spawn_by_flag_points[otherspawn]
317                their_pt = bs.Vec3(spt[0], spt[1], spt[2])
318                dist = (their_pt - our_pt).length()
319                if dist < closest_distance:
320                    closest_distance = dist
321                    closest_spawn = spawn
322
323        pos = self.map.spawn_by_flag_points[closest_spawn]
324        x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3])
325        z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5])
326        pos = (
327            pos[0] + random.uniform(*x_range),
328            pos[1],
329            pos[2] + random.uniform(*z_range),
330        )
331        return pos

A game where teams try to claim all flags on the map.

ConquestGame(settings: dict)
118    def __init__(self, settings: dict):
119        super().__init__(settings)
120        shared = SharedObjects.get()
121        self._scoreboard = Scoreboard()
122        self._score_sound = bs.getsound('score')
123        self._swipsound = bs.getsound('swip')
124        self._extraflagmat = bs.Material()
125        self._flags: list[ConquestFlag] = []
126        self._epic_mode = bool(settings['Epic Mode'])
127        self._time_limit = float(settings['Time Limit'])
128
129        # Base class overrides.
130        self.slow_motion = self._epic_mode
131        self.default_music = (
132            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.GRAND_ROMP
133        )
134
135        # We want flags to tell us they've been hit but not react physically.
136        self._extraflagmat.add_actions(
137            conditions=('they_have_material', shared.player_material),
138            actions=(
139                ('modify_part_collision', 'collide', True),
140                ('call', 'at_connect', self._handle_flag_player_collide),
141            ),
142        )

Instantiate the Activity.

name = 'Conquest'
description = 'Secure all flags on the map to win.'
available_settings = [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)]
@classmethod
def supports_session_type(cls, sessiontype: type[bascenev1._session.Session]) -> bool:
109    @classmethod
110    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
111        return issubclass(sessiontype, bs.DualTeamSession)

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]:
113    @classmethod
114    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
115        assert bs.app.classic is not None
116        return bs.app.classic.getmaps('conquest')

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]:
144    def get_instance_description(self) -> str | Sequence:
145        return 'Secure all ${ARG1} flags.', len(self.map.flag_points)

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]:
147    def get_instance_description_short(self) -> str | Sequence:
148        return 'secure all ${ARG1} flags', len(self.map.flag_points)

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 on_team_join(self, team: Team) -> None:
150    def on_team_join(self, team: Team) -> None:
151        if self.has_begun():
152            self._update_scores()

Called when a new bascenev1.Team joins the Activity.

(including the initial set of Teams)

def on_player_join(self, player: Player) -> None:
154    def on_player_join(self, player: Player) -> None:
155        player.respawn_timer = None
156
157        # Only spawn if this player's team has a flag currently.
158        if player.team.flags_held > 0:
159            self.spawn_player(player)

Called when a new bascenev1.Player has joined the Activity.

(including the initial set of Players)

def on_begin(self) -> None:
161    def on_begin(self) -> None:
162        super().on_begin()
163        self.setup_standard_time_limit(self._time_limit)
164        self.setup_standard_powerup_drops()
165
166        # Set up flags with marker lights.
167        for i, flag_point in enumerate(self.map.flag_points):
168            point = flag_point
169            flag = ConquestFlag(
170                position=point, touchable=False, materials=[self._extraflagmat]
171            )
172            self._flags.append(flag)
173            Flag.project_stand(point)
174            flag.light = bs.newnode(
175                'light',
176                owner=flag.node,
177                attrs={
178                    'position': point,
179                    'intensity': 0.25,
180                    'height_attenuated': False,
181                    'radius': 0.3,
182                    'color': (1, 1, 1),
183                },
184            )
185
186        # Give teams a flag to start with.
187        for i, team in enumerate(self.teams):
188            self._flags[i].team = team
189            light = self._flags[i].light
190            assert light
191            node = self._flags[i].node
192            assert node
193            light.color = team.color
194            node.color = team.color
195
196        self._update_scores()
197
198        # Initial joiners didn't spawn due to no flags being owned yet;
199        # spawn them now.
200        for player in self.players:
201            self.spawn_player(player)

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:
222    def end_game(self) -> None:
223        results = bs.GameResults()
224        for team in self.teams:
225            results.set_team_score(team, team.flags_held)
226        self.end(results=results)

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:
273    def handlemessage(self, msg: Any) -> Any:
274        if isinstance(msg, bs.PlayerDiedMessage):
275            # Augment standard behavior.
276            super().handlemessage(msg)
277
278            # Respawn only if this team has a flag.
279            player = msg.getplayer(Player)
280            if player.team.flags_held > 0:
281                self.respawn_player(player)
282            else:
283                player.respawn_timer = None
284
285        else:
286            super().handlemessage(msg)

General message handling; can be passed any message object.

def spawn_player( self, player: Player) -> bascenev1._actor.Actor:
288    def spawn_player(self, player: Player) -> bs.Actor:
289        # We spawn players at different places based on what flags are held.
290        return self.spawn_player_spaz(
291            player, self._get_player_spawn_position(player)
292        )

Spawn something for the provided bascenev1.Player.

The default implementation simply calls spawn_player_spaz().

Inherited Members
bascenev1._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
bascenev1._gameactivity.GameActivity
tips
scoreconfig
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
respawn_player
spawn_player_if_exists
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
create_team
bascenev1._dependency.DependencyComponent
dep_is_present
get_dynamic_deps