bastd.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
  9
 10import ba
 11from bastd.gameutils import SharedObjects
 12
 13if TYPE_CHECKING:
 14    from typing import Any, Sequence
 15
 16DEFAULT_POWERUP_INTERVAL = 8.0
 17
 18
 19class _TouchedMessage:
 20    pass
 21
 22
 23class PowerupBoxFactory:
 24    """A collection of media and other resources used by ba.Powerups.
 25
 26    Category: **Gameplay Classes**
 27
 28    A single instance of this is shared between all powerups
 29    and can be retrieved via ba.Powerup.get_factory().
 30    """
 31
 32    model: ba.Model
 33    """The ba.Model of the powerup box."""
 34
 35    model_simple: ba.Model
 36    """A simpler ba.Model of the powerup box, for use in shadows, etc."""
 37
 38    tex_bomb: ba.Texture
 39    """Triple-bomb powerup ba.Texture."""
 40
 41    tex_punch: ba.Texture
 42    """Punch powerup ba.Texture."""
 43
 44    tex_ice_bombs: ba.Texture
 45    """Ice bomb powerup ba.Texture."""
 46
 47    tex_sticky_bombs: ba.Texture
 48    """Sticky bomb powerup ba.Texture."""
 49
 50    tex_shield: ba.Texture
 51    """Shield powerup ba.Texture."""
 52
 53    tex_impact_bombs: ba.Texture
 54    """Impact-bomb powerup ba.Texture."""
 55
 56    tex_health: ba.Texture
 57    """Health powerup ba.Texture."""
 58
 59    tex_land_mines: ba.Texture
 60    """Land-mine powerup ba.Texture."""
 61
 62    tex_curse: ba.Texture
 63    """Curse powerup ba.Texture."""
 64
 65    health_powerup_sound: ba.Sound
 66    """ba.Sound played when a health powerup is accepted."""
 67
 68    powerup_sound: ba.Sound
 69    """ba.Sound played when a powerup is accepted."""
 70
 71    powerdown_sound: ba.Sound
 72    """ba.Sound that can be used when powerups wear off."""
 73
 74    powerup_material: ba.Material
 75    """ba.Material applied to powerup boxes."""
 76
 77    powerup_accept_material: ba.Material
 78    """Powerups will send a ba.PowerupMessage to anything they touch
 79       that has this ba.Material applied."""
 80
 81    _STORENAME = ba.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 ba.internal import get_default_powerup_distribution
 90
 91        shared = SharedObjects.get()
 92        self._lastpoweruptype: str | None = None
 93        self.model = ba.getmodel('powerup')
 94        self.model_simple = ba.getmodel('powerupSimple')
 95        self.tex_bomb = ba.gettexture('powerupBomb')
 96        self.tex_punch = ba.gettexture('powerupPunch')
 97        self.tex_ice_bombs = ba.gettexture('powerupIceBombs')
 98        self.tex_sticky_bombs = ba.gettexture('powerupStickyBombs')
 99        self.tex_shield = ba.gettexture('powerupShield')
100        self.tex_impact_bombs = ba.gettexture('powerupImpactBombs')
101        self.tex_health = ba.gettexture('powerupHealth')
102        self.tex_land_mines = ba.gettexture('powerupLandMines')
103        self.tex_curse = ba.gettexture('powerupCurse')
104        self.health_powerup_sound = ba.getsound('healthPowerup')
105        self.powerup_sound = ba.getsound('powerup01')
106        self.powerdown_sound = ba.getsound('powerdown01')
107        self.drop_sound = ba.getsound('boxDrop')
108
109        # Material for powerups.
110        self.powerup_material = ba.Material()
111
112        # Material for anyone wanting to accept powerups.
113        self.powerup_accept_material = ba.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 ba.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 ba.PowerupBoxFactory object, creating if needed."""
179        activity = ba.getactivity()
180        if activity is None:
181            raise ba.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
187
188
189class PowerupBox(ba.Actor):
190    """A box that grants a powerup.
191
192    category: Gameplay Classes
193
194    This will deliver a ba.PowerupMessage to anything that touches it
195    which has the ba.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: ba.Node
204    """The 'prop' ba.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 ba.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 = ba.newnode(
248            'prop',
249            delegate=self,
250            attrs={
251                'body': 'box',
252                'position': position,
253                'model': factory.model,
254                'light_model': factory.model_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        )  # yapf: disable
262
263        # Animate in.
264        curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1})
265        ba.timer(0.2, curve.delete)
266
267        if expire:
268            ba.timer(
269                DEFAULT_POWERUP_INTERVAL - 2.5,
270                ba.WeakCall(self._start_flashing),
271            )
272            ba.timer(
273                DEFAULT_POWERUP_INTERVAL - 1.0,
274                ba.WeakCall(self.handlemessage, ba.DieMessage()),
275            )
276
277    def _start_flashing(self) -> None:
278        if self.node:
279            self.node.flashing = True
280
281    def handlemessage(self, msg: Any) -> Any:
282        assert not self.expired
283
284        if isinstance(msg, ba.PowerupAcceptMessage):
285            factory = PowerupBoxFactory.get()
286            assert self.node
287            if self.poweruptype == 'health':
288                ba.playsound(
289                    factory.health_powerup_sound, 3, position=self.node.position
290                )
291            ba.playsound(factory.powerup_sound, 3, position=self.node.position)
292            self._powersgiven = True
293            self.handlemessage(ba.DieMessage())
294
295        elif isinstance(msg, _TouchedMessage):
296            if not self._powersgiven:
297                node = ba.getcollision().opposingnode
298                node.handlemessage(
299                    ba.PowerupMessage(self.poweruptype, sourcenode=self.node)
300                )
301
302        elif isinstance(msg, ba.DieMessage):
303            if self.node:
304                if msg.immediate:
305                    self.node.delete()
306                else:
307                    ba.animate(self.node, 'model_scale', {0: 1, 0.1: 0})
308                    ba.timer(0.1, self.node.delete)
309
310        elif isinstance(msg, ba.OutOfBoundsMessage):
311            self.handlemessage(ba.DieMessage())
312
313        elif isinstance(msg, ba.HitMessage):
314            # Don't die on punches (that's annoying).
315            if msg.hit_type != 'punch':
316                self.handlemessage(ba.DieMessage())
317        else:
318            return super().handlemessage(msg)
319        return None
class PowerupBoxFactory:
 24class PowerupBoxFactory:
 25    """A collection of media and other resources used by ba.Powerups.
 26
 27    Category: **Gameplay Classes**
 28
 29    A single instance of this is shared between all powerups
 30    and can be retrieved via ba.Powerup.get_factory().
 31    """
 32
 33    model: ba.Model
 34    """The ba.Model of the powerup box."""
 35
 36    model_simple: ba.Model
 37    """A simpler ba.Model of the powerup box, for use in shadows, etc."""
 38
 39    tex_bomb: ba.Texture
 40    """Triple-bomb powerup ba.Texture."""
 41
 42    tex_punch: ba.Texture
 43    """Punch powerup ba.Texture."""
 44
 45    tex_ice_bombs: ba.Texture
 46    """Ice bomb powerup ba.Texture."""
 47
 48    tex_sticky_bombs: ba.Texture
 49    """Sticky bomb powerup ba.Texture."""
 50
 51    tex_shield: ba.Texture
 52    """Shield powerup ba.Texture."""
 53
 54    tex_impact_bombs: ba.Texture
 55    """Impact-bomb powerup ba.Texture."""
 56
 57    tex_health: ba.Texture
 58    """Health powerup ba.Texture."""
 59
 60    tex_land_mines: ba.Texture
 61    """Land-mine powerup ba.Texture."""
 62
 63    tex_curse: ba.Texture
 64    """Curse powerup ba.Texture."""
 65
 66    health_powerup_sound: ba.Sound
 67    """ba.Sound played when a health powerup is accepted."""
 68
 69    powerup_sound: ba.Sound
 70    """ba.Sound played when a powerup is accepted."""
 71
 72    powerdown_sound: ba.Sound
 73    """ba.Sound that can be used when powerups wear off."""
 74
 75    powerup_material: ba.Material
 76    """ba.Material applied to powerup boxes."""
 77
 78    powerup_accept_material: ba.Material
 79    """Powerups will send a ba.PowerupMessage to anything they touch
 80       that has this ba.Material applied."""
 81
 82    _STORENAME = ba.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 ba.internal import get_default_powerup_distribution
 91
 92        shared = SharedObjects.get()
 93        self._lastpoweruptype: str | None = None
 94        self.model = ba.getmodel('powerup')
 95        self.model_simple = ba.getmodel('powerupSimple')
 96        self.tex_bomb = ba.gettexture('powerupBomb')
 97        self.tex_punch = ba.gettexture('powerupPunch')
 98        self.tex_ice_bombs = ba.gettexture('powerupIceBombs')
 99        self.tex_sticky_bombs = ba.gettexture('powerupStickyBombs')
100        self.tex_shield = ba.gettexture('powerupShield')
101        self.tex_impact_bombs = ba.gettexture('powerupImpactBombs')
102        self.tex_health = ba.gettexture('powerupHealth')
103        self.tex_land_mines = ba.gettexture('powerupLandMines')
104        self.tex_curse = ba.gettexture('powerupCurse')
105        self.health_powerup_sound = ba.getsound('healthPowerup')
106        self.powerup_sound = ba.getsound('powerup01')
107        self.powerdown_sound = ba.getsound('powerdown01')
108        self.drop_sound = ba.getsound('boxDrop')
109
110        # Material for powerups.
111        self.powerup_material = ba.Material()
112
113        # Material for anyone wanting to accept powerups.
114        self.powerup_accept_material = ba.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 ba.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 ba.PowerupBoxFactory object, creating if needed."""
180        activity = ba.getactivity()
181        if activity is None:
182            raise ba.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

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

Category: Gameplay Classes

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

PowerupBoxFactory()
 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 ba.internal import get_default_powerup_distribution
 91
 92        shared = SharedObjects.get()
 93        self._lastpoweruptype: str | None = None
 94        self.model = ba.getmodel('powerup')
 95        self.model_simple = ba.getmodel('powerupSimple')
 96        self.tex_bomb = ba.gettexture('powerupBomb')
 97        self.tex_punch = ba.gettexture('powerupPunch')
 98        self.tex_ice_bombs = ba.gettexture('powerupIceBombs')
 99        self.tex_sticky_bombs = ba.gettexture('powerupStickyBombs')
100        self.tex_shield = ba.gettexture('powerupShield')
101        self.tex_impact_bombs = ba.gettexture('powerupImpactBombs')
102        self.tex_health = ba.gettexture('powerupHealth')
103        self.tex_land_mines = ba.gettexture('powerupLandMines')
104        self.tex_curse = ba.gettexture('powerupCurse')
105        self.health_powerup_sound = ba.getsound('healthPowerup')
106        self.powerup_sound = ba.getsound('powerup01')
107        self.powerdown_sound = ba.getsound('powerdown01')
108        self.drop_sound = ba.getsound('boxDrop')
109
110        # Material for powerups.
111        self.powerup_material = ba.Material()
112
113        # Material for anyone wanting to accept powerups.
114        self.powerup_accept_material = ba.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)

Instantiate a PowerupBoxFactory.

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

model: _ba.Model

The ba.Model of the powerup box.

model_simple: _ba.Model

A simpler ba.Model of the powerup box, for use in shadows, etc.

tex_bomb: _ba.Texture

Triple-bomb powerup ba.Texture.

tex_punch: _ba.Texture

Punch powerup ba.Texture.

tex_ice_bombs: _ba.Texture

Ice bomb powerup ba.Texture.

tex_sticky_bombs: _ba.Texture

Sticky bomb powerup ba.Texture.

tex_shield: _ba.Texture

Shield powerup ba.Texture.

tex_impact_bombs: _ba.Texture

Impact-bomb powerup ba.Texture.

tex_health: _ba.Texture

Health powerup ba.Texture.

tex_land_mines: _ba.Texture

Land-mine powerup ba.Texture.

tex_curse: _ba.Texture

Curse powerup ba.Texture.

health_powerup_sound: _ba.Sound

ba.Sound played when a health powerup is accepted.

powerup_sound: _ba.Sound

ba.Sound played when a powerup is accepted.

powerdown_sound: _ba.Sound

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

powerup_material: _ba.Material

ba.Material applied to powerup boxes.

powerup_accept_material: _ba.Material

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

def get_random_powerup_type( self, forcetype: str | None = None, excludetypes: list[str] | None = None) -> str:
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 ba.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

Returns a random powerup type (string).

See ba.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) -> bastd.actor.powerupbox.PowerupBoxFactory:
177    @classmethod
178    def get(cls) -> PowerupBoxFactory:
179        """Return a shared ba.PowerupBoxFactory object, creating if needed."""
180        activity = ba.getactivity()
181        if activity is None:
182            raise ba.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

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

class PowerupBox(ba._actor.Actor):
190class PowerupBox(ba.Actor):
191    """A box that grants a powerup.
192
193    category: Gameplay Classes
194
195    This will deliver a ba.PowerupMessage to anything that touches it
196    which has the ba.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: ba.Node
205    """The 'prop' ba.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 ba.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 = ba.newnode(
249            'prop',
250            delegate=self,
251            attrs={
252                'body': 'box',
253                'position': position,
254                'model': factory.model,
255                'light_model': factory.model_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        )  # yapf: disable
263
264        # Animate in.
265        curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1})
266        ba.timer(0.2, curve.delete)
267
268        if expire:
269            ba.timer(
270                DEFAULT_POWERUP_INTERVAL - 2.5,
271                ba.WeakCall(self._start_flashing),
272            )
273            ba.timer(
274                DEFAULT_POWERUP_INTERVAL - 1.0,
275                ba.WeakCall(self.handlemessage, ba.DieMessage()),
276            )
277
278    def _start_flashing(self) -> None:
279        if self.node:
280            self.node.flashing = True
281
282    def handlemessage(self, msg: Any) -> Any:
283        assert not self.expired
284
285        if isinstance(msg, ba.PowerupAcceptMessage):
286            factory = PowerupBoxFactory.get()
287            assert self.node
288            if self.poweruptype == 'health':
289                ba.playsound(
290                    factory.health_powerup_sound, 3, position=self.node.position
291                )
292            ba.playsound(factory.powerup_sound, 3, position=self.node.position)
293            self._powersgiven = True
294            self.handlemessage(ba.DieMessage())
295
296        elif isinstance(msg, _TouchedMessage):
297            if not self._powersgiven:
298                node = ba.getcollision().opposingnode
299                node.handlemessage(
300                    ba.PowerupMessage(self.poweruptype, sourcenode=self.node)
301                )
302
303        elif isinstance(msg, ba.DieMessage):
304            if self.node:
305                if msg.immediate:
306                    self.node.delete()
307                else:
308                    ba.animate(self.node, 'model_scale', {0: 1, 0.1: 0})
309                    ba.timer(0.1, self.node.delete)
310
311        elif isinstance(msg, ba.OutOfBoundsMessage):
312            self.handlemessage(ba.DieMessage())
313
314        elif isinstance(msg, ba.HitMessage):
315            # Don't die on punches (that's annoying).
316            if msg.hit_type != 'punch':
317                self.handlemessage(ba.DieMessage())
318        else:
319            return super().handlemessage(msg)
320        return None

A box that grants a powerup.

category: Gameplay Classes

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

PowerupBox( position: Sequence[float] = (0.0, 1.0, 0.0), poweruptype: str = 'triple_bombs', expire: bool = True)
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 ba.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 = ba.newnode(
249            'prop',
250            delegate=self,
251            attrs={
252                'body': 'box',
253                'position': position,
254                'model': factory.model,
255                'light_model': factory.model_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        )  # yapf: disable
263
264        # Animate in.
265        curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1})
266        ba.timer(0.2, curve.delete)
267
268        if expire:
269            ba.timer(
270                DEFAULT_POWERUP_INTERVAL - 2.5,
271                ba.WeakCall(self._start_flashing),
272            )
273            ba.timer(
274                DEFAULT_POWERUP_INTERVAL - 1.0,
275                ba.WeakCall(self.handlemessage, ba.DieMessage()),
276            )

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

see ba.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: _ba.Node

The 'prop' ba.Node representing this box.

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

General message handling; can be passed any message object.

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