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