bascenev1lib.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 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.bomb import Bomb
 14from bascenev1lib.actor.playerspaz import PlayerSpaz
 15from bascenev1lib.actor.spazbot import SpazBotSet, BouncyBot, SpazBotDiedMessage
 16from bascenev1lib.actor.onscreencountdown import OnScreenCountdown
 17from bascenev1lib.actor.scoreboard import Scoreboard
 18from bascenev1lib.actor.respawnicon import RespawnIcon
 19from bascenev1lib.gameutils import SharedObjects
 20import bascenev1 as bs
 21
 22if TYPE_CHECKING:
 23    from typing import Any
 24
 25
 26class Player(bs.Player['Team']):
 27    """Our player type for this game."""
 28
 29    def __init__(self) -> None:
 30        self.respawn_timer: bs.Timer | None = None
 31        self.respawn_icon: RespawnIcon | None = None
 32
 33
 34class Team(bs.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 bascenev1.GameActivity
 42class EasterEggHuntGame(bs.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        bs.BoolSetting('Pro Mode', default=False),
 49        bs.BoolSetting('Epic Mode', default=False),
 50    ]
 51    scoreconfig = bs.ScoreConfig(label='Score', scoretype=bs.ScoreType.POINTS)
 52
 53    # We're currently hard-coded for one map.
 54    @classmethod
 55    def get_supported_maps(cls, sessiontype: type[bs.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[bs.Session]) -> bool:
 61        return (
 62            issubclass(sessiontype, bs.CoopSession)
 63            or issubclass(sessiontype, bs.DualTeamSession)
 64            or issubclass(sessiontype, bs.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_mesh = bs.getmesh('egg')
 73        self.egg_tex_1 = bs.gettexture('eggTex1')
 74        self.egg_tex_2 = bs.gettexture('eggTex2')
 75        self.egg_tex_3 = bs.gettexture('eggTex3')
 76        self._collect_sound = bs.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 = bs.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: bs.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            bs.MusicType.EPIC if self._epic_mode else bs.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 bascenev1lib.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 = bs.Timer(0.25, self._update, repeat=True)
112        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
113        bs.timer(4.0, self._countdown.start)
114        self._bots = SpazBotSet()
115
116        # Spawn evil bunny in co-op only.
117        if isinstance(self.session, bs.CoopSession) and self._pro_mode:
118            self._spawn_evil_bunny()
119
120    # Overriding the default character spawning.
121    def spawn_player(self, player: Player) -> bs.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 = bs.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 bs.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        self._collect_sound.play(0.5, position=egg.node.position)
158
159        # Create a flash.
160        light = bs.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        bs.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False)
170        bs.timer(0.200, light.delete)
171        egg.handlemessage(bs.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            # Occasionally spawn a land-mine in addition.
185            if self._pro_mode and random.random() < 0.25:
186                mine = Bomb(
187                    position=(xpos, ypos, zpos), bomb_type='land_mine'
188                ).autoretain()
189                mine.arm()
190            else:
191                self._eggs.append(Egg(position=(xpos, ypos, zpos)))
192
193    # Various high-level game events come through this method.
194    def handlemessage(self, msg: Any) -> Any:
195        # Respawn dead players.
196        if isinstance(msg, bs.PlayerDiedMessage):
197            # Augment standard behavior.
198            super().handlemessage(msg)
199
200            # Respawn them shortly.
201            player = msg.getplayer(Player)
202            assert self.initialplayerinfos is not None
203            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
204            player.respawn_timer = bs.Timer(
205                respawn_time, bs.Call(self.spawn_player_if_exists, player)
206            )
207            player.respawn_icon = RespawnIcon(player, respawn_time)
208
209        # Whenever our evil bunny dies, respawn him and spew some eggs.
210        elif isinstance(msg, SpazBotDiedMessage):
211            self._spawn_evil_bunny()
212            assert msg.spazbot.node
213            pos = msg.spazbot.node.position
214            for _i in range(6):
215                spread = 0.4
216                self._eggs.append(
217                    Egg(
218                        position=(
219                            pos[0] + random.uniform(-spread, spread),
220                            pos[1] + random.uniform(-spread, spread),
221                            pos[2] + random.uniform(-spread, spread),
222                        )
223                    )
224                )
225        else:
226            # Default handler.
227            return super().handlemessage(msg)
228        return None
229
230    def _update_scoreboard(self) -> None:
231        for team in self.teams:
232            self._scoreboard.set_team_value(team, team.score)
233
234    def end_game(self) -> None:
235        results = bs.GameResults()
236        for team in self.teams:
237            results.set_team_score(team, team.score)
238        self.end(results)
239
240
241class Egg(bs.Actor):
242    """A lovely egg that can be picked up for points."""
243
244    def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)):
245        super().__init__()
246        activity = self.activity
247        assert isinstance(activity, EasterEggHuntGame)
248        shared = SharedObjects.get()
249
250        # Spawn just above the provided point.
251        self._spawn_pos = (position[0], position[1] + 1.0, position[2])
252        ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
253            random.randrange(3)
254        ]
255        mats = [shared.object_material, activity.egg_material]
256        self.node = bs.newnode(
257            'prop',
258            delegate=self,
259            attrs={
260                'mesh': activity.egg_mesh,
261                'color_texture': ctex,
262                'body': 'capsule',
263                'reflection': 'soft',
264                'mesh_scale': 0.5,
265                'body_scale': 0.6,
266                'density': 4.0,
267                'reflection_scale': [0.15],
268                'shadow_size': 0.6,
269                'position': self._spawn_pos,
270                'materials': mats,
271            },
272        )
273
274    def exists(self) -> bool:
275        return bool(self.node)
276
277    def handlemessage(self, msg: Any) -> Any:
278        if isinstance(msg, bs.DieMessage):
279            if self.node:
280                self.node.delete()
281        elif isinstance(msg, bs.HitMessage):
282            if self.node:
283                assert msg.force_direction is not None
284                self.node.handlemessage(
285                    'impulse',
286                    msg.pos[0],
287                    msg.pos[1],
288                    msg.pos[2],
289                    msg.velocity[0],
290                    msg.velocity[1],
291                    msg.velocity[2],
292                    1.0 * msg.magnitude,
293                    1.0 * msg.velocity_magnitude,
294                    msg.radius,
295                    0,
296                    msg.force_direction[0],
297                    msg.force_direction[1],
298                    msg.force_direction[2],
299                )
300        else:
301            super().handlemessage(msg)
class Player(bascenev1._player.Player[ForwardRef('Team')]):
27class Player(bs.Player['Team']):
28    """Our player type for this game."""
29
30    def __init__(self) -> None:
31        self.respawn_timer: bs.Timer | None = None
32        self.respawn_icon: RespawnIcon | None = None

Our player type for this game.

respawn_timer: _bascenev1.Timer | None
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.easteregghunt.Player]):
35class Team(bs.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.

score
Inherited Members
bascenev1._team.Team
players
id
name
color
manual_init
customdata
on_expire
sessionteam
class EasterEggHuntGame(bascenev1._teamgame.TeamGameActivity[bascenev1lib.game.easteregghunt.Player, bascenev1lib.game.easteregghunt.Team]):
 43class EasterEggHuntGame(bs.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        bs.BoolSetting('Pro Mode', default=False),
 50        bs.BoolSetting('Epic Mode', default=False),
 51    ]
 52    scoreconfig = bs.ScoreConfig(label='Score', scoretype=bs.ScoreType.POINTS)
 53
 54    # We're currently hard-coded for one map.
 55    @classmethod
 56    def get_supported_maps(cls, sessiontype: type[bs.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[bs.Session]) -> bool:
 62        return (
 63            issubclass(sessiontype, bs.CoopSession)
 64            or issubclass(sessiontype, bs.DualTeamSession)
 65            or issubclass(sessiontype, bs.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_mesh = bs.getmesh('egg')
 74        self.egg_tex_1 = bs.gettexture('eggTex1')
 75        self.egg_tex_2 = bs.gettexture('eggTex2')
 76        self.egg_tex_3 = bs.gettexture('eggTex3')
 77        self._collect_sound = bs.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 = bs.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: bs.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            bs.MusicType.EPIC if self._epic_mode else bs.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 bascenev1lib.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 = bs.Timer(0.25, self._update, repeat=True)
113        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
114        bs.timer(4.0, self._countdown.start)
115        self._bots = SpazBotSet()
116
117        # Spawn evil bunny in co-op only.
118        if isinstance(self.session, bs.CoopSession) and self._pro_mode:
119            self._spawn_evil_bunny()
120
121    # Overriding the default character spawning.
122    def spawn_player(self, player: Player) -> bs.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 = bs.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 bs.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        self._collect_sound.play(0.5, position=egg.node.position)
159
160        # Create a flash.
161        light = bs.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        bs.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False)
171        bs.timer(0.200, light.delete)
172        egg.handlemessage(bs.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            # 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        # Respawn dead players.
197        if isinstance(msg, bs.PlayerDiedMessage):
198            # Augment standard behavior.
199            super().handlemessage(msg)
200
201            # Respawn them shortly.
202            player = msg.getplayer(Player)
203            assert self.initialplayerinfos is not None
204            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
205            player.respawn_timer = bs.Timer(
206                respawn_time, bs.Call(self.spawn_player_if_exists, player)
207            )
208            player.respawn_icon = RespawnIcon(player, respawn_time)
209
210        # Whenever our evil bunny dies, respawn him and spew some eggs.
211        elif isinstance(msg, SpazBotDiedMessage):
212            self._spawn_evil_bunny()
213            assert msg.spazbot.node
214            pos = msg.spazbot.node.position
215            for _i in range(6):
216                spread = 0.4
217                self._eggs.append(
218                    Egg(
219                        position=(
220                            pos[0] + random.uniform(-spread, spread),
221                            pos[1] + random.uniform(-spread, spread),
222                            pos[2] + random.uniform(-spread, spread),
223                        )
224                    )
225                )
226        else:
227            # Default handler.
228            return super().handlemessage(msg)
229        return None
230
231    def _update_scoreboard(self) -> None:
232        for team in self.teams:
233            self._scoreboard.set_team_value(team, team.score)
234
235    def end_game(self) -> None:
236        results = bs.GameResults()
237        for team in self.teams:
238            results.set_team_score(team, team.score)
239        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_mesh = bs.getmesh('egg')
74        self.egg_tex_1 = bs.gettexture('eggTex1')
75        self.egg_tex_2 = bs.gettexture('eggTex2')
76        self.egg_tex_3 = bs.gettexture('eggTex3')
77        self._collect_sound = bs.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 = bs.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: bs.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            bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FORWARD_MARCH
95        )

Instantiate the Activity.

name = 'Easter Egg Hunt'
description = 'Gather eggs!'
available_settings = [BoolSetting(name='Pro Mode', default=False), BoolSetting(name='Epic Mode', default=False)]
scoreconfig = ScoreConfig(label='Score', scoretype=<ScoreType.POINTS: 'p'>, lower_is_better=False, none_is_winner=False, version='')
@classmethod
def get_supported_maps(cls, sessiontype: type[bascenev1._session.Session]) -> list[str]:
55    @classmethod
56    def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
57        return ['Tower D']

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.

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

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

egg_mesh
egg_tex_1
egg_tex_2
egg_tex_3
egg_material
slow_motion = False

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

default_music = None
def on_team_join(self, team: Team) -> None:
97    def on_team_join(self, team: Team) -> None:
98        if self.has_begun():
99            self._update_scoreboard()

Called when a new bascenev1.Team joins the Activity.

(including the initial set of Teams)

def on_begin(self) -> None:
102    def on_begin(self) -> None:
103        from bascenev1lib.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 = bs.Timer(0.25, self._update, repeat=True)
113        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
114        bs.timer(4.0, self._countdown.start)
115        self._bots = SpazBotSet()
116
117        # Spawn evil bunny in co-op only.
118        if isinstance(self.session, bs.CoopSession) and self._pro_mode:
119            self._spawn_evil_bunny()

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 spawn_player( self, player: Player) -> bascenev1._actor.Actor:
122    def spawn_player(self, player: Player) -> bs.Actor:
123        spaz = self.spawn_player_spaz(player)
124        spaz.connect_controls_to_player()
125        return spaz

Spawn something for the provided bascenev1.Player.

The default implementation simply calls spawn_player_spaz().

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

General message handling; can be passed any message object.

def end_game(self) -> None:
235    def end_game(self) -> None:
236        results = bs.GameResults()
237        for team in self.teams:
238            results.set_team_score(team, team.score)
239        self.end(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.

Inherited Members
bascenev1._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
bascenev1._gameactivity.GameActivity
tips
allow_pausing
allow_kick_idle_players
show_kill_points
create_settings_ui
getscoreconfig
getname
get_display_string
get_team_display_string
get_description
get_description_display_string
get_available_settings
get_settings_display_string
initialplayerinfos
map
get_instance_display_string
get_instance_scoreboard_display_string
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
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
class Egg(bascenev1._actor.Actor):
242class Egg(bs.Actor):
243    """A lovely egg that can be picked up for points."""
244
245    def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)):
246        super().__init__()
247        activity = self.activity
248        assert isinstance(activity, EasterEggHuntGame)
249        shared = SharedObjects.get()
250
251        # Spawn just above the provided point.
252        self._spawn_pos = (position[0], position[1] + 1.0, position[2])
253        ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
254            random.randrange(3)
255        ]
256        mats = [shared.object_material, activity.egg_material]
257        self.node = bs.newnode(
258            'prop',
259            delegate=self,
260            attrs={
261                'mesh': activity.egg_mesh,
262                'color_texture': ctex,
263                'body': 'capsule',
264                'reflection': 'soft',
265                'mesh_scale': 0.5,
266                'body_scale': 0.6,
267                'density': 4.0,
268                'reflection_scale': [0.15],
269                'shadow_size': 0.6,
270                'position': self._spawn_pos,
271                'materials': mats,
272            },
273        )
274
275    def exists(self) -> bool:
276        return bool(self.node)
277
278    def handlemessage(self, msg: Any) -> Any:
279        if isinstance(msg, bs.DieMessage):
280            if self.node:
281                self.node.delete()
282        elif isinstance(msg, bs.HitMessage):
283            if self.node:
284                assert msg.force_direction is not None
285                self.node.handlemessage(
286                    'impulse',
287                    msg.pos[0],
288                    msg.pos[1],
289                    msg.pos[2],
290                    msg.velocity[0],
291                    msg.velocity[1],
292                    msg.velocity[2],
293                    1.0 * msg.magnitude,
294                    1.0 * msg.velocity_magnitude,
295                    msg.radius,
296                    0,
297                    msg.force_direction[0],
298                    msg.force_direction[1],
299                    msg.force_direction[2],
300                )
301        else:
302            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))
245    def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)):
246        super().__init__()
247        activity = self.activity
248        assert isinstance(activity, EasterEggHuntGame)
249        shared = SharedObjects.get()
250
251        # Spawn just above the provided point.
252        self._spawn_pos = (position[0], position[1] + 1.0, position[2])
253        ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
254            random.randrange(3)
255        ]
256        mats = [shared.object_material, activity.egg_material]
257        self.node = bs.newnode(
258            'prop',
259            delegate=self,
260            attrs={
261                'mesh': activity.egg_mesh,
262                'color_texture': ctex,
263                'body': 'capsule',
264                'reflection': 'soft',
265                'mesh_scale': 0.5,
266                'body_scale': 0.6,
267                'density': 4.0,
268                'reflection_scale': [0.15],
269                'shadow_size': 0.6,
270                'position': self._spawn_pos,
271                'materials': mats,
272            },
273        )

Instantiates an Actor in the current bascenev1.Activity.

node
def exists(self) -> bool:
275    def exists(self) -> bool:
276        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 bascenev1.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 bascenev1.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:
278    def handlemessage(self, msg: Any) -> Any:
279        if isinstance(msg, bs.DieMessage):
280            if self.node:
281                self.node.delete()
282        elif isinstance(msg, bs.HitMessage):
283            if self.node:
284                assert msg.force_direction is not None
285                self.node.handlemessage(
286                    'impulse',
287                    msg.pos[0],
288                    msg.pos[1],
289                    msg.pos[2],
290                    msg.velocity[0],
291                    msg.velocity[1],
292                    msg.velocity[2],
293                    1.0 * msg.magnitude,
294                    1.0 * msg.velocity_magnitude,
295                    msg.radius,
296                    0,
297                    msg.force_direction[0],
298                    msg.force_direction[1],
299                    msg.force_direction[2],
300                )
301        else:
302            super().handlemessage(msg)

General message handling; can be passed any message object.

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