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

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

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

PowerupBoxFactory()
 83    def __init__(self) -> None:
 84        """Instantiate a PowerupBoxFactory.
 85
 86        You shouldn't need to do this; call Powerup.get_factory()
 87        to get a shared instance.
 88        """
 89        from bascenev1 import get_default_powerup_distribution
 90
 91        shared = SharedObjects.get()
 92        self._lastpoweruptype: str | None = None
 93        self.mesh = bs.getmesh('powerup')
 94        self.mesh_simple = bs.getmesh('powerupSimple')
 95        self.tex_bomb = bs.gettexture('powerupBomb')
 96        self.tex_punch = bs.gettexture('powerupPunch')
 97        self.tex_ice_bombs = bs.gettexture('powerupIceBombs')
 98        self.tex_sticky_bombs = bs.gettexture('powerupStickyBombs')
 99        self.tex_shield = bs.gettexture('powerupShield')
100        self.tex_impact_bombs = bs.gettexture('powerupImpactBombs')
101        self.tex_health = bs.gettexture('powerupHealth')
102        self.tex_land_mines = bs.gettexture('powerupLandMines')
103        self.tex_curse = bs.gettexture('powerupCurse')
104        self.health_powerup_sound = bs.getsound('healthPowerup')
105        self.powerup_sound = bs.getsound('powerup01')
106        self.powerdown_sound = bs.getsound('powerdown01')
107        self.drop_sound = bs.getsound('boxDrop')
108
109        # Material for powerups.
110        self.powerup_material = bs.Material()
111
112        # Material for anyone wanting to accept powerups.
113        self.powerup_accept_material = bs.Material()
114
115        # Pass a powerup-touched message to applicable stuff.
116        self.powerup_material.add_actions(
117            conditions=('they_have_material', self.powerup_accept_material),
118            actions=(
119                ('modify_part_collision', 'collide', True),
120                ('modify_part_collision', 'physical', False),
121                ('message', 'our_node', 'at_connect', _TouchedMessage()),
122            ),
123        )
124
125        # We don't wanna be picked up.
126        self.powerup_material.add_actions(
127            conditions=('they_have_material', shared.pickup_material),
128            actions=('modify_part_collision', 'collide', False),
129        )
130
131        self.powerup_material.add_actions(
132            conditions=('they_have_material', shared.footing_material),
133            actions=('impact_sound', self.drop_sound, 0.5, 0.1),
134        )
135
136        self._powerupdist: list[str] = []
137        for powerup, freq in get_default_powerup_distribution():
138            for _i in range(int(freq)):
139                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:
141    def get_random_powerup_type(
142        self,
143        forcetype: str | None = None,
144        excludetypes: list[str] | None = None,
145    ) -> str:
146        """Returns a random powerup type (string).
147
148        See bs.Powerup.poweruptype for available type values.
149
150        There are certain non-random aspects to this; a 'curse' powerup,
151        for instance, is always followed by a 'health' powerup (to keep things
152        interesting). Passing 'forcetype' forces a given returned type while
153        still properly interacting with the non-random aspects of the system
154        (ie: forcing a 'curse' powerup will result
155        in the next powerup being health).
156        """
157        if excludetypes is None:
158            excludetypes = []
159        if forcetype:
160            ptype = forcetype
161        else:
162            # If the last one was a curse, make this one a health to
163            # provide some hope.
164            if self._lastpoweruptype == 'curse':
165                ptype = 'health'
166            else:
167                while True:
168                    ptype = self._powerupdist[
169                        random.randint(0, len(self._powerupdist) - 1)
170                    ]
171                    if ptype not in excludetypes:
172                        break
173        self._lastpoweruptype = ptype
174        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:
176    @classmethod
177    def get(cls) -> PowerupBoxFactory:
178        """Return a shared bs.PowerupBoxFactory object, creating if needed."""
179        activity = bs.getactivity()
180        if activity is None:
181            raise bs.ContextError('No current activity.')
182        factory = activity.customdata.get(cls._STORENAME)
183        if factory is None:
184            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
185        assert isinstance(factory, PowerupBoxFactory)
186        return factory

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

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

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

General message handling; can be passed any message object.