bascenev1lib.game.meteorshower

Defines a bomb-dodging mini-game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Defines a bomb-dodging mini-game."""
  4
  5# ba_meta require api 9
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import random
 11from typing import TYPE_CHECKING, override
 12
 13import bascenev1 as bs
 14
 15from bascenev1lib.actor.bomb import Bomb
 16from bascenev1lib.actor.onscreentimer import OnScreenTimer
 17
 18if TYPE_CHECKING:
 19    from typing import Any, Sequence
 20
 21
 22class Player(bs.Player['Team']):
 23    """Our player type for this game."""
 24
 25    def __init__(self) -> None:
 26        super().__init__()
 27        self.death_time: float | None = None
 28
 29
 30class Team(bs.Team[Player]):
 31    """Our team type for this game."""
 32
 33
 34# ba_meta export bascenev1.GameActivity
 35class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
 36    """Minigame involving dodging falling bombs."""
 37
 38    name = 'Meteor Shower'
 39    description = 'Dodge the falling bombs.'
 40    available_settings = [bs.BoolSetting('Epic Mode', default=False)]
 41    scoreconfig = bs.ScoreConfig(
 42        label='Survived', scoretype=bs.ScoreType.MILLISECONDS, version='B'
 43    )
 44
 45    # Print messages when players die (since its meaningful in this game).
 46    announce_player_deaths = True
 47
 48    # Don't allow joining after we start
 49    # (would enable leave/rejoin tomfoolery).
 50    allow_mid_activity_joins = False
 51
 52    # We're currently hard-coded for one map.
 53    @override
 54    @classmethod
 55    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
 56        return ['Rampage']
 57
 58    # We support teams, free-for-all, and co-op sessions.
 59    @override
 60    @classmethod
 61    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
 62        return (
 63            issubclass(sessiontype, bs.DualTeamSession)
 64            or issubclass(sessiontype, bs.FreeForAllSession)
 65            or issubclass(sessiontype, bs.CoopSession)
 66        )
 67
 68    def __init__(self, settings: dict):
 69        super().__init__(settings)
 70
 71        self._epic_mode = settings.get('Epic Mode', False)
 72        self._last_player_death_time: float | None = None
 73        self._meteor_time = 2.0
 74        self._timer: OnScreenTimer | None = None
 75        self._ended: bool = False
 76
 77        # Some base class overrides:
 78        self.default_music = (
 79            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
 80        )
 81        if self._epic_mode:
 82            self.slow_motion = True
 83
 84    @override
 85    def on_begin(self) -> None:
 86        super().on_begin()
 87
 88        # Drop a wave every few seconds.. and every so often drop the time
 89        # between waves ..lets have things increase faster if we have fewer
 90        # players.
 91        delay = 5.0 if len(self.players) > 2 else 2.5
 92        if self._epic_mode:
 93            delay *= 0.25
 94        bs.timer(delay, self._decrement_meteor_time, repeat=True)
 95
 96        # Kick off the first wave in a few seconds.
 97        delay = 3.0
 98        if self._epic_mode:
 99            delay *= 0.25
100        bs.timer(delay, self._set_meteor_timer)
101
102        self._timer = OnScreenTimer()
103        self._timer.start()
104
105        # Check for immediate end (if we've only got 1 player, etc).
106        bs.timer(5.0, self._check_end_game)
107
108    @override
109    def on_player_leave(self, player: Player) -> None:
110        # Augment default behavior.
111        super().on_player_leave(player)
112
113        # A departing player may trigger game-over.
114        self._check_end_game()
115
116    # overriding the default character spawning..
117    @override
118    def spawn_player(self, player: Player) -> bs.Actor:
119        spaz = self.spawn_player_spaz(player)
120
121        # Let's reconnect this player's controls to this
122        # spaz but *without* the ability to attack or pick stuff up.
123        spaz.connect_controls_to_player(
124            enable_punch=False, enable_bomb=False, enable_pickup=False
125        )
126
127        # Also lets have them make some noise when they die.
128        spaz.play_big_death_sound = True
129        return spaz
130
131    # Various high-level game events come through this method.
132    @override
133    def handlemessage(self, msg: Any) -> Any:
134        if isinstance(msg, bs.PlayerDiedMessage):
135            # Augment standard behavior.
136            super().handlemessage(msg)
137
138            curtime = bs.time()
139
140            # Record the player's moment of death.
141            # assert isinstance(msg.spaz.player
142            msg.getplayer(Player).death_time = curtime
143
144            # In co-op mode, end the game the instant everyone dies
145            # (more accurate looking).
146            # In teams/ffa, allow a one-second fudge-factor so we can
147            # get more draws if players die basically at the same time.
148            if isinstance(self.session, bs.CoopSession):
149                # Teams will still show up if we check now.. check in
150                # the next cycle.
151                bs.pushcall(self._check_end_game)
152
153                # Also record this for a final setting of the clock.
154                self._last_player_death_time = curtime
155            else:
156                bs.timer(1.0, self._check_end_game)
157
158        else:
159            # Default handler:
160            return super().handlemessage(msg)
161        return None
162
163    def _check_end_game(self) -> None:
164        # We don't want to end this activity more than once.
165        if self._ended:
166            return
167
168        living_team_count = 0
169        for team in self.teams:
170            for player in team.players:
171                if player.is_alive():
172                    living_team_count += 1
173                    break
174
175        # In co-op, we go till everyone is dead.. otherwise we go
176        # until one team remains.
177        if isinstance(self.session, bs.CoopSession):
178            if living_team_count <= 0:
179                self.end_game()
180        else:
181            if living_team_count <= 1:
182                self.end_game()
183
184    def _set_meteor_timer(self) -> None:
185        bs.timer(
186            (1.0 + 0.2 * random.random()) * self._meteor_time,
187            self._drop_bomb_cluster,
188        )
189
190    def _drop_bomb_cluster(self) -> None:
191        # Random note: code like this is a handy way to plot out extents
192        # and debug things.
193        loc_test = False
194        if loc_test:
195            bs.newnode('locator', attrs={'position': (8, 6, -5.5)})
196            bs.newnode('locator', attrs={'position': (8, 6, -2.3)})
197            bs.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
198            bs.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
199
200        # Drop several bombs in series.
201        delay = 0.0
202        for _i in range(random.randrange(1, 3)):
203            # Drop them somewhere within our bounds with velocity pointing
204            # toward the opposite side.
205            pos = (
206                -7.3 + 15.3 * random.random(),
207                11,
208                -5.57 + 2.1 * random.random(),
209            )
210            dropdir = -1.0 if pos[0] > 0 else 1.0
211            vel = (
212                (-5.0 + random.random() * 30.0) * dropdir,
213                random.uniform(-3.066, -4.12),
214                0,
215            )
216            bs.timer(delay, bs.Call(self._drop_bomb, pos, vel))
217            delay += 0.1
218        self._set_meteor_timer()
219
220    def _drop_bomb(
221        self, position: Sequence[float], velocity: Sequence[float]
222    ) -> None:
223        Bomb(position=position, velocity=velocity).autoretain()
224
225    def _decrement_meteor_time(self) -> None:
226        self._meteor_time = max(0.01, self._meteor_time * 0.9)
227
228    @override
229    def end_game(self) -> None:
230        cur_time = bs.time()
231        assert self._timer is not None
232        start_time = self._timer.getstarttime()
233
234        # Mark death-time as now for any still-living players
235        # and award players points for how long they lasted.
236        # (these per-player scores are only meaningful in team-games)
237        for team in self.teams:
238            for player in team.players:
239                survived = False
240
241                # Throw an extra fudge factor in so teams that
242                # didn't die come out ahead of teams that did.
243                if player.death_time is None:
244                    survived = True
245                    player.death_time = cur_time + 1
246
247                # Award a per-player score depending on how many seconds
248                # they lasted (per-player scores only affect teams mode;
249                # everywhere else just looks at the per-team score).
250                score = int(player.death_time - self._timer.getstarttime())
251                if survived:
252                    score += 50  # A bit extra for survivors.
253                self.stats.player_scored(player, score, screenmessage=False)
254
255        # Stop updating our time text, and set the final time to match
256        # exactly when our last guy died.
257        self._timer.stop(endtime=self._last_player_death_time)
258
259        # Ok now calc game results: set a score for each team and then tell
260        # the game to end.
261        results = bs.GameResults()
262
263        # Remember that 'free-for-all' mode is simply a special form
264        # of 'teams' mode where each player gets their own team, so we can
265        # just always deal in teams and have all cases covered.
266        for team in self.teams:
267            # Set the team score to the max time survived by any player on
268            # that team.
269            longest_life = 0.0
270            for player in team.players:
271                assert player.death_time is not None
272                longest_life = max(longest_life, player.death_time - start_time)
273
274            # Submit the score value in milliseconds.
275            results.set_team_score(team, int(1000.0 * longest_life))
276
277        self._ended = True
278        self.end(results=results)
class Player(bascenev1._player.Player[ForwardRef('Team')]):
23class Player(bs.Player['Team']):
24    """Our player type for this game."""
25
26    def __init__(self) -> None:
27        super().__init__()
28        self.death_time: float | None = None

Our player type for this game.

death_time: float | None
class Team(bascenev1._team.Team[bascenev1lib.game.meteorshower.Player]):
31class Team(bs.Team[Player]):
32    """Our team type for this game."""

Our team type for this game.

class MeteorShowerGame(bascenev1._teamgame.TeamGameActivity[bascenev1lib.game.meteorshower.Player, bascenev1lib.game.meteorshower.Team]):
 36class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
 37    """Minigame involving dodging falling bombs."""
 38
 39    name = 'Meteor Shower'
 40    description = 'Dodge the falling bombs.'
 41    available_settings = [bs.BoolSetting('Epic Mode', default=False)]
 42    scoreconfig = bs.ScoreConfig(
 43        label='Survived', scoretype=bs.ScoreType.MILLISECONDS, version='B'
 44    )
 45
 46    # Print messages when players die (since its meaningful in this game).
 47    announce_player_deaths = True
 48
 49    # Don't allow joining after we start
 50    # (would enable leave/rejoin tomfoolery).
 51    allow_mid_activity_joins = False
 52
 53    # We're currently hard-coded for one map.
 54    @override
 55    @classmethod
 56    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
 57        return ['Rampage']
 58
 59    # We support teams, free-for-all, and co-op sessions.
 60    @override
 61    @classmethod
 62    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
 63        return (
 64            issubclass(sessiontype, bs.DualTeamSession)
 65            or issubclass(sessiontype, bs.FreeForAllSession)
 66            or issubclass(sessiontype, bs.CoopSession)
 67        )
 68
 69    def __init__(self, settings: dict):
 70        super().__init__(settings)
 71
 72        self._epic_mode = settings.get('Epic Mode', False)
 73        self._last_player_death_time: float | None = None
 74        self._meteor_time = 2.0
 75        self._timer: OnScreenTimer | None = None
 76        self._ended: bool = False
 77
 78        # Some base class overrides:
 79        self.default_music = (
 80            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
 81        )
 82        if self._epic_mode:
 83            self.slow_motion = True
 84
 85    @override
 86    def on_begin(self) -> None:
 87        super().on_begin()
 88
 89        # Drop a wave every few seconds.. and every so often drop the time
 90        # between waves ..lets have things increase faster if we have fewer
 91        # players.
 92        delay = 5.0 if len(self.players) > 2 else 2.5
 93        if self._epic_mode:
 94            delay *= 0.25
 95        bs.timer(delay, self._decrement_meteor_time, repeat=True)
 96
 97        # Kick off the first wave in a few seconds.
 98        delay = 3.0
 99        if self._epic_mode:
100            delay *= 0.25
101        bs.timer(delay, self._set_meteor_timer)
102
103        self._timer = OnScreenTimer()
104        self._timer.start()
105
106        # Check for immediate end (if we've only got 1 player, etc).
107        bs.timer(5.0, self._check_end_game)
108
109    @override
110    def on_player_leave(self, player: Player) -> None:
111        # Augment default behavior.
112        super().on_player_leave(player)
113
114        # A departing player may trigger game-over.
115        self._check_end_game()
116
117    # overriding the default character spawning..
118    @override
119    def spawn_player(self, player: Player) -> bs.Actor:
120        spaz = self.spawn_player_spaz(player)
121
122        # Let's reconnect this player's controls to this
123        # spaz but *without* the ability to attack or pick stuff up.
124        spaz.connect_controls_to_player(
125            enable_punch=False, enable_bomb=False, enable_pickup=False
126        )
127
128        # Also lets have them make some noise when they die.
129        spaz.play_big_death_sound = True
130        return spaz
131
132    # Various high-level game events come through this method.
133    @override
134    def handlemessage(self, msg: Any) -> Any:
135        if isinstance(msg, bs.PlayerDiedMessage):
136            # Augment standard behavior.
137            super().handlemessage(msg)
138
139            curtime = bs.time()
140
141            # Record the player's moment of death.
142            # assert isinstance(msg.spaz.player
143            msg.getplayer(Player).death_time = curtime
144
145            # In co-op mode, end the game the instant everyone dies
146            # (more accurate looking).
147            # In teams/ffa, allow a one-second fudge-factor so we can
148            # get more draws if players die basically at the same time.
149            if isinstance(self.session, bs.CoopSession):
150                # Teams will still show up if we check now.. check in
151                # the next cycle.
152                bs.pushcall(self._check_end_game)
153
154                # Also record this for a final setting of the clock.
155                self._last_player_death_time = curtime
156            else:
157                bs.timer(1.0, self._check_end_game)
158
159        else:
160            # Default handler:
161            return super().handlemessage(msg)
162        return None
163
164    def _check_end_game(self) -> None:
165        # We don't want to end this activity more than once.
166        if self._ended:
167            return
168
169        living_team_count = 0
170        for team in self.teams:
171            for player in team.players:
172                if player.is_alive():
173                    living_team_count += 1
174                    break
175
176        # In co-op, we go till everyone is dead.. otherwise we go
177        # until one team remains.
178        if isinstance(self.session, bs.CoopSession):
179            if living_team_count <= 0:
180                self.end_game()
181        else:
182            if living_team_count <= 1:
183                self.end_game()
184
185    def _set_meteor_timer(self) -> None:
186        bs.timer(
187            (1.0 + 0.2 * random.random()) * self._meteor_time,
188            self._drop_bomb_cluster,
189        )
190
191    def _drop_bomb_cluster(self) -> None:
192        # Random note: code like this is a handy way to plot out extents
193        # and debug things.
194        loc_test = False
195        if loc_test:
196            bs.newnode('locator', attrs={'position': (8, 6, -5.5)})
197            bs.newnode('locator', attrs={'position': (8, 6, -2.3)})
198            bs.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
199            bs.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
200
201        # Drop several bombs in series.
202        delay = 0.0
203        for _i in range(random.randrange(1, 3)):
204            # Drop them somewhere within our bounds with velocity pointing
205            # toward the opposite side.
206            pos = (
207                -7.3 + 15.3 * random.random(),
208                11,
209                -5.57 + 2.1 * random.random(),
210            )
211            dropdir = -1.0 if pos[0] > 0 else 1.0
212            vel = (
213                (-5.0 + random.random() * 30.0) * dropdir,
214                random.uniform(-3.066, -4.12),
215                0,
216            )
217            bs.timer(delay, bs.Call(self._drop_bomb, pos, vel))
218            delay += 0.1
219        self._set_meteor_timer()
220
221    def _drop_bomb(
222        self, position: Sequence[float], velocity: Sequence[float]
223    ) -> None:
224        Bomb(position=position, velocity=velocity).autoretain()
225
226    def _decrement_meteor_time(self) -> None:
227        self._meteor_time = max(0.01, self._meteor_time * 0.9)
228
229    @override
230    def end_game(self) -> None:
231        cur_time = bs.time()
232        assert self._timer is not None
233        start_time = self._timer.getstarttime()
234
235        # Mark death-time as now for any still-living players
236        # and award players points for how long they lasted.
237        # (these per-player scores are only meaningful in team-games)
238        for team in self.teams:
239            for player in team.players:
240                survived = False
241
242                # Throw an extra fudge factor in so teams that
243                # didn't die come out ahead of teams that did.
244                if player.death_time is None:
245                    survived = True
246                    player.death_time = cur_time + 1
247
248                # Award a per-player score depending on how many seconds
249                # they lasted (per-player scores only affect teams mode;
250                # everywhere else just looks at the per-team score).
251                score = int(player.death_time - self._timer.getstarttime())
252                if survived:
253                    score += 50  # A bit extra for survivors.
254                self.stats.player_scored(player, score, screenmessage=False)
255
256        # Stop updating our time text, and set the final time to match
257        # exactly when our last guy died.
258        self._timer.stop(endtime=self._last_player_death_time)
259
260        # Ok now calc game results: set a score for each team and then tell
261        # the game to end.
262        results = bs.GameResults()
263
264        # Remember that 'free-for-all' mode is simply a special form
265        # of 'teams' mode where each player gets their own team, so we can
266        # just always deal in teams and have all cases covered.
267        for team in self.teams:
268            # Set the team score to the max time survived by any player on
269            # that team.
270            longest_life = 0.0
271            for player in team.players:
272                assert player.death_time is not None
273                longest_life = max(longest_life, player.death_time - start_time)
274
275            # Submit the score value in milliseconds.
276            results.set_team_score(team, int(1000.0 * longest_life))
277
278        self._ended = True
279        self.end(results=results)

Minigame involving dodging falling bombs.

MeteorShowerGame(settings: dict)
69    def __init__(self, settings: dict):
70        super().__init__(settings)
71
72        self._epic_mode = settings.get('Epic Mode', False)
73        self._last_player_death_time: float | None = None
74        self._meteor_time = 2.0
75        self._timer: OnScreenTimer | None = None
76        self._ended: bool = False
77
78        # Some base class overrides:
79        self.default_music = (
80            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
81        )
82        if self._epic_mode:
83            self.slow_motion = True

Instantiate the Activity.

name = 'Meteor Shower'
description = 'Dodge the falling bombs.'
available_settings = [BoolSetting(name='Epic Mode', default=False)]
scoreconfig = ScoreConfig(label='Survived', scoretype=<ScoreType.MILLISECONDS: 'ms'>, lower_is_better=False, none_is_winner=False, version='B')
announce_player_deaths = True

Whether to print every time a player dies. This can be pertinent in games such as Death-Match but can be annoying in games where it doesn't matter.

allow_mid_activity_joins = False

Whether players should be allowed to join in the middle of this activity. Note that Sessions may not allow mid-activity-joins even if the activity says its ok.

@override
@classmethod
def get_supported_maps(cls, sessiontype: type[bascenev1.Session]) -> list[str]:
54    @override
55    @classmethod
56    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
57        return ['Rampage']

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.

@override
@classmethod
def supports_session_type(cls, sessiontype: type[bascenev1.Session]) -> bool:
60    @override
61    @classmethod
62    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
63        return (
64            issubclass(sessiontype, bs.DualTeamSession)
65            or issubclass(sessiontype, bs.FreeForAllSession)
66            or issubclass(sessiontype, bs.CoopSession)
67        )

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

default_music = None
@override
def on_begin(self) -> None:
 85    @override
 86    def on_begin(self) -> None:
 87        super().on_begin()
 88
 89        # Drop a wave every few seconds.. and every so often drop the time
 90        # between waves ..lets have things increase faster if we have fewer
 91        # players.
 92        delay = 5.0 if len(self.players) > 2 else 2.5
 93        if self._epic_mode:
 94            delay *= 0.25
 95        bs.timer(delay, self._decrement_meteor_time, repeat=True)
 96
 97        # Kick off the first wave in a few seconds.
 98        delay = 3.0
 99        if self._epic_mode:
100            delay *= 0.25
101        bs.timer(delay, self._set_meteor_timer)
102
103        self._timer = OnScreenTimer()
104        self._timer.start()
105
106        # Check for immediate end (if we've only got 1 player, etc).
107        bs.timer(5.0, self._check_end_game)

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.

@override
def on_player_leave(self, player: Player) -> None:
109    @override
110    def on_player_leave(self, player: Player) -> None:
111        # Augment default behavior.
112        super().on_player_leave(player)
113
114        # A departing player may trigger game-over.
115        self._check_end_game()

Called when a bascenev1.Player is leaving the Activity.

@override
def spawn_player( self, player: Player) -> bascenev1.Actor:
118    @override
119    def spawn_player(self, player: Player) -> bs.Actor:
120        spaz = self.spawn_player_spaz(player)
121
122        # Let's reconnect this player's controls to this
123        # spaz but *without* the ability to attack or pick stuff up.
124        spaz.connect_controls_to_player(
125            enable_punch=False, enable_bomb=False, enable_pickup=False
126        )
127
128        # Also lets have them make some noise when they die.
129        spaz.play_big_death_sound = True
130        return spaz

Spawn something for the provided bascenev1.Player.

The default implementation simply calls spawn_player_spaz().

@override
def handlemessage(self, msg: Any) -> Any:
133    @override
134    def handlemessage(self, msg: Any) -> Any:
135        if isinstance(msg, bs.PlayerDiedMessage):
136            # Augment standard behavior.
137            super().handlemessage(msg)
138
139            curtime = bs.time()
140
141            # Record the player's moment of death.
142            # assert isinstance(msg.spaz.player
143            msg.getplayer(Player).death_time = curtime
144
145            # In co-op mode, end the game the instant everyone dies
146            # (more accurate looking).
147            # In teams/ffa, allow a one-second fudge-factor so we can
148            # get more draws if players die basically at the same time.
149            if isinstance(self.session, bs.CoopSession):
150                # Teams will still show up if we check now.. check in
151                # the next cycle.
152                bs.pushcall(self._check_end_game)
153
154                # Also record this for a final setting of the clock.
155                self._last_player_death_time = curtime
156            else:
157                bs.timer(1.0, self._check_end_game)
158
159        else:
160            # Default handler:
161            return super().handlemessage(msg)
162        return None

General message handling; can be passed any message object.

@override
def end_game(self) -> None:
229    @override
230    def end_game(self) -> None:
231        cur_time = bs.time()
232        assert self._timer is not None
233        start_time = self._timer.getstarttime()
234
235        # Mark death-time as now for any still-living players
236        # and award players points for how long they lasted.
237        # (these per-player scores are only meaningful in team-games)
238        for team in self.teams:
239            for player in team.players:
240                survived = False
241
242                # Throw an extra fudge factor in so teams that
243                # didn't die come out ahead of teams that did.
244                if player.death_time is None:
245                    survived = True
246                    player.death_time = cur_time + 1
247
248                # Award a per-player score depending on how many seconds
249                # they lasted (per-player scores only affect teams mode;
250                # everywhere else just looks at the per-team score).
251                score = int(player.death_time - self._timer.getstarttime())
252                if survived:
253                    score += 50  # A bit extra for survivors.
254                self.stats.player_scored(player, score, screenmessage=False)
255
256        # Stop updating our time text, and set the final time to match
257        # exactly when our last guy died.
258        self._timer.stop(endtime=self._last_player_death_time)
259
260        # Ok now calc game results: set a score for each team and then tell
261        # the game to end.
262        results = bs.GameResults()
263
264        # Remember that 'free-for-all' mode is simply a special form
265        # of 'teams' mode where each player gets their own team, so we can
266        # just always deal in teams and have all cases covered.
267        for team in self.teams:
268            # Set the team score to the max time survived by any player on
269            # that team.
270            longest_life = 0.0
271            for player in team.players:
272                assert player.death_time is not None
273                longest_life = max(longest_life, player.death_time - start_time)
274
275            # Submit the score value in milliseconds.
276            results.set_team_score(team, int(1000.0 * longest_life))
277
278        self._ended = True
279        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.