bascenev1lib.actor.powerupbox

Defines Actor(s).

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Defines Actor(s)."""
  4
  5from __future__ import annotations
  6
  7import random
  8from typing import TYPE_CHECKING, override
  9
 10import bascenev1 as bs
 11
 12from bascenev1lib.gameutils import SharedObjects
 13
 14if TYPE_CHECKING:
 15    from typing import Any, Sequence
 16
 17DEFAULT_POWERUP_INTERVAL = 8.0
 18
 19
 20class _TouchedMessage:
 21    pass
 22
 23
 24class PowerupBoxFactory:
 25    """A collection of media and other resources used by bs.Powerups.
 26
 27    Category: **Gameplay Classes**
 28
 29    A single instance of this is shared between all powerups
 30    and can be retrieved via bs.Powerup.get_factory().
 31    """
 32
 33    mesh: bs.Mesh
 34    """The bs.Mesh of the powerup box."""
 35
 36    mesh_simple: bs.Mesh
 37    """A simpler bs.Mesh of the powerup box, for use in shadows, etc."""
 38
 39    tex_bomb: bs.Texture
 40    """Triple-bomb powerup bs.Texture."""
 41
 42    tex_punch: bs.Texture
 43    """Punch powerup bs.Texture."""
 44
 45    tex_ice_bombs: bs.Texture
 46    """Ice bomb powerup bs.Texture."""
 47
 48    tex_sticky_bombs: bs.Texture
 49    """Sticky bomb powerup bs.Texture."""
 50
 51    tex_shield: bs.Texture
 52    """Shield powerup bs.Texture."""
 53
 54    tex_impact_bombs: bs.Texture
 55    """Impact-bomb powerup bs.Texture."""
 56
 57    tex_health: bs.Texture
 58    """Health powerup bs.Texture."""
 59
 60    tex_land_mines: bs.Texture
 61    """Land-mine powerup bs.Texture."""
 62
 63    tex_curse: bs.Texture
 64    """Curse powerup bs.Texture."""
 65
 66    health_powerup_sound: bs.Sound
 67    """bs.Sound played when a health powerup is accepted."""
 68
 69    powerup_sound: bs.Sound
 70    """bs.Sound played when a powerup is accepted."""
 71
 72    powerdown_sound: bs.Sound
 73    """bs.Sound that can be used when powerups wear off."""
 74
 75    powerup_material: bs.Material
 76    """bs.Material applied to powerup boxes."""
 77
 78    powerup_accept_material: bs.Material
 79    """Powerups will send a bs.PowerupMessage to anything they touch
 80       that has this bs.Material applied."""
 81
 82    _STORENAME = bs.storagename()
 83
 84    def __init__(self) -> None:
 85        """Instantiate a PowerupBoxFactory.
 86
 87        You shouldn't need to do this; call Powerup.get_factory()
 88        to get a shared instance.
 89        """
 90        from bascenev1 import get_default_powerup_distribution
 91
 92        shared = SharedObjects.get()
 93        self._lastpoweruptype: str | None = None
 94        self.mesh = bs.getmesh('powerup')
 95        self.mesh_simple = bs.getmesh('powerupSimple')
 96        self.tex_bomb = bs.gettexture('powerupBomb')
 97        self.tex_punch = bs.gettexture('powerupPunch')
 98        self.tex_ice_bombs = bs.gettexture('powerupIceBombs')
 99        self.tex_sticky_bombs = bs.gettexture('powerupStickyBombs')
100        self.tex_shield = bs.gettexture('powerupShield')
101        self.tex_impact_bombs = bs.gettexture('powerupImpactBombs')
102        self.tex_health = bs.gettexture('powerupHealth')
103        self.tex_land_mines = bs.gettexture('powerupLandMines')
104        self.tex_curse = bs.gettexture('powerupCurse')
105        self.health_powerup_sound = bs.getsound('healthPowerup')
106        self.powerup_sound = bs.getsound('powerup01')
107        self.powerdown_sound = bs.getsound('powerdown01')
108        self.drop_sound = bs.getsound('boxDrop')
109
110        # Material for powerups.
111        self.powerup_material = bs.Material()
112
113        # Material for anyone wanting to accept powerups.
114        self.powerup_accept_material = bs.Material()
115
116        # Pass a powerup-touched message to applicable stuff.
117        self.powerup_material.add_actions(
118            conditions=('they_have_material', self.powerup_accept_material),
119            actions=(
120                ('modify_part_collision', 'collide', True),
121                ('modify_part_collision', 'physical', False),
122                ('message', 'our_node', 'at_connect', _TouchedMessage()),
123            ),
124        )
125
126        # We don't wanna be picked up.
127        self.powerup_material.add_actions(
128            conditions=('they_have_material', shared.pickup_material),
129            actions=('modify_part_collision', 'collide', False),
130        )
131
132        self.powerup_material.add_actions(
133            conditions=('they_have_material', shared.footing_material),
134            actions=('impact_sound', self.drop_sound, 0.5, 0.1),
135        )
136
137        self._powerupdist: list[str] = []
138        for powerup, freq in get_default_powerup_distribution():
139            for _i in range(int(freq)):
140                self._powerupdist.append(powerup)
141
142    def get_random_powerup_type(
143        self,
144        forcetype: str | None = None,
145        excludetypes: list[str] | None = None,
146    ) -> str:
147        """Returns a random powerup type (string).
148
149        See bs.Powerup.poweruptype for available type values.
150
151        There are certain non-random aspects to this; a 'curse' powerup,
152        for instance, is always followed by a 'health' powerup (to keep things
153        interesting). Passing 'forcetype' forces a given returned type while
154        still properly interacting with the non-random aspects of the system
155        (ie: forcing a 'curse' powerup will result
156        in the next powerup being health).
157        """
158        if excludetypes is None:
159            excludetypes = []
160        if forcetype:
161            ptype = forcetype
162        else:
163            # If the last one was a curse, make this one a health to
164            # provide some hope.
165            if self._lastpoweruptype == 'curse':
166                ptype = 'health'
167            else:
168                while True:
169                    ptype = self._powerupdist[
170                        random.randint(0, len(self._powerupdist) - 1)
171                    ]
172                    if ptype not in excludetypes:
173                        break
174        self._lastpoweruptype = ptype
175        return ptype
176
177    @classmethod
178    def get(cls) -> PowerupBoxFactory:
179        """Return a shared bs.PowerupBoxFactory object, creating if needed."""
180        activity = bs.getactivity()
181        if activity is None:
182            raise bs.ContextError('No current activity.')
183        factory = activity.customdata.get(cls._STORENAME)
184        if factory is None:
185            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
186        assert isinstance(factory, PowerupBoxFactory)
187        return factory
188
189
190class PowerupBox(bs.Actor):
191    """A box that grants a powerup.
192
193    category: Gameplay Classes
194
195    This will deliver a bs.PowerupMessage to anything that touches it
196    which has the bs.PowerupBoxFactory.powerup_accept_material applied.
197    """
198
199    poweruptype: str
200    """The string powerup type.  This can be 'triple_bombs', 'punch',
201       'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs', 'shield',
202       'health', or 'curse'."""
203
204    node: bs.Node
205    """The 'prop' bs.Node representing this box."""
206
207    def __init__(
208        self,
209        position: Sequence[float] = (0.0, 1.0, 0.0),
210        poweruptype: str = 'triple_bombs',
211        expire: bool = True,
212    ):
213        """Create a powerup-box of the requested type at the given position.
214
215        see bs.Powerup.poweruptype for valid type strings.
216        """
217
218        super().__init__()
219        shared = SharedObjects.get()
220        factory = PowerupBoxFactory.get()
221        self.poweruptype = poweruptype
222        self._powersgiven = False
223
224        if poweruptype == 'triple_bombs':
225            tex = factory.tex_bomb
226        elif poweruptype == 'punch':
227            tex = factory.tex_punch
228        elif poweruptype == 'ice_bombs':
229            tex = factory.tex_ice_bombs
230        elif poweruptype == 'impact_bombs':
231            tex = factory.tex_impact_bombs
232        elif poweruptype == 'land_mines':
233            tex = factory.tex_land_mines
234        elif poweruptype == 'sticky_bombs':
235            tex = factory.tex_sticky_bombs
236        elif poweruptype == 'shield':
237            tex = factory.tex_shield
238        elif poweruptype == 'health':
239            tex = factory.tex_health
240        elif poweruptype == 'curse':
241            tex = factory.tex_curse
242        else:
243            raise ValueError('invalid poweruptype: ' + str(poweruptype))
244
245        if len(position) != 3:
246            raise ValueError('expected 3 floats for position')
247
248        self.node = bs.newnode(
249            'prop',
250            delegate=self,
251            attrs={
252                'body': 'box',
253                'position': position,
254                'mesh': factory.mesh,
255                'light_mesh': factory.mesh_simple,
256                'shadow_size': 0.5,
257                'color_texture': tex,
258                'reflection': 'powerup',
259                'reflection_scale': [1.0],
260                'materials': (factory.powerup_material, shared.object_material),
261            },
262        )
263
264        # Animate in.
265        curve = bs.animate(self.node, 'mesh_scale', {0: 0, 0.14: 1.6, 0.2: 1})
266        bs.timer(0.2, curve.delete)
267
268        if expire:
269            bs.timer(
270                DEFAULT_POWERUP_INTERVAL - 2.5,
271                bs.WeakCall(self._start_flashing),
272            )
273            bs.timer(
274                DEFAULT_POWERUP_INTERVAL - 1.0,
275                bs.WeakCall(self.handlemessage, bs.DieMessage()),
276            )
277
278    def _start_flashing(self) -> None:
279        if self.node:
280            self.node.flashing = True
281
282    @override
283    def handlemessage(self, msg: Any) -> Any:
284        assert not self.expired
285
286        if isinstance(msg, bs.PowerupAcceptMessage):
287            factory = PowerupBoxFactory.get()
288            assert self.node
289            if self.poweruptype == 'health':
290                factory.health_powerup_sound.play(
291                    3, position=self.node.position
292                )
293
294            factory.powerup_sound.play(3, position=self.node.position)
295            self._powersgiven = True
296            self.handlemessage(bs.DieMessage())
297
298        elif isinstance(msg, _TouchedMessage):
299            if not self._powersgiven:
300                node = bs.getcollision().opposingnode
301                node.handlemessage(
302                    bs.PowerupMessage(self.poweruptype, sourcenode=self.node)
303                )
304
305        elif isinstance(msg, bs.DieMessage):
306            if self.node:
307                if msg.immediate:
308                    self.node.delete()
309                else:
310                    bs.animate(self.node, 'mesh_scale', {0: 1, 0.1: 0})
311                    bs.timer(0.1, self.node.delete)
312
313        elif isinstance(msg, bs.OutOfBoundsMessage):
314            self.handlemessage(bs.DieMessage())
315
316        elif isinstance(msg, bs.HitMessage):
317            # Don't die on punches (that's annoying).
318            if msg.hit_type != 'punch':
319                self.handlemessage(bs.DieMessage())
320        else:
321            return super().handlemessage(msg)
322        return None
DEFAULT_POWERUP_INTERVAL = 8.0
class PowerupBoxFactory:
 25class PowerupBoxFactory:
 26    """A collection of media and other resources used by bs.Powerups.
 27
 28    Category: **Gameplay Classes**
 29
 30    A single instance of this is shared between all powerups
 31    and can be retrieved via bs.Powerup.get_factory().
 32    """
 33
 34    mesh: bs.Mesh
 35    """The bs.Mesh of the powerup box."""
 36
 37    mesh_simple: bs.Mesh
 38    """A simpler bs.Mesh of the powerup box, for use in shadows, etc."""
 39
 40    tex_bomb: bs.Texture
 41    """Triple-bomb powerup bs.Texture."""
 42
 43    tex_punch: bs.Texture
 44    """Punch powerup bs.Texture."""
 45
 46    tex_ice_bombs: bs.Texture
 47    """Ice bomb powerup bs.Texture."""
 48
 49    tex_sticky_bombs: bs.Texture
 50    """Sticky bomb powerup bs.Texture."""
 51
 52    tex_shield: bs.Texture
 53    """Shield powerup bs.Texture."""
 54
 55    tex_impact_bombs: bs.Texture
 56    """Impact-bomb powerup bs.Texture."""
 57
 58    tex_health: bs.Texture
 59    """Health powerup bs.Texture."""
 60
 61    tex_land_mines: bs.Texture
 62    """Land-mine powerup bs.Texture."""
 63
 64    tex_curse: bs.Texture
 65    """Curse powerup bs.Texture."""
 66
 67    health_powerup_sound: bs.Sound
 68    """bs.Sound played when a health powerup is accepted."""
 69
 70    powerup_sound: bs.Sound
 71    """bs.Sound played when a powerup is accepted."""
 72
 73    powerdown_sound: bs.Sound
 74    """bs.Sound that can be used when powerups wear off."""
 75
 76    powerup_material: bs.Material
 77    """bs.Material applied to powerup boxes."""
 78
 79    powerup_accept_material: bs.Material
 80    """Powerups will send a bs.PowerupMessage to anything they touch
 81       that has this bs.Material applied."""
 82
 83    _STORENAME = bs.storagename()
 84
 85    def __init__(self) -> None:
 86        """Instantiate a PowerupBoxFactory.
 87
 88        You shouldn't need to do this; call Powerup.get_factory()
 89        to get a shared instance.
 90        """
 91        from bascenev1 import get_default_powerup_distribution
 92
 93        shared = SharedObjects.get()
 94        self._lastpoweruptype: str | None = None
 95        self.mesh = bs.getmesh('powerup')
 96        self.mesh_simple = bs.getmesh('powerupSimple')
 97        self.tex_bomb = bs.gettexture('powerupBomb')
 98        self.tex_punch = bs.gettexture('powerupPunch')
 99        self.tex_ice_bombs = bs.gettexture('powerupIceBombs')
100        self.tex_sticky_bombs = bs.gettexture('powerupStickyBombs')
101        self.tex_shield = bs.gettexture('powerupShield')
102        self.tex_impact_bombs = bs.gettexture('powerupImpactBombs')
103        self.tex_health = bs.gettexture('powerupHealth')
104        self.tex_land_mines = bs.gettexture('powerupLandMines')
105        self.tex_curse = bs.gettexture('powerupCurse')
106        self.health_powerup_sound = bs.getsound('healthPowerup')
107        self.powerup_sound = bs.getsound('powerup01')
108        self.powerdown_sound = bs.getsound('powerdown01')
109        self.drop_sound = bs.getsound('boxDrop')
110
111        # Material for powerups.
112        self.powerup_material = bs.Material()
113
114        # Material for anyone wanting to accept powerups.
115        self.powerup_accept_material = bs.Material()
116
117        # Pass a powerup-touched message to applicable stuff.
118        self.powerup_material.add_actions(
119            conditions=('they_have_material', self.powerup_accept_material),
120            actions=(
121                ('modify_part_collision', 'collide', True),
122                ('modify_part_collision', 'physical', False),
123                ('message', 'our_node', 'at_connect', _TouchedMessage()),
124            ),
125        )
126
127        # We don't wanna be picked up.
128        self.powerup_material.add_actions(
129            conditions=('they_have_material', shared.pickup_material),
130            actions=('modify_part_collision', 'collide', False),
131        )
132
133        self.powerup_material.add_actions(
134            conditions=('they_have_material', shared.footing_material),
135            actions=('impact_sound', self.drop_sound, 0.5, 0.1),
136        )
137
138        self._powerupdist: list[str] = []
139        for powerup, freq in get_default_powerup_distribution():
140            for _i in range(int(freq)):
141                self._powerupdist.append(powerup)
142
143    def get_random_powerup_type(
144        self,
145        forcetype: str | None = None,
146        excludetypes: list[str] | None = None,
147    ) -> str:
148        """Returns a random powerup type (string).
149
150        See bs.Powerup.poweruptype for available type values.
151
152        There are certain non-random aspects to this; a 'curse' powerup,
153        for instance, is always followed by a 'health' powerup (to keep things
154        interesting). Passing 'forcetype' forces a given returned type while
155        still properly interacting with the non-random aspects of the system
156        (ie: forcing a 'curse' powerup will result
157        in the next powerup being health).
158        """
159        if excludetypes is None:
160            excludetypes = []
161        if forcetype:
162            ptype = forcetype
163        else:
164            # If the last one was a curse, make this one a health to
165            # provide some hope.
166            if self._lastpoweruptype == 'curse':
167                ptype = 'health'
168            else:
169                while True:
170                    ptype = self._powerupdist[
171                        random.randint(0, len(self._powerupdist) - 1)
172                    ]
173                    if ptype not in excludetypes:
174                        break
175        self._lastpoweruptype = ptype
176        return ptype
177
178    @classmethod
179    def get(cls) -> PowerupBoxFactory:
180        """Return a shared bs.PowerupBoxFactory object, creating if needed."""
181        activity = bs.getactivity()
182        if activity is None:
183            raise bs.ContextError('No current activity.')
184        factory = activity.customdata.get(cls._STORENAME)
185        if factory is None:
186            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
187        assert isinstance(factory, PowerupBoxFactory)
188        return factory

A collection of media and other resources used by bs.Powerups.

Category: Gameplay Classes

A single instance of this is shared between all powerups and can be retrieved via bs.Powerup.get_factory().

PowerupBoxFactory()
 85    def __init__(self) -> None:
 86        """Instantiate a PowerupBoxFactory.
 87
 88        You shouldn't need to do this; call Powerup.get_factory()
 89        to get a shared instance.
 90        """
 91        from bascenev1 import get_default_powerup_distribution
 92
 93        shared = SharedObjects.get()
 94        self._lastpoweruptype: str | None = None
 95        self.mesh = bs.getmesh('powerup')
 96        self.mesh_simple = bs.getmesh('powerupSimple')
 97        self.tex_bomb = bs.gettexture('powerupBomb')
 98        self.tex_punch = bs.gettexture('powerupPunch')
 99        self.tex_ice_bombs = bs.gettexture('powerupIceBombs')
100        self.tex_sticky_bombs = bs.gettexture('powerupStickyBombs')
101        self.tex_shield = bs.gettexture('powerupShield')
102        self.tex_impact_bombs = bs.gettexture('powerupImpactBombs')
103        self.tex_health = bs.gettexture('powerupHealth')
104        self.tex_land_mines = bs.gettexture('powerupLandMines')
105        self.tex_curse = bs.gettexture('powerupCurse')
106        self.health_powerup_sound = bs.getsound('healthPowerup')
107        self.powerup_sound = bs.getsound('powerup01')
108        self.powerdown_sound = bs.getsound('powerdown01')
109        self.drop_sound = bs.getsound('boxDrop')
110
111        # Material for powerups.
112        self.powerup_material = bs.Material()
113
114        # Material for anyone wanting to accept powerups.
115        self.powerup_accept_material = bs.Material()
116
117        # Pass a powerup-touched message to applicable stuff.
118        self.powerup_material.add_actions(
119            conditions=('they_have_material', self.powerup_accept_material),
120            actions=(
121                ('modify_part_collision', 'collide', True),
122                ('modify_part_collision', 'physical', False),
123                ('message', 'our_node', 'at_connect', _TouchedMessage()),
124            ),
125        )
126
127        # We don't wanna be picked up.
128        self.powerup_material.add_actions(
129            conditions=('they_have_material', shared.pickup_material),
130            actions=('modify_part_collision', 'collide', False),
131        )
132
133        self.powerup_material.add_actions(
134            conditions=('they_have_material', shared.footing_material),
135            actions=('impact_sound', self.drop_sound, 0.5, 0.1),
136        )
137
138        self._powerupdist: list[str] = []
139        for powerup, freq in get_default_powerup_distribution():
140            for _i in range(int(freq)):
141                self._powerupdist.append(powerup)

Instantiate a PowerupBoxFactory.

You shouldn't need to do this; call Powerup.get_factory() to get a shared instance.

mesh: _bascenev1.Mesh

The bs.Mesh of the powerup box.

mesh_simple: _bascenev1.Mesh

A simpler bs.Mesh of the powerup box, for use in shadows, etc.

tex_bomb: _bascenev1.Texture

Triple-bomb powerup bs.Texture.

tex_punch: _bascenev1.Texture

Punch powerup bs.Texture.

tex_ice_bombs: _bascenev1.Texture

Ice bomb powerup bs.Texture.

tex_sticky_bombs: _bascenev1.Texture

Sticky bomb powerup bs.Texture.

tex_shield: _bascenev1.Texture

Shield powerup bs.Texture.

tex_impact_bombs: _bascenev1.Texture

Impact-bomb powerup bs.Texture.

tex_health: _bascenev1.Texture

Health powerup bs.Texture.

tex_land_mines: _bascenev1.Texture

Land-mine powerup bs.Texture.

tex_curse: _bascenev1.Texture

Curse powerup bs.Texture.

health_powerup_sound: _bascenev1.Sound

bs.Sound played when a health powerup is accepted.

powerup_sound: _bascenev1.Sound

bs.Sound played when a powerup is accepted.

powerdown_sound: _bascenev1.Sound

bs.Sound that can be used when powerups wear off.

powerup_material: _bascenev1.Material

bs.Material applied to powerup boxes.

powerup_accept_material: _bascenev1.Material

Powerups will send a bs.PowerupMessage to anything they touch that has this bs.Material applied.

drop_sound
def get_random_powerup_type( self, forcetype: str | None = None, excludetypes: list[str] | None = None) -> str:
143    def get_random_powerup_type(
144        self,
145        forcetype: str | None = None,
146        excludetypes: list[str] | None = None,
147    ) -> str:
148        """Returns a random powerup type (string).
149
150        See bs.Powerup.poweruptype for available type values.
151
152        There are certain non-random aspects to this; a 'curse' powerup,
153        for instance, is always followed by a 'health' powerup (to keep things
154        interesting). Passing 'forcetype' forces a given returned type while
155        still properly interacting with the non-random aspects of the system
156        (ie: forcing a 'curse' powerup will result
157        in the next powerup being health).
158        """
159        if excludetypes is None:
160            excludetypes = []
161        if forcetype:
162            ptype = forcetype
163        else:
164            # If the last one was a curse, make this one a health to
165            # provide some hope.
166            if self._lastpoweruptype == 'curse':
167                ptype = 'health'
168            else:
169                while True:
170                    ptype = self._powerupdist[
171                        random.randint(0, len(self._powerupdist) - 1)
172                    ]
173                    if ptype not in excludetypes:
174                        break
175        self._lastpoweruptype = ptype
176        return ptype

Returns a random powerup type (string).

See bs.Powerup.poweruptype for available type values.

There are certain non-random aspects to this; a 'curse' powerup, for instance, is always followed by a 'health' powerup (to keep things interesting). Passing 'forcetype' forces a given returned type while still properly interacting with the non-random aspects of the system (ie: forcing a 'curse' powerup will result in the next powerup being health).

@classmethod
def get(cls) -> PowerupBoxFactory:
178    @classmethod
179    def get(cls) -> PowerupBoxFactory:
180        """Return a shared bs.PowerupBoxFactory object, creating if needed."""
181        activity = bs.getactivity()
182        if activity is None:
183            raise bs.ContextError('No current activity.')
184        factory = activity.customdata.get(cls._STORENAME)
185        if factory is None:
186            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
187        assert isinstance(factory, PowerupBoxFactory)
188        return factory

Return a shared bs.PowerupBoxFactory object, creating if needed.

class PowerupBox(bascenev1._actor.Actor):
191class PowerupBox(bs.Actor):
192    """A box that grants a powerup.
193
194    category: Gameplay Classes
195
196    This will deliver a bs.PowerupMessage to anything that touches it
197    which has the bs.PowerupBoxFactory.powerup_accept_material applied.
198    """
199
200    poweruptype: str
201    """The string powerup type.  This can be 'triple_bombs', 'punch',
202       'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs', 'shield',
203       'health', or 'curse'."""
204
205    node: bs.Node
206    """The 'prop' bs.Node representing this box."""
207
208    def __init__(
209        self,
210        position: Sequence[float] = (0.0, 1.0, 0.0),
211        poweruptype: str = 'triple_bombs',
212        expire: bool = True,
213    ):
214        """Create a powerup-box of the requested type at the given position.
215
216        see bs.Powerup.poweruptype for valid type strings.
217        """
218
219        super().__init__()
220        shared = SharedObjects.get()
221        factory = PowerupBoxFactory.get()
222        self.poweruptype = poweruptype
223        self._powersgiven = False
224
225        if poweruptype == 'triple_bombs':
226            tex = factory.tex_bomb
227        elif poweruptype == 'punch':
228            tex = factory.tex_punch
229        elif poweruptype == 'ice_bombs':
230            tex = factory.tex_ice_bombs
231        elif poweruptype == 'impact_bombs':
232            tex = factory.tex_impact_bombs
233        elif poweruptype == 'land_mines':
234            tex = factory.tex_land_mines
235        elif poweruptype == 'sticky_bombs':
236            tex = factory.tex_sticky_bombs
237        elif poweruptype == 'shield':
238            tex = factory.tex_shield
239        elif poweruptype == 'health':
240            tex = factory.tex_health
241        elif poweruptype == 'curse':
242            tex = factory.tex_curse
243        else:
244            raise ValueError('invalid poweruptype: ' + str(poweruptype))
245
246        if len(position) != 3:
247            raise ValueError('expected 3 floats for position')
248
249        self.node = bs.newnode(
250            'prop',
251            delegate=self,
252            attrs={
253                'body': 'box',
254                'position': position,
255                'mesh': factory.mesh,
256                'light_mesh': factory.mesh_simple,
257                'shadow_size': 0.5,
258                'color_texture': tex,
259                'reflection': 'powerup',
260                'reflection_scale': [1.0],
261                'materials': (factory.powerup_material, shared.object_material),
262            },
263        )
264
265        # Animate in.
266        curve = bs.animate(self.node, 'mesh_scale', {0: 0, 0.14: 1.6, 0.2: 1})
267        bs.timer(0.2, curve.delete)
268
269        if expire:
270            bs.timer(
271                DEFAULT_POWERUP_INTERVAL - 2.5,
272                bs.WeakCall(self._start_flashing),
273            )
274            bs.timer(
275                DEFAULT_POWERUP_INTERVAL - 1.0,
276                bs.WeakCall(self.handlemessage, bs.DieMessage()),
277            )
278
279    def _start_flashing(self) -> None:
280        if self.node:
281            self.node.flashing = True
282
283    @override
284    def handlemessage(self, msg: Any) -> Any:
285        assert not self.expired
286
287        if isinstance(msg, bs.PowerupAcceptMessage):
288            factory = PowerupBoxFactory.get()
289            assert self.node
290            if self.poweruptype == 'health':
291                factory.health_powerup_sound.play(
292                    3, position=self.node.position
293                )
294
295            factory.powerup_sound.play(3, position=self.node.position)
296            self._powersgiven = True
297            self.handlemessage(bs.DieMessage())
298
299        elif isinstance(msg, _TouchedMessage):
300            if not self._powersgiven:
301                node = bs.getcollision().opposingnode
302                node.handlemessage(
303                    bs.PowerupMessage(self.poweruptype, sourcenode=self.node)
304                )
305
306        elif isinstance(msg, bs.DieMessage):
307            if self.node:
308                if msg.immediate:
309                    self.node.delete()
310                else:
311                    bs.animate(self.node, 'mesh_scale', {0: 1, 0.1: 0})
312                    bs.timer(0.1, self.node.delete)
313
314        elif isinstance(msg, bs.OutOfBoundsMessage):
315            self.handlemessage(bs.DieMessage())
316
317        elif isinstance(msg, bs.HitMessage):
318            # Don't die on punches (that's annoying).
319            if msg.hit_type != 'punch':
320                self.handlemessage(bs.DieMessage())
321        else:
322            return super().handlemessage(msg)
323        return None

A box that grants a powerup.

category: Gameplay Classes

This will deliver a bs.PowerupMessage to anything that touches it which has the bs.PowerupBoxFactory.powerup_accept_material applied.

PowerupBox( position: Sequence[float] = (0.0, 1.0, 0.0), poweruptype: str = 'triple_bombs', expire: bool = True)
208    def __init__(
209        self,
210        position: Sequence[float] = (0.0, 1.0, 0.0),
211        poweruptype: str = 'triple_bombs',
212        expire: bool = True,
213    ):
214        """Create a powerup-box of the requested type at the given position.
215
216        see bs.Powerup.poweruptype for valid type strings.
217        """
218
219        super().__init__()
220        shared = SharedObjects.get()
221        factory = PowerupBoxFactory.get()
222        self.poweruptype = poweruptype
223        self._powersgiven = False
224
225        if poweruptype == 'triple_bombs':
226            tex = factory.tex_bomb
227        elif poweruptype == 'punch':
228            tex = factory.tex_punch
229        elif poweruptype == 'ice_bombs':
230            tex = factory.tex_ice_bombs
231        elif poweruptype == 'impact_bombs':
232            tex = factory.tex_impact_bombs
233        elif poweruptype == 'land_mines':
234            tex = factory.tex_land_mines
235        elif poweruptype == 'sticky_bombs':
236            tex = factory.tex_sticky_bombs
237        elif poweruptype == 'shield':
238            tex = factory.tex_shield
239        elif poweruptype == 'health':
240            tex = factory.tex_health
241        elif poweruptype == 'curse':
242            tex = factory.tex_curse
243        else:
244            raise ValueError('invalid poweruptype: ' + str(poweruptype))
245
246        if len(position) != 3:
247            raise ValueError('expected 3 floats for position')
248
249        self.node = bs.newnode(
250            'prop',
251            delegate=self,
252            attrs={
253                'body': 'box',
254                'position': position,
255                'mesh': factory.mesh,
256                'light_mesh': factory.mesh_simple,
257                'shadow_size': 0.5,
258                'color_texture': tex,
259                'reflection': 'powerup',
260                'reflection_scale': [1.0],
261                'materials': (factory.powerup_material, shared.object_material),
262            },
263        )
264
265        # Animate in.
266        curve = bs.animate(self.node, 'mesh_scale', {0: 0, 0.14: 1.6, 0.2: 1})
267        bs.timer(0.2, curve.delete)
268
269        if expire:
270            bs.timer(
271                DEFAULT_POWERUP_INTERVAL - 2.5,
272                bs.WeakCall(self._start_flashing),
273            )
274            bs.timer(
275                DEFAULT_POWERUP_INTERVAL - 1.0,
276                bs.WeakCall(self.handlemessage, bs.DieMessage()),
277            )

Create a powerup-box of the requested type at the given position.

see bs.Powerup.poweruptype for valid type strings.

poweruptype: str

The string powerup type. This can be 'triple_bombs', 'punch', 'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs', 'shield', 'health', or 'curse'.

node: _bascenev1.Node

The 'prop' bs.Node representing this box.

@override
def handlemessage(self, msg: Any) -> Any:
283    @override
284    def handlemessage(self, msg: Any) -> Any:
285        assert not self.expired
286
287        if isinstance(msg, bs.PowerupAcceptMessage):
288            factory = PowerupBoxFactory.get()
289            assert self.node
290            if self.poweruptype == 'health':
291                factory.health_powerup_sound.play(
292                    3, position=self.node.position
293                )
294
295            factory.powerup_sound.play(3, position=self.node.position)
296            self._powersgiven = True
297            self.handlemessage(bs.DieMessage())
298
299        elif isinstance(msg, _TouchedMessage):
300            if not self._powersgiven:
301                node = bs.getcollision().opposingnode
302                node.handlemessage(
303                    bs.PowerupMessage(self.poweruptype, sourcenode=self.node)
304                )
305
306        elif isinstance(msg, bs.DieMessage):
307            if self.node:
308                if msg.immediate:
309                    self.node.delete()
310                else:
311                    bs.animate(self.node, 'mesh_scale', {0: 1, 0.1: 0})
312                    bs.timer(0.1, self.node.delete)
313
314        elif isinstance(msg, bs.OutOfBoundsMessage):
315            self.handlemessage(bs.DieMessage())
316
317        elif isinstance(msg, bs.HitMessage):
318            # Don't die on punches (that's annoying).
319            if msg.hit_type != 'punch':
320                self.handlemessage(bs.DieMessage())
321        else:
322            return super().handlemessage(msg)
323        return None

General message handling; can be passed any message object.