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        shared = SharedObjects.get()
 91        self._lastpoweruptype: str | None = None
 92        self.model = ba.getmodel('powerup')
 93        self.model_simple = ba.getmodel('powerupSimple')
 94        self.tex_bomb = ba.gettexture('powerupBomb')
 95        self.tex_punch = ba.gettexture('powerupPunch')
 96        self.tex_ice_bombs = ba.gettexture('powerupIceBombs')
 97        self.tex_sticky_bombs = ba.gettexture('powerupStickyBombs')
 98        self.tex_shield = ba.gettexture('powerupShield')
 99        self.tex_impact_bombs = ba.gettexture('powerupImpactBombs')
100        self.tex_health = ba.gettexture('powerupHealth')
101        self.tex_land_mines = ba.gettexture('powerupLandMines')
102        self.tex_curse = ba.gettexture('powerupCurse')
103        self.health_powerup_sound = ba.getsound('healthPowerup')
104        self.powerup_sound = ba.getsound('powerup01')
105        self.powerdown_sound = ba.getsound('powerdown01')
106        self.drop_sound = ba.getsound('boxDrop')
107
108        # Material for powerups.
109        self.powerup_material = ba.Material()
110
111        # Material for anyone wanting to accept powerups.
112        self.powerup_accept_material = ba.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        # We don't wanna be picked up.
124        self.powerup_material.add_actions(
125            conditions=('they_have_material', shared.pickup_material),
126            actions=('modify_part_collision', 'collide', False),
127        )
128
129        self.powerup_material.add_actions(
130            conditions=('they_have_material', shared.footing_material),
131            actions=('impact_sound', self.drop_sound, 0.5, 0.1),
132        )
133
134        self._powerupdist: list[str] = []
135        for powerup, freq in get_default_powerup_distribution():
136            for _i in range(int(freq)):
137                self._powerupdist.append(powerup)
138
139    def get_random_powerup_type(self,
140                                forcetype: str | None = None,
141                                excludetypes: list[str] | None = None) -> str:
142        """Returns a random powerup type (string).
143
144        See ba.Powerup.poweruptype for available type values.
145
146        There are certain non-random aspects to this; a 'curse' powerup,
147        for instance, is always followed by a 'health' powerup (to keep things
148        interesting). Passing 'forcetype' forces a given returned type while
149        still properly interacting with the non-random aspects of the system
150        (ie: forcing a 'curse' powerup will result
151        in the next powerup being health).
152        """
153        if excludetypes is None:
154            excludetypes = []
155        if forcetype:
156            ptype = forcetype
157        else:
158            # If the last one was a curse, make this one a health to
159            # provide some hope.
160            if self._lastpoweruptype == 'curse':
161                ptype = 'health'
162            else:
163                while True:
164                    ptype = self._powerupdist[random.randint(
165                        0,
166                        len(self._powerupdist) - 1)]
167                    if ptype not in excludetypes:
168                        break
169        self._lastpoweruptype = ptype
170        return ptype
171
172    @classmethod
173    def get(cls) -> PowerupBoxFactory:
174        """Return a shared ba.PowerupBoxFactory object, creating if needed."""
175        activity = ba.getactivity()
176        if activity is None:
177            raise ba.ContextError('No current activity.')
178        factory = activity.customdata.get(cls._STORENAME)
179        if factory is None:
180            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
181        assert isinstance(factory, PowerupBoxFactory)
182        return factory
183
184
185class PowerupBox(ba.Actor):
186    """A box that grants a powerup.
187
188    category: Gameplay Classes
189
190    This will deliver a ba.PowerupMessage to anything that touches it
191    which has the ba.PowerupBoxFactory.powerup_accept_material applied.
192    """
193
194    poweruptype: str
195    """The string powerup type.  This can be 'triple_bombs', 'punch',
196       'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs', 'shield',
197       'health', or 'curse'."""
198
199    node: ba.Node
200    """The 'prop' ba.Node representing this box."""
201
202    def __init__(self,
203                 position: Sequence[float] = (0.0, 1.0, 0.0),
204                 poweruptype: str = 'triple_bombs',
205                 expire: bool = True):
206        """Create a powerup-box of the requested type at the given position.
207
208        see ba.Powerup.poweruptype for valid type strings.
209        """
210
211        super().__init__()
212        shared = SharedObjects.get()
213        factory = PowerupBoxFactory.get()
214        self.poweruptype = poweruptype
215        self._powersgiven = False
216
217        if poweruptype == 'triple_bombs':
218            tex = factory.tex_bomb
219        elif poweruptype == 'punch':
220            tex = factory.tex_punch
221        elif poweruptype == 'ice_bombs':
222            tex = factory.tex_ice_bombs
223        elif poweruptype == 'impact_bombs':
224            tex = factory.tex_impact_bombs
225        elif poweruptype == 'land_mines':
226            tex = factory.tex_land_mines
227        elif poweruptype == 'sticky_bombs':
228            tex = factory.tex_sticky_bombs
229        elif poweruptype == 'shield':
230            tex = factory.tex_shield
231        elif poweruptype == 'health':
232            tex = factory.tex_health
233        elif poweruptype == 'curse':
234            tex = factory.tex_curse
235        else:
236            raise ValueError('invalid poweruptype: ' + str(poweruptype))
237
238        if len(position) != 3:
239            raise ValueError('expected 3 floats for position')
240
241        self.node = ba.newnode(
242            'prop',
243            delegate=self,
244            attrs={
245                'body': 'box',
246                'position': position,
247                'model': factory.model,
248                'light_model': factory.model_simple,
249                'shadow_size': 0.5,
250                'color_texture': tex,
251                'reflection': 'powerup',
252                'reflection_scale': [1.0],
253                'materials': (factory.powerup_material,
254                              shared.object_material)
255            })  # yapf: disable
256
257        # Animate in.
258        curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1})
259        ba.timer(0.2, curve.delete)
260
261        if expire:
262            ba.timer(DEFAULT_POWERUP_INTERVAL - 2.5,
263                     ba.WeakCall(self._start_flashing))
264            ba.timer(DEFAULT_POWERUP_INTERVAL - 1.0,
265                     ba.WeakCall(self.handlemessage, ba.DieMessage()))
266
267    def _start_flashing(self) -> None:
268        if self.node:
269            self.node.flashing = True
270
271    def handlemessage(self, msg: Any) -> Any:
272        assert not self.expired
273
274        if isinstance(msg, ba.PowerupAcceptMessage):
275            factory = PowerupBoxFactory.get()
276            assert self.node
277            if self.poweruptype == 'health':
278                ba.playsound(factory.health_powerup_sound,
279                             3,
280                             position=self.node.position)
281            ba.playsound(factory.powerup_sound, 3, position=self.node.position)
282            self._powersgiven = True
283            self.handlemessage(ba.DieMessage())
284
285        elif isinstance(msg, _TouchedMessage):
286            if not self._powersgiven:
287                node = ba.getcollision().opposingnode
288                node.handlemessage(
289                    ba.PowerupMessage(self.poweruptype, sourcenode=self.node))
290
291        elif isinstance(msg, ba.DieMessage):
292            if self.node:
293                if msg.immediate:
294                    self.node.delete()
295                else:
296                    ba.animate(self.node, 'model_scale', {0: 1, 0.1: 0})
297                    ba.timer(0.1, self.node.delete)
298
299        elif isinstance(msg, ba.OutOfBoundsMessage):
300            self.handlemessage(ba.DieMessage())
301
302        elif isinstance(msg, ba.HitMessage):
303            # Don't die on punches (that's annoying).
304            if msg.hit_type != 'punch':
305                self.handlemessage(ba.DieMessage())
306        else:
307            return super().handlemessage(msg)
308        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        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        # 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(self,
141                                forcetype: str | None = None,
142                                excludetypes: list[str] | None = None) -> str:
143        """Returns a random powerup type (string).
144
145        See ba.Powerup.poweruptype for available type values.
146
147        There are certain non-random aspects to this; a 'curse' powerup,
148        for instance, is always followed by a 'health' powerup (to keep things
149        interesting). Passing 'forcetype' forces a given returned type while
150        still properly interacting with the non-random aspects of the system
151        (ie: forcing a 'curse' powerup will result
152        in the next powerup being health).
153        """
154        if excludetypes is None:
155            excludetypes = []
156        if forcetype:
157            ptype = forcetype
158        else:
159            # If the last one was a curse, make this one a health to
160            # provide some hope.
161            if self._lastpoweruptype == 'curse':
162                ptype = 'health'
163            else:
164                while True:
165                    ptype = self._powerupdist[random.randint(
166                        0,
167                        len(self._powerupdist) - 1)]
168                    if ptype not in excludetypes:
169                        break
170        self._lastpoweruptype = ptype
171        return ptype
172
173    @classmethod
174    def get(cls) -> PowerupBoxFactory:
175        """Return a shared ba.PowerupBoxFactory object, creating if needed."""
176        activity = ba.getactivity()
177        if activity is None:
178            raise ba.ContextError('No current activity.')
179        factory = activity.customdata.get(cls._STORENAME)
180        if factory is None:
181            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
182        assert isinstance(factory, PowerupBoxFactory)
183        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        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        # 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)

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:
140    def get_random_powerup_type(self,
141                                forcetype: str | None = None,
142                                excludetypes: list[str] | None = None) -> str:
143        """Returns a random powerup type (string).
144
145        See ba.Powerup.poweruptype for available type values.
146
147        There are certain non-random aspects to this; a 'curse' powerup,
148        for instance, is always followed by a 'health' powerup (to keep things
149        interesting). Passing 'forcetype' forces a given returned type while
150        still properly interacting with the non-random aspects of the system
151        (ie: forcing a 'curse' powerup will result
152        in the next powerup being health).
153        """
154        if excludetypes is None:
155            excludetypes = []
156        if forcetype:
157            ptype = forcetype
158        else:
159            # If the last one was a curse, make this one a health to
160            # provide some hope.
161            if self._lastpoweruptype == 'curse':
162                ptype = 'health'
163            else:
164                while True:
165                    ptype = self._powerupdist[random.randint(
166                        0,
167                        len(self._powerupdist) - 1)]
168                    if ptype not in excludetypes:
169                        break
170        self._lastpoweruptype = ptype
171        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:
173    @classmethod
174    def get(cls) -> PowerupBoxFactory:
175        """Return a shared ba.PowerupBoxFactory object, creating if needed."""
176        activity = ba.getactivity()
177        if activity is None:
178            raise ba.ContextError('No current activity.')
179        factory = activity.customdata.get(cls._STORENAME)
180        if factory is None:
181            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
182        assert isinstance(factory, PowerupBoxFactory)
183        return factory

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

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

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:
272    def handlemessage(self, msg: Any) -> Any:
273        assert not self.expired
274
275        if isinstance(msg, ba.PowerupAcceptMessage):
276            factory = PowerupBoxFactory.get()
277            assert self.node
278            if self.poweruptype == 'health':
279                ba.playsound(factory.health_powerup_sound,
280                             3,
281                             position=self.node.position)
282            ba.playsound(factory.powerup_sound, 3, position=self.node.position)
283            self._powersgiven = True
284            self.handlemessage(ba.DieMessage())
285
286        elif isinstance(msg, _TouchedMessage):
287            if not self._powersgiven:
288                node = ba.getcollision().opposingnode
289                node.handlemessage(
290                    ba.PowerupMessage(self.poweruptype, sourcenode=self.node))
291
292        elif isinstance(msg, ba.DieMessage):
293            if self.node:
294                if msg.immediate:
295                    self.node.delete()
296                else:
297                    ba.animate(self.node, 'model_scale', {0: 1, 0.1: 0})
298                    ba.timer(0.1, self.node.delete)
299
300        elif isinstance(msg, ba.OutOfBoundsMessage):
301            self.handlemessage(ba.DieMessage())
302
303        elif isinstance(msg, ba.HitMessage):
304            # Don't die on punches (that's annoying).
305            if msg.hit_type != 'punch':
306                self.handlemessage(ba.DieMessage())
307        else:
308            return super().handlemessage(msg)
309        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