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

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

Category: Gameplay Classes

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

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

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

@classmethod
def get(cls) -> SpazFactory:
298    @classmethod
299    def get(cls) -> SpazFactory:
300        """Return the shared bs.SpazFactory, creating it if necessary."""
301        # pylint: disable=cyclic-import
302        activity = bs.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

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