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

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

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

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

Instantiate a factory object.

impact_sounds_medium: Sequence[_bascenev1.Sound]

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

impact_sounds_hard: Sequence[_bascenev1.Sound]

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

impact_sounds_harder: Sequence[_bascenev1.Sound]

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

single_player_death_sound: _bascenev1.Sound

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

punch_sound_weak: _bascenev1.Sound

A weak punch bs.Sound.

punch_sound: _bascenev1.Sound

A standard punch bs.Sound.

punch_sound_strong: Sequence[_bascenev1.Sound]

A tuple of stronger sounding punch bs.Sounds.

punch_sound_stronger: _bascenev1.Sound

A really really strong sounding punch bs.Sound.

swish_sound: _bascenev1.Sound

A punch swish bs.Sound.

block_sound: _bascenev1.Sound

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

shatter_sound: _bascenev1.Sound

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

splatter_sound: _bascenev1.Sound

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

spaz_material: _bascenev1.Material

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

roller_material: _bascenev1.Material

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

punch_material: _bascenev1.Material

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

pickup_material: _bascenev1.Material

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

curse_material: _bascenev1.Material

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

foot_impact_sounds
foot_skid_sound
foot_roll_sound
skid_sound
shield_up_sound
shield_down_sound
shield_hit_sound
spaz_media: dict[str, typing.Any]
shield_decay_rate
punch_cooldown
punch_cooldown_gloves
punch_power_scale
punch_power_scale_gloves
max_shield_spillover_damage
def get_style(self, character: str) -> str:
260    def get_style(self, character: str) -> str:
261        """Return the named style for this character.
262
263        (this influences subtle aspects of their appearance, etc)
264        """
265        assert bs.app.classic is not None
266        return bs.app.classic.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]:
268    def get_media(self, character: str) -> dict[str, Any]:
269        """Return the set of media used by this variant of spaz."""
270        assert bs.app.classic is not None
271        char = bs.app.classic.spaz_appearances[character]
272        if character not in self.spaz_media:
273            media = self.spaz_media[character] = {
274                'jump_sounds': [bs.getsound(s) for s in char.jump_sounds],
275                'attack_sounds': [bs.getsound(s) for s in char.attack_sounds],
276                'impact_sounds': [bs.getsound(s) for s in char.impact_sounds],
277                'death_sounds': [bs.getsound(s) for s in char.death_sounds],
278                'pickup_sounds': [bs.getsound(s) for s in char.pickup_sounds],
279                'fall_sounds': [bs.getsound(s) for s in char.fall_sounds],
280                'color_texture': bs.gettexture(char.color_texture),
281                'color_mask_texture': bs.gettexture(char.color_mask_texture),
282                'head_mesh': bs.getmesh(char.head_mesh),
283                'torso_mesh': bs.getmesh(char.torso_mesh),
284                'pelvis_mesh': bs.getmesh(char.pelvis_mesh),
285                'upper_arm_mesh': bs.getmesh(char.upper_arm_mesh),
286                'forearm_mesh': bs.getmesh(char.forearm_mesh),
287                'hand_mesh': bs.getmesh(char.hand_mesh),
288                'upper_leg_mesh': bs.getmesh(char.upper_leg_mesh),
289                'lower_leg_mesh': bs.getmesh(char.lower_leg_mesh),
290                'toes_mesh': bs.getmesh(char.toes_mesh),
291            }
292        else:
293            media = self.spaz_media[character]
294        return media

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

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

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