bastd.game.easteregghunt

Provides an easter egg hunt game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides an easter egg hunt game."""
  4
  5# ba_meta require api 7
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import random
 11from typing import TYPE_CHECKING
 12
 13import ba
 14from bastd.actor.bomb import Bomb
 15from bastd.actor.playerspaz import PlayerSpaz
 16from bastd.actor.spazbot import SpazBotSet, BouncyBot, SpazBotDiedMessage
 17from bastd.actor.onscreencountdown import OnScreenCountdown
 18from bastd.actor.scoreboard import Scoreboard
 19from bastd.actor.respawnicon import RespawnIcon
 20from bastd.gameutils import SharedObjects
 21
 22if TYPE_CHECKING:
 23    from typing import Any
 24
 25
 26class Player(ba.Player['Team']):
 27    """Our player type for this game."""
 28
 29    def __init__(self) -> None:
 30        self.respawn_timer: ba.Timer | None = None
 31        self.respawn_icon: RespawnIcon | None = None
 32
 33
 34class Team(ba.Team[Player]):
 35    """Our team type for this game."""
 36
 37    def __init__(self) -> None:
 38        self.score = 0
 39
 40
 41# ba_meta export game
 42class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
 43    """A game where score is based on collecting eggs."""
 44
 45    name = 'Easter Egg Hunt'
 46    description = 'Gather eggs!'
 47    available_settings = [
 48        ba.BoolSetting('Pro Mode', default=False),
 49        ba.BoolSetting('Epic Mode', default=False),
 50    ]
 51    scoreconfig = ba.ScoreConfig(label='Score', scoretype=ba.ScoreType.POINTS)
 52
 53    # We're currently hard-coded for one map.
 54    @classmethod
 55    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 56        return ['Tower D']
 57
 58    # We support teams, free-for-all, and co-op sessions.
 59    @classmethod
 60    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 61        return (
 62            issubclass(sessiontype, ba.CoopSession)
 63            or issubclass(sessiontype, ba.DualTeamSession)
 64            or issubclass(sessiontype, ba.FreeForAllSession)
 65        )
 66
 67    def __init__(self, settings: dict):
 68        super().__init__(settings)
 69        shared = SharedObjects.get()
 70        self._last_player_death_time = None
 71        self._scoreboard = Scoreboard()
 72        self.egg_model = ba.getmodel('egg')
 73        self.egg_tex_1 = ba.gettexture('eggTex1')
 74        self.egg_tex_2 = ba.gettexture('eggTex2')
 75        self.egg_tex_3 = ba.gettexture('eggTex3')
 76        self._collect_sound = ba.getsound('powerup01')
 77        self._pro_mode = settings.get('Pro Mode', False)
 78        self._epic_mode = settings.get('Epic Mode', False)
 79        self._max_eggs = 1.0
 80        self.egg_material = ba.Material()
 81        self.egg_material.add_actions(
 82            conditions=('they_have_material', shared.player_material),
 83            actions=(('call', 'at_connect', self._on_egg_player_collide),),
 84        )
 85        self._eggs: list[Egg] = []
 86        self._update_timer: ba.Timer | None = None
 87        self._countdown: OnScreenCountdown | None = None
 88        self._bots: SpazBotSet | None = None
 89
 90        # Base class overrides
 91        self.slow_motion = self._epic_mode
 92        self.default_music = (
 93            ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH
 94        )
 95
 96    def on_team_join(self, team: Team) -> None:
 97        if self.has_begun():
 98            self._update_scoreboard()
 99
100    # Called when our game actually starts.
101    def on_begin(self) -> None:
102        from bastd.maps import TowerD
103
104        # There's a player-wall on the tower-d level to prevent
105        # players from getting up on the stairs.. we wanna kill that.
106        gamemap = self.map
107        assert isinstance(gamemap, TowerD)
108        gamemap.player_wall.delete()
109        super().on_begin()
110        self._update_scoreboard()
111        self._update_timer = ba.Timer(0.25, self._update, repeat=True)
112        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
113        ba.timer(4.0, self._countdown.start)
114        self._bots = SpazBotSet()
115
116        # Spawn evil bunny in co-op only.
117        if isinstance(self.session, ba.CoopSession) and self._pro_mode:
118            self._spawn_evil_bunny()
119
120    # Overriding the default character spawning.
121    def spawn_player(self, player: Player) -> ba.Actor:
122        spaz = self.spawn_player_spaz(player)
123        spaz.connect_controls_to_player()
124        return spaz
125
126    def _spawn_evil_bunny(self) -> None:
127        assert self._bots is not None
128        self._bots.spawn_bot(BouncyBot, pos=(6, 4, -7.8), spawn_time=10.0)
129
130    def _on_egg_player_collide(self) -> None:
131        if self.has_ended():
132            return
133        collision = ba.getcollision()
134
135        # Be defensive here; we could be hitting the corpse of a player
136        # who just left/etc.
137        try:
138            egg = collision.sourcenode.getdelegate(Egg, True)
139            player = collision.opposingnode.getdelegate(
140                PlayerSpaz, True
141            ).getplayer(Player, True)
142        except ba.NotFoundError:
143            return
144
145        player.team.score += 1
146
147        # Displays a +1 (and adds to individual player score in
148        # teams mode).
149        self.stats.player_scored(player, 1, screenmessage=False)
150        if self._max_eggs < 5:
151            self._max_eggs += 1.0
152        elif self._max_eggs < 10:
153            self._max_eggs += 0.5
154        elif self._max_eggs < 30:
155            self._max_eggs += 0.3
156        self._update_scoreboard()
157        ba.playsound(self._collect_sound, 0.5, position=egg.node.position)
158
159        # Create a flash.
160        light = ba.newnode(
161            'light',
162            attrs={
163                'position': egg.node.position,
164                'height_attenuated': False,
165                'radius': 0.1,
166                'color': (1, 1, 0),
167            },
168        )
169        ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False)
170        ba.timer(0.200, light.delete)
171        egg.handlemessage(ba.DieMessage())
172
173    def _update(self) -> None:
174        # Misc. periodic updating.
175        xpos = random.uniform(-7.1, 6.0)
176        ypos = random.uniform(3.5, 3.5)
177        zpos = random.uniform(-8.2, 3.7)
178
179        # Prune dead eggs from our list.
180        self._eggs = [e for e in self._eggs if e]
181
182        # Spawn more eggs if we've got space.
183        if len(self._eggs) < int(self._max_eggs):
184
185            # Occasionally spawn a land-mine in addition.
186            if self._pro_mode and random.random() < 0.25:
187                mine = Bomb(
188                    position=(xpos, ypos, zpos), bomb_type='land_mine'
189                ).autoretain()
190                mine.arm()
191            else:
192                self._eggs.append(Egg(position=(xpos, ypos, zpos)))
193
194    # Various high-level game events come through this method.
195    def handlemessage(self, msg: Any) -> Any:
196
197        # Respawn dead players.
198        if isinstance(msg, ba.PlayerDiedMessage):
199            # Augment standard behavior.
200            super().handlemessage(msg)
201
202            # Respawn them shortly.
203            player = msg.getplayer(Player)
204            assert self.initialplayerinfos is not None
205            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
206            player.respawn_timer = ba.Timer(
207                respawn_time, ba.Call(self.spawn_player_if_exists, player)
208            )
209            player.respawn_icon = RespawnIcon(player, respawn_time)
210
211        # Whenever our evil bunny dies, respawn him and spew some eggs.
212        elif isinstance(msg, SpazBotDiedMessage):
213            self._spawn_evil_bunny()
214            assert msg.spazbot.node
215            pos = msg.spazbot.node.position
216            for _i in range(6):
217                spread = 0.4
218                self._eggs.append(
219                    Egg(
220                        position=(
221                            pos[0] + random.uniform(-spread, spread),
222                            pos[1] + random.uniform(-spread, spread),
223                            pos[2] + random.uniform(-spread, spread),
224                        )
225                    )
226                )
227        else:
228            # Default handler.
229            return super().handlemessage(msg)
230        return None
231
232    def _update_scoreboard(self) -> None:
233        for team in self.teams:
234            self._scoreboard.set_team_value(team, team.score)
235
236    def end_game(self) -> None:
237        results = ba.GameResults()
238        for team in self.teams:
239            results.set_team_score(team, team.score)
240        self.end(results)
241
242
243class Egg(ba.Actor):
244    """A lovely egg that can be picked up for points."""
245
246    def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)):
247        super().__init__()
248        activity = self.activity
249        assert isinstance(activity, EasterEggHuntGame)
250        shared = SharedObjects.get()
251
252        # Spawn just above the provided point.
253        self._spawn_pos = (position[0], position[1] + 1.0, position[2])
254        ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
255            random.randrange(3)
256        ]
257        mats = [shared.object_material, activity.egg_material]
258        self.node = ba.newnode(
259            'prop',
260            delegate=self,
261            attrs={
262                'model': activity.egg_model,
263                'color_texture': ctex,
264                'body': 'capsule',
265                'reflection': 'soft',
266                'model_scale': 0.5,
267                'body_scale': 0.6,
268                'density': 4.0,
269                'reflection_scale': [0.15],
270                'shadow_size': 0.6,
271                'position': self._spawn_pos,
272                'materials': mats,
273            },
274        )
275
276    def exists(self) -> bool:
277        return bool(self.node)
278
279    def handlemessage(self, msg: Any) -> Any:
280        if isinstance(msg, ba.DieMessage):
281            if self.node:
282                self.node.delete()
283        elif isinstance(msg, ba.HitMessage):
284            if self.node:
285                assert msg.force_direction is not None
286                self.node.handlemessage(
287                    'impulse',
288                    msg.pos[0],
289                    msg.pos[1],
290                    msg.pos[2],
291                    msg.velocity[0],
292                    msg.velocity[1],
293                    msg.velocity[2],
294                    1.0 * msg.magnitude,
295                    1.0 * msg.velocity_magnitude,
296                    msg.radius,
297                    0,
298                    msg.force_direction[0],
299                    msg.force_direction[1],
300                    msg.force_direction[2],
301                )
302        else:
303            super().handlemessage(msg)
class Player(ba._player.Player[ForwardRef('Team')]):
27class Player(ba.Player['Team']):
28    """Our player type for this game."""
29
30    def __init__(self) -> None:
31        self.respawn_timer: ba.Timer | None = None
32        self.respawn_icon: RespawnIcon | None = None

Our player type for this game.

Player()
30    def __init__(self) -> None:
31        self.respawn_timer: ba.Timer | None = None
32        self.respawn_icon: RespawnIcon | None = None
Inherited Members
ba._player.Player
actor
on_expire
team
customdata
sessionplayer
node
position
exists
getname
is_alive
get_icon
assigninput
resetinput
class Team(ba._team.Team[bastd.game.easteregghunt.Player]):
35class Team(ba.Team[Player]):
36    """Our team type for this game."""
37
38    def __init__(self) -> None:
39        self.score = 0

Our team type for this game.

Team()
38    def __init__(self) -> None:
39        self.score = 0
Inherited Members
ba._team.Team
manual_init
customdata
on_expire
sessionteam
class EasterEggHuntGame(ba._teamgame.TeamGameActivity[bastd.game.easteregghunt.Player, bastd.game.easteregghunt.Team]):
 43class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
 44    """A game where score is based on collecting eggs."""
 45
 46    name = 'Easter Egg Hunt'
 47    description = 'Gather eggs!'
 48    available_settings = [
 49        ba.BoolSetting('Pro Mode', default=False),
 50        ba.BoolSetting('Epic Mode', default=False),
 51    ]
 52    scoreconfig = ba.ScoreConfig(label='Score', scoretype=ba.ScoreType.POINTS)
 53
 54    # We're currently hard-coded for one map.
 55    @classmethod
 56    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 57        return ['Tower D']
 58
 59    # We support teams, free-for-all, and co-op sessions.
 60    @classmethod
 61    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 62        return (
 63            issubclass(sessiontype, ba.CoopSession)
 64            or issubclass(sessiontype, ba.DualTeamSession)
 65            or issubclass(sessiontype, ba.FreeForAllSession)
 66        )
 67
 68    def __init__(self, settings: dict):
 69        super().__init__(settings)
 70        shared = SharedObjects.get()
 71        self._last_player_death_time = None
 72        self._scoreboard = Scoreboard()
 73        self.egg_model = ba.getmodel('egg')
 74        self.egg_tex_1 = ba.gettexture('eggTex1')
 75        self.egg_tex_2 = ba.gettexture('eggTex2')
 76        self.egg_tex_3 = ba.gettexture('eggTex3')
 77        self._collect_sound = ba.getsound('powerup01')
 78        self._pro_mode = settings.get('Pro Mode', False)
 79        self._epic_mode = settings.get('Epic Mode', False)
 80        self._max_eggs = 1.0
 81        self.egg_material = ba.Material()
 82        self.egg_material.add_actions(
 83            conditions=('they_have_material', shared.player_material),
 84            actions=(('call', 'at_connect', self._on_egg_player_collide),),
 85        )
 86        self._eggs: list[Egg] = []
 87        self._update_timer: ba.Timer | None = None
 88        self._countdown: OnScreenCountdown | None = None
 89        self._bots: SpazBotSet | None = None
 90
 91        # Base class overrides
 92        self.slow_motion = self._epic_mode
 93        self.default_music = (
 94            ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH
 95        )
 96
 97    def on_team_join(self, team: Team) -> None:
 98        if self.has_begun():
 99            self._update_scoreboard()
100
101    # Called when our game actually starts.
102    def on_begin(self) -> None:
103        from bastd.maps import TowerD
104
105        # There's a player-wall on the tower-d level to prevent
106        # players from getting up on the stairs.. we wanna kill that.
107        gamemap = self.map
108        assert isinstance(gamemap, TowerD)
109        gamemap.player_wall.delete()
110        super().on_begin()
111        self._update_scoreboard()
112        self._update_timer = ba.Timer(0.25, self._update, repeat=True)
113        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
114        ba.timer(4.0, self._countdown.start)
115        self._bots = SpazBotSet()
116
117        # Spawn evil bunny in co-op only.
118        if isinstance(self.session, ba.CoopSession) and self._pro_mode:
119            self._spawn_evil_bunny()
120
121    # Overriding the default character spawning.
122    def spawn_player(self, player: Player) -> ba.Actor:
123        spaz = self.spawn_player_spaz(player)
124        spaz.connect_controls_to_player()
125        return spaz
126
127    def _spawn_evil_bunny(self) -> None:
128        assert self._bots is not None
129        self._bots.spawn_bot(BouncyBot, pos=(6, 4, -7.8), spawn_time=10.0)
130
131    def _on_egg_player_collide(self) -> None:
132        if self.has_ended():
133            return
134        collision = ba.getcollision()
135
136        # Be defensive here; we could be hitting the corpse of a player
137        # who just left/etc.
138        try:
139            egg = collision.sourcenode.getdelegate(Egg, True)
140            player = collision.opposingnode.getdelegate(
141                PlayerSpaz, True
142            ).getplayer(Player, True)
143        except ba.NotFoundError:
144            return
145
146        player.team.score += 1
147
148        # Displays a +1 (and adds to individual player score in
149        # teams mode).
150        self.stats.player_scored(player, 1, screenmessage=False)
151        if self._max_eggs < 5:
152            self._max_eggs += 1.0
153        elif self._max_eggs < 10:
154            self._max_eggs += 0.5
155        elif self._max_eggs < 30:
156            self._max_eggs += 0.3
157        self._update_scoreboard()
158        ba.playsound(self._collect_sound, 0.5, position=egg.node.position)
159
160        # Create a flash.
161        light = ba.newnode(
162            'light',
163            attrs={
164                'position': egg.node.position,
165                'height_attenuated': False,
166                'radius': 0.1,
167                'color': (1, 1, 0),
168            },
169        )
170        ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False)
171        ba.timer(0.200, light.delete)
172        egg.handlemessage(ba.DieMessage())
173
174    def _update(self) -> None:
175        # Misc. periodic updating.
176        xpos = random.uniform(-7.1, 6.0)
177        ypos = random.uniform(3.5, 3.5)
178        zpos = random.uniform(-8.2, 3.7)
179
180        # Prune dead eggs from our list.
181        self._eggs = [e for e in self._eggs if e]
182
183        # Spawn more eggs if we've got space.
184        if len(self._eggs) < int(self._max_eggs):
185
186            # Occasionally spawn a land-mine in addition.
187            if self._pro_mode and random.random() < 0.25:
188                mine = Bomb(
189                    position=(xpos, ypos, zpos), bomb_type='land_mine'
190                ).autoretain()
191                mine.arm()
192            else:
193                self._eggs.append(Egg(position=(xpos, ypos, zpos)))
194
195    # Various high-level game events come through this method.
196    def handlemessage(self, msg: Any) -> Any:
197
198        # Respawn dead players.
199        if isinstance(msg, ba.PlayerDiedMessage):
200            # Augment standard behavior.
201            super().handlemessage(msg)
202
203            # Respawn them shortly.
204            player = msg.getplayer(Player)
205            assert self.initialplayerinfos is not None
206            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
207            player.respawn_timer = ba.Timer(
208                respawn_time, ba.Call(self.spawn_player_if_exists, player)
209            )
210            player.respawn_icon = RespawnIcon(player, respawn_time)
211
212        # Whenever our evil bunny dies, respawn him and spew some eggs.
213        elif isinstance(msg, SpazBotDiedMessage):
214            self._spawn_evil_bunny()
215            assert msg.spazbot.node
216            pos = msg.spazbot.node.position
217            for _i in range(6):
218                spread = 0.4
219                self._eggs.append(
220                    Egg(
221                        position=(
222                            pos[0] + random.uniform(-spread, spread),
223                            pos[1] + random.uniform(-spread, spread),
224                            pos[2] + random.uniform(-spread, spread),
225                        )
226                    )
227                )
228        else:
229            # Default handler.
230            return super().handlemessage(msg)
231        return None
232
233    def _update_scoreboard(self) -> None:
234        for team in self.teams:
235            self._scoreboard.set_team_value(team, team.score)
236
237    def end_game(self) -> None:
238        results = ba.GameResults()
239        for team in self.teams:
240            results.set_team_score(team, team.score)
241        self.end(results)

A game where score is based on collecting eggs.

EasterEggHuntGame(settings: dict)
68    def __init__(self, settings: dict):
69        super().__init__(settings)
70        shared = SharedObjects.get()
71        self._last_player_death_time = None
72        self._scoreboard = Scoreboard()
73        self.egg_model = ba.getmodel('egg')
74        self.egg_tex_1 = ba.gettexture('eggTex1')
75        self.egg_tex_2 = ba.gettexture('eggTex2')
76        self.egg_tex_3 = ba.gettexture('eggTex3')
77        self._collect_sound = ba.getsound('powerup01')
78        self._pro_mode = settings.get('Pro Mode', False)
79        self._epic_mode = settings.get('Epic Mode', False)
80        self._max_eggs = 1.0
81        self.egg_material = ba.Material()
82        self.egg_material.add_actions(
83            conditions=('they_have_material', shared.player_material),
84            actions=(('call', 'at_connect', self._on_egg_player_collide),),
85        )
86        self._eggs: list[Egg] = []
87        self._update_timer: ba.Timer | None = None
88        self._countdown: OnScreenCountdown | None = None
89        self._bots: SpazBotSet | None = None
90
91        # Base class overrides
92        self.slow_motion = self._epic_mode
93        self.default_music = (
94            ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH
95        )

Instantiate the Activity.

@classmethod
def get_supported_maps(cls, sessiontype: type[ba._session.Session]) -> list[str]:
55    @classmethod
56    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
57        return ['Tower D']

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

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

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

slow_motion = False

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

def on_team_join(self, team: bastd.game.easteregghunt.Team) -> None:
97    def on_team_join(self, team: Team) -> None:
98        if self.has_begun():
99            self._update_scoreboard()

Called when a new ba.Team joins the Activity.

(including the initial set of Teams)

def on_begin(self) -> None:
102    def on_begin(self) -> None:
103        from bastd.maps import TowerD
104
105        # There's a player-wall on the tower-d level to prevent
106        # players from getting up on the stairs.. we wanna kill that.
107        gamemap = self.map
108        assert isinstance(gamemap, TowerD)
109        gamemap.player_wall.delete()
110        super().on_begin()
111        self._update_scoreboard()
112        self._update_timer = ba.Timer(0.25, self._update, repeat=True)
113        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
114        ba.timer(4.0, self._countdown.start)
115        self._bots = SpazBotSet()
116
117        # Spawn evil bunny in co-op only.
118        if isinstance(self.session, ba.CoopSession) and self._pro_mode:
119            self._spawn_evil_bunny()

Called once the previous ba.Activity has finished transitioning out.

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

def spawn_player(self, player: bastd.game.easteregghunt.Player) -> ba._actor.Actor:
122    def spawn_player(self, player: Player) -> ba.Actor:
123        spaz = self.spawn_player_spaz(player)
124        spaz.connect_controls_to_player()
125        return spaz

Spawn something for the provided ba.Player.

The default implementation simply calls spawn_player_spaz().

def handlemessage(self, msg: Any) -> Any:
196    def handlemessage(self, msg: Any) -> Any:
197
198        # Respawn dead players.
199        if isinstance(msg, ba.PlayerDiedMessage):
200            # Augment standard behavior.
201            super().handlemessage(msg)
202
203            # Respawn them shortly.
204            player = msg.getplayer(Player)
205            assert self.initialplayerinfos is not None
206            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
207            player.respawn_timer = ba.Timer(
208                respawn_time, ba.Call(self.spawn_player_if_exists, player)
209            )
210            player.respawn_icon = RespawnIcon(player, respawn_time)
211
212        # Whenever our evil bunny dies, respawn him and spew some eggs.
213        elif isinstance(msg, SpazBotDiedMessage):
214            self._spawn_evil_bunny()
215            assert msg.spazbot.node
216            pos = msg.spazbot.node.position
217            for _i in range(6):
218                spread = 0.4
219                self._eggs.append(
220                    Egg(
221                        position=(
222                            pos[0] + random.uniform(-spread, spread),
223                            pos[1] + random.uniform(-spread, spread),
224                            pos[2] + random.uniform(-spread, spread),
225                        )
226                    )
227                )
228        else:
229            # Default handler.
230            return super().handlemessage(msg)
231        return None

General message handling; can be passed any message object.

def end_game(self) -> None:
237    def end_game(self) -> None:
238        results = ba.GameResults()
239        for team in self.teams:
240            results.set_team_score(team, team.score)
241        self.end(results)

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

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

Inherited Members
ba._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
ba._gameactivity.GameActivity
allow_pausing
allow_kick_idle_players
create_settings_ui
getscoreconfig
getname
get_display_string
get_team_display_string
get_description
get_description_display_string
get_available_settings
get_settings_display_string
map
get_instance_display_string
get_instance_scoreboard_display_string
get_instance_description
get_instance_description_short
on_continue
is_waiting_for_continue
continue_or_end_game
on_player_join
respawn_player
spawn_player_if_exists
setup_standard_powerup_drops
setup_standard_time_limit
show_zoom_message
ba._activity.Activity
settings_raw
teams
players
announce_player_deaths
is_joining_activity
use_fixed_vr_overlay
inherits_slow_motion
inherits_music
inherits_vr_camera_offset
inherits_vr_overlay_center
inherits_tint
allow_mid_activity_joins
transition_time
can_show_ad_on_death
globalsnode
stats
on_expire
customdata
expired
playertype
teamtype
retain_actor
add_actor_weak_ref
session
on_player_leave
on_team_leave
on_transition_out
has_transitioned_in
has_begun
has_ended
is_transitioning_out
transition_out
create_player
create_team
ba._dependency.DependencyComponent
dep_is_present
get_dynamic_deps
class Egg(ba._actor.Actor):
244class Egg(ba.Actor):
245    """A lovely egg that can be picked up for points."""
246
247    def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)):
248        super().__init__()
249        activity = self.activity
250        assert isinstance(activity, EasterEggHuntGame)
251        shared = SharedObjects.get()
252
253        # Spawn just above the provided point.
254        self._spawn_pos = (position[0], position[1] + 1.0, position[2])
255        ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
256            random.randrange(3)
257        ]
258        mats = [shared.object_material, activity.egg_material]
259        self.node = ba.newnode(
260            'prop',
261            delegate=self,
262            attrs={
263                'model': activity.egg_model,
264                'color_texture': ctex,
265                'body': 'capsule',
266                'reflection': 'soft',
267                'model_scale': 0.5,
268                'body_scale': 0.6,
269                'density': 4.0,
270                'reflection_scale': [0.15],
271                'shadow_size': 0.6,
272                'position': self._spawn_pos,
273                'materials': mats,
274            },
275        )
276
277    def exists(self) -> bool:
278        return bool(self.node)
279
280    def handlemessage(self, msg: Any) -> Any:
281        if isinstance(msg, ba.DieMessage):
282            if self.node:
283                self.node.delete()
284        elif isinstance(msg, ba.HitMessage):
285            if self.node:
286                assert msg.force_direction is not None
287                self.node.handlemessage(
288                    'impulse',
289                    msg.pos[0],
290                    msg.pos[1],
291                    msg.pos[2],
292                    msg.velocity[0],
293                    msg.velocity[1],
294                    msg.velocity[2],
295                    1.0 * msg.magnitude,
296                    1.0 * msg.velocity_magnitude,
297                    msg.radius,
298                    0,
299                    msg.force_direction[0],
300                    msg.force_direction[1],
301                    msg.force_direction[2],
302                )
303        else:
304            super().handlemessage(msg)

A lovely egg that can be picked up for points.

Egg(position: tuple[float, float, float] = (0.0, 1.0, 0.0))
247    def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)):
248        super().__init__()
249        activity = self.activity
250        assert isinstance(activity, EasterEggHuntGame)
251        shared = SharedObjects.get()
252
253        # Spawn just above the provided point.
254        self._spawn_pos = (position[0], position[1] + 1.0, position[2])
255        ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
256            random.randrange(3)
257        ]
258        mats = [shared.object_material, activity.egg_material]
259        self.node = ba.newnode(
260            'prop',
261            delegate=self,
262            attrs={
263                'model': activity.egg_model,
264                'color_texture': ctex,
265                'body': 'capsule',
266                'reflection': 'soft',
267                'model_scale': 0.5,
268                'body_scale': 0.6,
269                'density': 4.0,
270                'reflection_scale': [0.15],
271                'shadow_size': 0.6,
272                'position': self._spawn_pos,
273                'materials': mats,
274            },
275        )

Instantiates an Actor in the current ba.Activity.

def exists(self) -> bool:
277    def exists(self) -> bool:
278        return bool(self.node)

Returns whether the Actor is still present in a meaningful way.

Note that a dying character should still return True here as long as their corpse is visible; this is about presence, not being 'alive' (see ba.Actor.is_alive() for that).

If this returns False, it is assumed the Actor can be completely deleted without affecting the game; this call is often used when pruning lists of Actors, such as with ba.Actor.autoretain()

The default implementation of this method always return True.

Note that the boolean operator for the Actor class calls this method, so a simple "if myactor" test will conveniently do the right thing even if myactor is set to None.

def handlemessage(self, msg: Any) -> Any:
280    def handlemessage(self, msg: Any) -> Any:
281        if isinstance(msg, ba.DieMessage):
282            if self.node:
283                self.node.delete()
284        elif isinstance(msg, ba.HitMessage):
285            if self.node:
286                assert msg.force_direction is not None
287                self.node.handlemessage(
288                    'impulse',
289                    msg.pos[0],
290                    msg.pos[1],
291                    msg.pos[2],
292                    msg.velocity[0],
293                    msg.velocity[1],
294                    msg.velocity[2],
295                    1.0 * msg.magnitude,
296                    1.0 * msg.velocity_magnitude,
297                    msg.radius,
298                    0,
299                    msg.force_direction[0],
300                    msg.force_direction[1],
301                    msg.force_direction[2],
302                )
303        else:
304            super().handlemessage(msg)

General message handling; can be passed any message object.

Inherited Members
ba._actor.Actor
autoretain
on_expire
expired
is_alive
activity
getactivity