bastd.actor.spazfactory

Provides a factory object from creating Spazzes.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a factory object from creating Spazzes."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING
  8
  9import ba
 10import ba.internal
 11from bastd.gameutils import SharedObjects
 12
 13if TYPE_CHECKING:
 14    from typing import Any, Sequence
 15
 16
 17class SpazFactory:
 18    """Wraps up media and other resources used by ba.Spaz instances.
 19
 20    Category: **Gameplay Classes**
 21
 22    Generally one of these is created per ba.Activity and shared
 23    between all spaz instances. Use ba.Spaz.get_factory() to return
 24    the shared factory for the current activity.
 25    """
 26
 27    impact_sounds_medium: Sequence[ba.Sound]
 28    """A tuple of ba.Sound-s for when a ba.Spaz hits something kinda hard."""
 29
 30    impact_sounds_hard: Sequence[ba.Sound]
 31    """A tuple of ba.Sound-s for when a ba.Spaz hits something really hard."""
 32
 33    impact_sounds_harder: Sequence[ba.Sound]
 34    """A tuple of ba.Sound-s for when a ba.Spaz hits something really
 35       really hard."""
 36
 37    single_player_death_sound: ba.Sound
 38    """The sound that plays for an 'important' spaz death such as in
 39       co-op games."""
 40
 41    punch_sound_weak: ba.Sound
 42    """A weak punch ba.Sound."""
 43
 44    punch_sound: ba.Sound
 45    """A standard punch ba.Sound."""
 46
 47    punch_sound_strong: Sequence[ba.Sound]
 48    """A tuple of stronger sounding punch ba.Sounds."""
 49
 50    punch_sound_stronger: ba.Sound
 51    """A really really strong sounding punch ba.Sound."""
 52
 53    swish_sound: ba.Sound
 54    """A punch swish ba.Sound."""
 55
 56    block_sound: ba.Sound
 57    """A ba.Sound for when an attack is blocked by invincibility."""
 58
 59    shatter_sound: ba.Sound
 60    """A ba.Sound for when a frozen ba.Spaz shatters."""
 61
 62    splatter_sound: ba.Sound
 63    """A ba.Sound for when a ba.Spaz blows up via curse."""
 64
 65    spaz_material: ba.Material
 66    """A ba.Material applied to all of parts of a ba.Spaz."""
 67
 68    roller_material: ba.Material
 69    """A ba.Material applied to the invisible roller ball body that
 70       a ba.Spaz uses for locomotion."""
 71
 72    punch_material: ba.Material
 73    """A ba.Material applied to the 'fist' of a ba.Spaz."""
 74
 75    pickup_material: ba.Material
 76    """A ba.Material applied to the 'grabber' body of a ba.Spaz."""
 77
 78    curse_material: ba.Material
 79    """A ba.Material applied to a cursed ba.Spaz that triggers an explosion."""
 80
 81    _STORENAME = ba.storagename()
 82
 83    def _preload(self, character: str) -> None:
 84        """Preload media needed for a given character."""
 85        self.get_media(character)
 86
 87    def __init__(self) -> None:
 88        """Instantiate a factory object."""
 89        # pylint: disable=cyclic-import
 90        # FIXME: should probably put these somewhere common so we don't
 91        # have to import them from a module that imports us.
 92        from bastd.actor.spaz import (
 93            PickupMessage,
 94            PunchHitMessage,
 95            CurseExplodeMessage,
 96        )
 97
 98        shared = SharedObjects.get()
 99        self.impact_sounds_medium = (
100            ba.getsound('impactMedium'),
101            ba.getsound('impactMedium2'),
102        )
103        self.impact_sounds_hard = (
104            ba.getsound('impactHard'),
105            ba.getsound('impactHard2'),
106            ba.getsound('impactHard3'),
107        )
108        self.impact_sounds_harder = (
109            ba.getsound('bigImpact'),
110            ba.getsound('bigImpact2'),
111        )
112        self.single_player_death_sound = ba.getsound('playerDeath')
113        self.punch_sound_weak = ba.getsound('punchWeak01')
114        self.punch_sound = ba.getsound('punch01')
115        self.punch_sound_strong = (
116            ba.getsound('punchStrong01'),
117            ba.getsound('punchStrong02'),
118        )
119        self.punch_sound_stronger = ba.getsound('superPunch')
120        self.swish_sound = ba.getsound('punchSwish')
121        self.block_sound = ba.getsound('block')
122        self.shatter_sound = ba.getsound('shatter')
123        self.splatter_sound = ba.getsound('splatter')
124        self.spaz_material = ba.Material()
125        self.roller_material = ba.Material()
126        self.punch_material = ba.Material()
127        self.pickup_material = ba.Material()
128        self.curse_material = ba.Material()
129
130        footing_material = shared.footing_material
131        object_material = shared.object_material
132        player_material = shared.player_material
133        region_material = shared.region_material
134
135        # Send footing messages to spazzes so they know when they're on
136        # solid ground.
137        # Eww; this probably should just be built into the spaz node.
138        self.roller_material.add_actions(
139            conditions=('they_have_material', footing_material),
140            actions=(
141                ('message', 'our_node', 'at_connect', 'footing', 1),
142                ('message', 'our_node', 'at_disconnect', 'footing', -1),
143            ),
144        )
145
146        self.spaz_material.add_actions(
147            conditions=('they_have_material', footing_material),
148            actions=(
149                ('message', 'our_node', 'at_connect', 'footing', 1),
150                ('message', 'our_node', 'at_disconnect', 'footing', -1),
151            ),
152        )
153
154        # Punches.
155        self.punch_material.add_actions(
156            conditions=('they_are_different_node_than_us',),
157            actions=(
158                ('modify_part_collision', 'collide', True),
159                ('modify_part_collision', 'physical', False),
160                ('message', 'our_node', 'at_connect', PunchHitMessage()),
161            ),
162        )
163
164        # Pickups.
165        self.pickup_material.add_actions(
166            conditions=(
167                ('they_are_different_node_than_us',),
168                'and',
169                ('they_have_material', object_material),
170            ),
171            actions=(
172                ('modify_part_collision', 'collide', True),
173                ('modify_part_collision', 'physical', False),
174                ('message', 'our_node', 'at_connect', PickupMessage()),
175            ),
176        )
177
178        # Curse.
179        self.curse_material.add_actions(
180            conditions=(
181                ('they_are_different_node_than_us',),
182                'and',
183                ('they_have_material', player_material),
184            ),
185            actions=(
186                'message',
187                'our_node',
188                'at_connect',
189                CurseExplodeMessage(),
190            ),
191        )
192
193        self.foot_impact_sounds = (
194            ba.getsound('footImpact01'),
195            ba.getsound('footImpact02'),
196            ba.getsound('footImpact03'),
197        )
198
199        self.foot_skid_sound = ba.getsound('skid01')
200        self.foot_roll_sound = ba.getsound('scamper01')
201
202        self.roller_material.add_actions(
203            conditions=('they_have_material', footing_material),
204            actions=(
205                ('impact_sound', self.foot_impact_sounds, 1, 0.2),
206                ('skid_sound', self.foot_skid_sound, 20, 0.3),
207                ('roll_sound', self.foot_roll_sound, 20, 3.0),
208            ),
209        )
210
211        self.skid_sound = ba.getsound('gravelSkid')
212
213        self.spaz_material.add_actions(
214            conditions=('they_have_material', footing_material),
215            actions=(
216                ('impact_sound', self.foot_impact_sounds, 20, 6),
217                ('skid_sound', self.skid_sound, 2.0, 1),
218                ('roll_sound', self.skid_sound, 2.0, 1),
219            ),
220        )
221
222        self.shield_up_sound = ba.getsound('shieldUp')
223        self.shield_down_sound = ba.getsound('shieldDown')
224        self.shield_hit_sound = ba.getsound('shieldHit')
225
226        # We don't want to collide with stuff we're initially overlapping
227        # (unless its marked with a special region material).
228        self.spaz_material.add_actions(
229            conditions=(
230                (
231                    ('we_are_younger_than', 51),
232                    'and',
233                    ('they_are_different_node_than_us',),
234                ),
235                'and',
236                ('they_dont_have_material', region_material),
237            ),
238            actions=('modify_node_collision', 'collide', False),
239        )
240
241        self.spaz_media: dict[str, Any] = {}
242
243        # Lets load some basic rules.
244        # (allows them to be tweaked from the master server)
245        self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val(
246            'rsdr', 10.0
247        )
248        self.punch_cooldown = ba.internal.get_v1_account_misc_read_val(
249            'rpc', 400
250        )
251        self.punch_cooldown_gloves = ba.internal.get_v1_account_misc_read_val(
252            'rpcg', 300
253        )
254        self.punch_power_scale = ba.internal.get_v1_account_misc_read_val(
255            'rpp', 1.2
256        )
257        self.punch_power_scale_gloves = (
258            ba.internal.get_v1_account_misc_read_val('rppg', 1.4)
259        )
260        self.max_shield_spillover_damage = (
261            ba.internal.get_v1_account_misc_read_val('rsms', 500)
262        )
263
264    def get_style(self, character: str) -> str:
265        """Return the named style for this character.
266
267        (this influences subtle aspects of their appearance, etc)
268        """
269        return ba.app.spaz_appearances[character].style
270
271    def get_media(self, character: str) -> dict[str, Any]:
272        """Return the set of media used by this variant of spaz."""
273        char = ba.app.spaz_appearances[character]
274        if character not in self.spaz_media:
275            media = self.spaz_media[character] = {
276                'jump_sounds': [ba.getsound(s) for s in char.jump_sounds],
277                'attack_sounds': [ba.getsound(s) for s in char.attack_sounds],
278                'impact_sounds': [ba.getsound(s) for s in char.impact_sounds],
279                'death_sounds': [ba.getsound(s) for s in char.death_sounds],
280                'pickup_sounds': [ba.getsound(s) for s in char.pickup_sounds],
281                'fall_sounds': [ba.getsound(s) for s in char.fall_sounds],
282                'color_texture': ba.gettexture(char.color_texture),
283                'color_mask_texture': ba.gettexture(char.color_mask_texture),
284                'head_model': ba.getmodel(char.head_model),
285                'torso_model': ba.getmodel(char.torso_model),
286                'pelvis_model': ba.getmodel(char.pelvis_model),
287                'upper_arm_model': ba.getmodel(char.upper_arm_model),
288                'forearm_model': ba.getmodel(char.forearm_model),
289                'hand_model': ba.getmodel(char.hand_model),
290                'upper_leg_model': ba.getmodel(char.upper_leg_model),
291                'lower_leg_model': ba.getmodel(char.lower_leg_model),
292                'toes_model': ba.getmodel(char.toes_model),
293            }
294        else:
295            media = self.spaz_media[character]
296        return media
297
298    @classmethod
299    def get(cls) -> SpazFactory:
300        """Return the shared ba.SpazFactory, creating it if necessary."""
301        # pylint: disable=cyclic-import
302        activity = ba.getactivity()
303        factory = activity.customdata.get(cls._STORENAME)
304        if factory is None:
305            factory = activity.customdata[cls._STORENAME] = SpazFactory()
306        assert isinstance(factory, SpazFactory)
307        return factory
class SpazFactory:
 18class SpazFactory:
 19    """Wraps up media and other resources used by ba.Spaz instances.
 20
 21    Category: **Gameplay Classes**
 22
 23    Generally one of these is created per ba.Activity and shared
 24    between all spaz instances. Use ba.Spaz.get_factory() to return
 25    the shared factory for the current activity.
 26    """
 27
 28    impact_sounds_medium: Sequence[ba.Sound]
 29    """A tuple of ba.Sound-s for when a ba.Spaz hits something kinda hard."""
 30
 31    impact_sounds_hard: Sequence[ba.Sound]
 32    """A tuple of ba.Sound-s for when a ba.Spaz hits something really hard."""
 33
 34    impact_sounds_harder: Sequence[ba.Sound]
 35    """A tuple of ba.Sound-s for when a ba.Spaz hits something really
 36       really hard."""
 37
 38    single_player_death_sound: ba.Sound
 39    """The sound that plays for an 'important' spaz death such as in
 40       co-op games."""
 41
 42    punch_sound_weak: ba.Sound
 43    """A weak punch ba.Sound."""
 44
 45    punch_sound: ba.Sound
 46    """A standard punch ba.Sound."""
 47
 48    punch_sound_strong: Sequence[ba.Sound]
 49    """A tuple of stronger sounding punch ba.Sounds."""
 50
 51    punch_sound_stronger: ba.Sound
 52    """A really really strong sounding punch ba.Sound."""
 53
 54    swish_sound: ba.Sound
 55    """A punch swish ba.Sound."""
 56
 57    block_sound: ba.Sound
 58    """A ba.Sound for when an attack is blocked by invincibility."""
 59
 60    shatter_sound: ba.Sound
 61    """A ba.Sound for when a frozen ba.Spaz shatters."""
 62
 63    splatter_sound: ba.Sound
 64    """A ba.Sound for when a ba.Spaz blows up via curse."""
 65
 66    spaz_material: ba.Material
 67    """A ba.Material applied to all of parts of a ba.Spaz."""
 68
 69    roller_material: ba.Material
 70    """A ba.Material applied to the invisible roller ball body that
 71       a ba.Spaz uses for locomotion."""
 72
 73    punch_material: ba.Material
 74    """A ba.Material applied to the 'fist' of a ba.Spaz."""
 75
 76    pickup_material: ba.Material
 77    """A ba.Material applied to the 'grabber' body of a ba.Spaz."""
 78
 79    curse_material: ba.Material
 80    """A ba.Material applied to a cursed ba.Spaz that triggers an explosion."""
 81
 82    _STORENAME = ba.storagename()
 83
 84    def _preload(self, character: str) -> None:
 85        """Preload media needed for a given character."""
 86        self.get_media(character)
 87
 88    def __init__(self) -> None:
 89        """Instantiate a factory object."""
 90        # pylint: disable=cyclic-import
 91        # FIXME: should probably put these somewhere common so we don't
 92        # have to import them from a module that imports us.
 93        from bastd.actor.spaz import (
 94            PickupMessage,
 95            PunchHitMessage,
 96            CurseExplodeMessage,
 97        )
 98
 99        shared = SharedObjects.get()
100        self.impact_sounds_medium = (
101            ba.getsound('impactMedium'),
102            ba.getsound('impactMedium2'),
103        )
104        self.impact_sounds_hard = (
105            ba.getsound('impactHard'),
106            ba.getsound('impactHard2'),
107            ba.getsound('impactHard3'),
108        )
109        self.impact_sounds_harder = (
110            ba.getsound('bigImpact'),
111            ba.getsound('bigImpact2'),
112        )
113        self.single_player_death_sound = ba.getsound('playerDeath')
114        self.punch_sound_weak = ba.getsound('punchWeak01')
115        self.punch_sound = ba.getsound('punch01')
116        self.punch_sound_strong = (
117            ba.getsound('punchStrong01'),
118            ba.getsound('punchStrong02'),
119        )
120        self.punch_sound_stronger = ba.getsound('superPunch')
121        self.swish_sound = ba.getsound('punchSwish')
122        self.block_sound = ba.getsound('block')
123        self.shatter_sound = ba.getsound('shatter')
124        self.splatter_sound = ba.getsound('splatter')
125        self.spaz_material = ba.Material()
126        self.roller_material = ba.Material()
127        self.punch_material = ba.Material()
128        self.pickup_material = ba.Material()
129        self.curse_material = ba.Material()
130
131        footing_material = shared.footing_material
132        object_material = shared.object_material
133        player_material = shared.player_material
134        region_material = shared.region_material
135
136        # Send footing messages to spazzes so they know when they're on
137        # solid ground.
138        # Eww; this probably should just be built into the spaz node.
139        self.roller_material.add_actions(
140            conditions=('they_have_material', footing_material),
141            actions=(
142                ('message', 'our_node', 'at_connect', 'footing', 1),
143                ('message', 'our_node', 'at_disconnect', 'footing', -1),
144            ),
145        )
146
147        self.spaz_material.add_actions(
148            conditions=('they_have_material', footing_material),
149            actions=(
150                ('message', 'our_node', 'at_connect', 'footing', 1),
151                ('message', 'our_node', 'at_disconnect', 'footing', -1),
152            ),
153        )
154
155        # Punches.
156        self.punch_material.add_actions(
157            conditions=('they_are_different_node_than_us',),
158            actions=(
159                ('modify_part_collision', 'collide', True),
160                ('modify_part_collision', 'physical', False),
161                ('message', 'our_node', 'at_connect', PunchHitMessage()),
162            ),
163        )
164
165        # Pickups.
166        self.pickup_material.add_actions(
167            conditions=(
168                ('they_are_different_node_than_us',),
169                'and',
170                ('they_have_material', object_material),
171            ),
172            actions=(
173                ('modify_part_collision', 'collide', True),
174                ('modify_part_collision', 'physical', False),
175                ('message', 'our_node', 'at_connect', PickupMessage()),
176            ),
177        )
178
179        # Curse.
180        self.curse_material.add_actions(
181            conditions=(
182                ('they_are_different_node_than_us',),
183                'and',
184                ('they_have_material', player_material),
185            ),
186            actions=(
187                'message',
188                'our_node',
189                'at_connect',
190                CurseExplodeMessage(),
191            ),
192        )
193
194        self.foot_impact_sounds = (
195            ba.getsound('footImpact01'),
196            ba.getsound('footImpact02'),
197            ba.getsound('footImpact03'),
198        )
199
200        self.foot_skid_sound = ba.getsound('skid01')
201        self.foot_roll_sound = ba.getsound('scamper01')
202
203        self.roller_material.add_actions(
204            conditions=('they_have_material', footing_material),
205            actions=(
206                ('impact_sound', self.foot_impact_sounds, 1, 0.2),
207                ('skid_sound', self.foot_skid_sound, 20, 0.3),
208                ('roll_sound', self.foot_roll_sound, 20, 3.0),
209            ),
210        )
211
212        self.skid_sound = ba.getsound('gravelSkid')
213
214        self.spaz_material.add_actions(
215            conditions=('they_have_material', footing_material),
216            actions=(
217                ('impact_sound', self.foot_impact_sounds, 20, 6),
218                ('skid_sound', self.skid_sound, 2.0, 1),
219                ('roll_sound', self.skid_sound, 2.0, 1),
220            ),
221        )
222
223        self.shield_up_sound = ba.getsound('shieldUp')
224        self.shield_down_sound = ba.getsound('shieldDown')
225        self.shield_hit_sound = ba.getsound('shieldHit')
226
227        # We don't want to collide with stuff we're initially overlapping
228        # (unless its marked with a special region material).
229        self.spaz_material.add_actions(
230            conditions=(
231                (
232                    ('we_are_younger_than', 51),
233                    'and',
234                    ('they_are_different_node_than_us',),
235                ),
236                'and',
237                ('they_dont_have_material', region_material),
238            ),
239            actions=('modify_node_collision', 'collide', False),
240        )
241
242        self.spaz_media: dict[str, Any] = {}
243
244        # Lets load some basic rules.
245        # (allows them to be tweaked from the master server)
246        self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val(
247            'rsdr', 10.0
248        )
249        self.punch_cooldown = ba.internal.get_v1_account_misc_read_val(
250            'rpc', 400
251        )
252        self.punch_cooldown_gloves = ba.internal.get_v1_account_misc_read_val(
253            'rpcg', 300
254        )
255        self.punch_power_scale = ba.internal.get_v1_account_misc_read_val(
256            'rpp', 1.2
257        )
258        self.punch_power_scale_gloves = (
259            ba.internal.get_v1_account_misc_read_val('rppg', 1.4)
260        )
261        self.max_shield_spillover_damage = (
262            ba.internal.get_v1_account_misc_read_val('rsms', 500)
263        )
264
265    def get_style(self, character: str) -> str:
266        """Return the named style for this character.
267
268        (this influences subtle aspects of their appearance, etc)
269        """
270        return ba.app.spaz_appearances[character].style
271
272    def get_media(self, character: str) -> dict[str, Any]:
273        """Return the set of media used by this variant of spaz."""
274        char = ba.app.spaz_appearances[character]
275        if character not in self.spaz_media:
276            media = self.spaz_media[character] = {
277                'jump_sounds': [ba.getsound(s) for s in char.jump_sounds],
278                'attack_sounds': [ba.getsound(s) for s in char.attack_sounds],
279                'impact_sounds': [ba.getsound(s) for s in char.impact_sounds],
280                'death_sounds': [ba.getsound(s) for s in char.death_sounds],
281                'pickup_sounds': [ba.getsound(s) for s in char.pickup_sounds],
282                'fall_sounds': [ba.getsound(s) for s in char.fall_sounds],
283                'color_texture': ba.gettexture(char.color_texture),
284                'color_mask_texture': ba.gettexture(char.color_mask_texture),
285                'head_model': ba.getmodel(char.head_model),
286                'torso_model': ba.getmodel(char.torso_model),
287                'pelvis_model': ba.getmodel(char.pelvis_model),
288                'upper_arm_model': ba.getmodel(char.upper_arm_model),
289                'forearm_model': ba.getmodel(char.forearm_model),
290                'hand_model': ba.getmodel(char.hand_model),
291                'upper_leg_model': ba.getmodel(char.upper_leg_model),
292                'lower_leg_model': ba.getmodel(char.lower_leg_model),
293                'toes_model': ba.getmodel(char.toes_model),
294            }
295        else:
296            media = self.spaz_media[character]
297        return media
298
299    @classmethod
300    def get(cls) -> SpazFactory:
301        """Return the shared ba.SpazFactory, creating it if necessary."""
302        # pylint: disable=cyclic-import
303        activity = ba.getactivity()
304        factory = activity.customdata.get(cls._STORENAME)
305        if factory is None:
306            factory = activity.customdata[cls._STORENAME] = SpazFactory()
307        assert isinstance(factory, SpazFactory)
308        return factory

Wraps up media and other resources used by ba.Spaz instances.

Category: Gameplay Classes

Generally one of these is created per ba.Activity and shared between all spaz instances. Use ba.Spaz.get_factory() to return the shared factory for the current activity.

SpazFactory()
 88    def __init__(self) -> None:
 89        """Instantiate a factory object."""
 90        # pylint: disable=cyclic-import
 91        # FIXME: should probably put these somewhere common so we don't
 92        # have to import them from a module that imports us.
 93        from bastd.actor.spaz import (
 94            PickupMessage,
 95            PunchHitMessage,
 96            CurseExplodeMessage,
 97        )
 98
 99        shared = SharedObjects.get()
100        self.impact_sounds_medium = (
101            ba.getsound('impactMedium'),
102            ba.getsound('impactMedium2'),
103        )
104        self.impact_sounds_hard = (
105            ba.getsound('impactHard'),
106            ba.getsound('impactHard2'),
107            ba.getsound('impactHard3'),
108        )
109        self.impact_sounds_harder = (
110            ba.getsound('bigImpact'),
111            ba.getsound('bigImpact2'),
112        )
113        self.single_player_death_sound = ba.getsound('playerDeath')
114        self.punch_sound_weak = ba.getsound('punchWeak01')
115        self.punch_sound = ba.getsound('punch01')
116        self.punch_sound_strong = (
117            ba.getsound('punchStrong01'),
118            ba.getsound('punchStrong02'),
119        )
120        self.punch_sound_stronger = ba.getsound('superPunch')
121        self.swish_sound = ba.getsound('punchSwish')
122        self.block_sound = ba.getsound('block')
123        self.shatter_sound = ba.getsound('shatter')
124        self.splatter_sound = ba.getsound('splatter')
125        self.spaz_material = ba.Material()
126        self.roller_material = ba.Material()
127        self.punch_material = ba.Material()
128        self.pickup_material = ba.Material()
129        self.curse_material = ba.Material()
130
131        footing_material = shared.footing_material
132        object_material = shared.object_material
133        player_material = shared.player_material
134        region_material = shared.region_material
135
136        # Send footing messages to spazzes so they know when they're on
137        # solid ground.
138        # Eww; this probably should just be built into the spaz node.
139        self.roller_material.add_actions(
140            conditions=('they_have_material', footing_material),
141            actions=(
142                ('message', 'our_node', 'at_connect', 'footing', 1),
143                ('message', 'our_node', 'at_disconnect', 'footing', -1),
144            ),
145        )
146
147        self.spaz_material.add_actions(
148            conditions=('they_have_material', footing_material),
149            actions=(
150                ('message', 'our_node', 'at_connect', 'footing', 1),
151                ('message', 'our_node', 'at_disconnect', 'footing', -1),
152            ),
153        )
154
155        # Punches.
156        self.punch_material.add_actions(
157            conditions=('they_are_different_node_than_us',),
158            actions=(
159                ('modify_part_collision', 'collide', True),
160                ('modify_part_collision', 'physical', False),
161                ('message', 'our_node', 'at_connect', PunchHitMessage()),
162            ),
163        )
164
165        # Pickups.
166        self.pickup_material.add_actions(
167            conditions=(
168                ('they_are_different_node_than_us',),
169                'and',
170                ('they_have_material', object_material),
171            ),
172            actions=(
173                ('modify_part_collision', 'collide', True),
174                ('modify_part_collision', 'physical', False),
175                ('message', 'our_node', 'at_connect', PickupMessage()),
176            ),
177        )
178
179        # Curse.
180        self.curse_material.add_actions(
181            conditions=(
182                ('they_are_different_node_than_us',),
183                'and',
184                ('they_have_material', player_material),
185            ),
186            actions=(
187                'message',
188                'our_node',
189                'at_connect',
190                CurseExplodeMessage(),
191            ),
192        )
193
194        self.foot_impact_sounds = (
195            ba.getsound('footImpact01'),
196            ba.getsound('footImpact02'),
197            ba.getsound('footImpact03'),
198        )
199
200        self.foot_skid_sound = ba.getsound('skid01')
201        self.foot_roll_sound = ba.getsound('scamper01')
202
203        self.roller_material.add_actions(
204            conditions=('they_have_material', footing_material),
205            actions=(
206                ('impact_sound', self.foot_impact_sounds, 1, 0.2),
207                ('skid_sound', self.foot_skid_sound, 20, 0.3),
208                ('roll_sound', self.foot_roll_sound, 20, 3.0),
209            ),
210        )
211
212        self.skid_sound = ba.getsound('gravelSkid')
213
214        self.spaz_material.add_actions(
215            conditions=('they_have_material', footing_material),
216            actions=(
217                ('impact_sound', self.foot_impact_sounds, 20, 6),
218                ('skid_sound', self.skid_sound, 2.0, 1),
219                ('roll_sound', self.skid_sound, 2.0, 1),
220            ),
221        )
222
223        self.shield_up_sound = ba.getsound('shieldUp')
224        self.shield_down_sound = ba.getsound('shieldDown')
225        self.shield_hit_sound = ba.getsound('shieldHit')
226
227        # We don't want to collide with stuff we're initially overlapping
228        # (unless its marked with a special region material).
229        self.spaz_material.add_actions(
230            conditions=(
231                (
232                    ('we_are_younger_than', 51),
233                    'and',
234                    ('they_are_different_node_than_us',),
235                ),
236                'and',
237                ('they_dont_have_material', region_material),
238            ),
239            actions=('modify_node_collision', 'collide', False),
240        )
241
242        self.spaz_media: dict[str, Any] = {}
243
244        # Lets load some basic rules.
245        # (allows them to be tweaked from the master server)
246        self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val(
247            'rsdr', 10.0
248        )
249        self.punch_cooldown = ba.internal.get_v1_account_misc_read_val(
250            'rpc', 400
251        )
252        self.punch_cooldown_gloves = ba.internal.get_v1_account_misc_read_val(
253            'rpcg', 300
254        )
255        self.punch_power_scale = ba.internal.get_v1_account_misc_read_val(
256            'rpp', 1.2
257        )
258        self.punch_power_scale_gloves = (
259            ba.internal.get_v1_account_misc_read_val('rppg', 1.4)
260        )
261        self.max_shield_spillover_damage = (
262            ba.internal.get_v1_account_misc_read_val('rsms', 500)
263        )

Instantiate a factory object.

impact_sounds_medium: Sequence[_ba.Sound]

A tuple of ba.Sound-s for when a ba.Spaz hits something kinda hard.

impact_sounds_hard: Sequence[_ba.Sound]

A tuple of ba.Sound-s for when a ba.Spaz hits something really hard.

impact_sounds_harder: Sequence[_ba.Sound]

A tuple of ba.Sound-s for when a ba.Spaz hits something really really hard.

single_player_death_sound: _ba.Sound

The sound that plays for an 'important' spaz death such as in co-op games.

punch_sound_weak: _ba.Sound

A weak punch ba.Sound.

punch_sound: _ba.Sound

A standard punch ba.Sound.

punch_sound_strong: Sequence[_ba.Sound]

A tuple of stronger sounding punch ba.Sounds.

punch_sound_stronger: _ba.Sound

A really really strong sounding punch ba.Sound.

swish_sound: _ba.Sound

A punch swish ba.Sound.

block_sound: _ba.Sound

A ba.Sound for when an attack is blocked by invincibility.

shatter_sound: _ba.Sound

A ba.Sound for when a frozen ba.Spaz shatters.

splatter_sound: _ba.Sound

A ba.Sound for when a ba.Spaz blows up via curse.

spaz_material: _ba.Material

A ba.Material applied to all of parts of a ba.Spaz.

roller_material: _ba.Material

A ba.Material applied to the invisible roller ball body that a ba.Spaz uses for locomotion.

punch_material: _ba.Material

A ba.Material applied to the 'fist' of a ba.Spaz.

pickup_material: _ba.Material

A ba.Material applied to the 'grabber' body of a ba.Spaz.

curse_material: _ba.Material

A ba.Material applied to a cursed ba.Spaz that triggers an explosion.

def get_style(self, character: str) -> str:
265    def get_style(self, character: str) -> str:
266        """Return the named style for this character.
267
268        (this influences subtle aspects of their appearance, etc)
269        """
270        return ba.app.spaz_appearances[character].style

Return the named style for this character.

(this influences subtle aspects of their appearance, etc)

def get_media(self, character: str) -> dict[str, typing.Any]:
272    def get_media(self, character: str) -> dict[str, Any]:
273        """Return the set of media used by this variant of spaz."""
274        char = ba.app.spaz_appearances[character]
275        if character not in self.spaz_media:
276            media = self.spaz_media[character] = {
277                'jump_sounds': [ba.getsound(s) for s in char.jump_sounds],
278                'attack_sounds': [ba.getsound(s) for s in char.attack_sounds],
279                'impact_sounds': [ba.getsound(s) for s in char.impact_sounds],
280                'death_sounds': [ba.getsound(s) for s in char.death_sounds],
281                'pickup_sounds': [ba.getsound(s) for s in char.pickup_sounds],
282                'fall_sounds': [ba.getsound(s) for s in char.fall_sounds],
283                'color_texture': ba.gettexture(char.color_texture),
284                'color_mask_texture': ba.gettexture(char.color_mask_texture),
285                'head_model': ba.getmodel(char.head_model),
286                'torso_model': ba.getmodel(char.torso_model),
287                'pelvis_model': ba.getmodel(char.pelvis_model),
288                'upper_arm_model': ba.getmodel(char.upper_arm_model),
289                'forearm_model': ba.getmodel(char.forearm_model),
290                'hand_model': ba.getmodel(char.hand_model),
291                'upper_leg_model': ba.getmodel(char.upper_leg_model),
292                'lower_leg_model': ba.getmodel(char.lower_leg_model),
293                'toes_model': ba.getmodel(char.toes_model),
294            }
295        else:
296            media = self.spaz_media[character]
297        return media

Return the set of media used by this variant of spaz.

@classmethod
def get(cls) -> bastd.actor.spazfactory.SpazFactory:
299    @classmethod
300    def get(cls) -> SpazFactory:
301        """Return the shared ba.SpazFactory, creating it if necessary."""
302        # pylint: disable=cyclic-import
303        activity = ba.getactivity()
304        factory = activity.customdata.get(cls._STORENAME)
305        if factory is None:
306            factory = activity.customdata[cls._STORENAME] = SpazFactory()
307        assert isinstance(factory, SpazFactory)
308        return factory

Return the shared ba.SpazFactory, creating it if necessary.