# Released under the MIT License. See LICENSE for details.
#
"""Provides a factory object from creating Spazzes."""
from __future__ import annotations
from typing import TYPE_CHECKING
import bascenev1 as bs
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence
[docs]
class SpazFactory:
"""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.
"""
impact_sounds_medium: Sequence[bs.Sound]
"""A tuple of bs.Sound-s for when a bs.Spaz hits something kinda hard."""
impact_sounds_hard: Sequence[bs.Sound]
"""A tuple of bs.Sound-s for when a bs.Spaz hits something really hard."""
impact_sounds_harder: Sequence[bs.Sound]
"""A tuple of bs.Sound-s for when a bs.Spaz hits something really
really hard."""
single_player_death_sound: bs.Sound
"""The sound that plays for an 'important' spaz death such as in
co-op games."""
punch_sound_weak: bs.Sound
"""A weak punch bs.Sound."""
punch_sound: bs.Sound
"""A standard punch bs.Sound."""
punch_sound_strong: Sequence[bs.Sound]
"""A tuple of stronger sounding punch bs.Sounds."""
punch_sound_stronger: bs.Sound
"""A really really strong sounding punch bs.Sound."""
swish_sound: bs.Sound
"""A punch swish bs.Sound."""
block_sound: bs.Sound
"""A bs.Sound for when an attack is blocked by invincibility."""
shatter_sound: bs.Sound
"""A bs.Sound for when a frozen bs.Spaz shatters."""
splatter_sound: bs.Sound
"""A bs.Sound for when a bs.Spaz blows up via curse."""
spaz_material: bs.Material
"""A bs.Material applied to all of parts of a bs.Spaz."""
roller_material: bs.Material
"""A bs.Material applied to the invisible roller ball body that
a bs.Spaz uses for locomotion."""
punch_material: bs.Material
"""A bs.Material applied to the 'fist' of a bs.Spaz."""
pickup_material: bs.Material
"""A bs.Material applied to the 'grabber' body of a bs.Spaz."""
curse_material: bs.Material
"""A bs.Material applied to a cursed bs.Spaz that triggers an explosion."""
_STORENAME = bs.storagename()
def _preload(self, character: str) -> None:
"""Preload media needed for a given character."""
self.get_media(character)
def __init__(self) -> None:
"""Instantiate a factory object."""
# pylint: disable=cyclic-import
plus = bs.app.plus
assert plus is not None
# FIXME: should probably put these somewhere common so we don't
# have to import them from a module that imports us.
from bascenev1lib.actor.spaz import (
PickupMessage,
PunchHitMessage,
CurseExplodeMessage,
)
shared = SharedObjects.get()
self.impact_sounds_medium = (
bs.getsound('impactMedium'),
bs.getsound('impactMedium2'),
)
self.impact_sounds_hard = (
bs.getsound('impactHard'),
bs.getsound('impactHard2'),
bs.getsound('impactHard3'),
)
self.impact_sounds_harder = (
bs.getsound('bigImpact'),
bs.getsound('bigImpact2'),
)
self.single_player_death_sound = bs.getsound('playerDeath')
self.punch_sound_weak = bs.getsound('punchWeak01')
self.punch_sound = bs.getsound('punch01')
self.punch_sound_strong = (
bs.getsound('punchStrong01'),
bs.getsound('punchStrong02'),
)
self.punch_sound_stronger = bs.getsound('superPunch')
self.swish_sound = bs.getsound('punchSwish')
self.block_sound = bs.getsound('block')
self.shatter_sound = bs.getsound('shatter')
self.splatter_sound = bs.getsound('splatter')
self.spaz_material = bs.Material()
self.roller_material = bs.Material()
self.punch_material = bs.Material()
self.pickup_material = bs.Material()
self.curse_material = bs.Material()
footing_material = shared.footing_material
object_material = shared.object_material
player_material = shared.player_material
region_material = shared.region_material
# Send footing messages to spazzes so they know when they're on
# solid ground.
# Eww; this probably should just be built into the spaz node.
self.roller_material.add_actions(
conditions=('they_have_material', footing_material),
actions=(
('message', 'our_node', 'at_connect', 'footing', 1),
('message', 'our_node', 'at_disconnect', 'footing', -1),
),
)
self.spaz_material.add_actions(
conditions=('they_have_material', footing_material),
actions=(
('message', 'our_node', 'at_connect', 'footing', 1),
('message', 'our_node', 'at_disconnect', 'footing', -1),
),
)
# Punches.
self.punch_material.add_actions(
conditions=('they_are_different_node_than_us',),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('message', 'our_node', 'at_connect', PunchHitMessage()),
),
)
# Pickups.
self.pickup_material.add_actions(
conditions=(
('they_are_different_node_than_us',),
'and',
('they_have_material', object_material),
),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('message', 'our_node', 'at_connect', PickupMessage()),
),
)
# Curse.
self.curse_material.add_actions(
conditions=(
('they_are_different_node_than_us',),
'and',
('they_have_material', player_material),
),
actions=(
'message',
'our_node',
'at_connect',
CurseExplodeMessage(),
),
)
self.foot_impact_sounds = (
bs.getsound('footImpact01'),
bs.getsound('footImpact02'),
bs.getsound('footImpact03'),
)
self.foot_skid_sound = bs.getsound('skid01')
self.foot_roll_sound = bs.getsound('scamper01')
self.roller_material.add_actions(
conditions=('they_have_material', footing_material),
actions=(
('impact_sound', self.foot_impact_sounds, 1, 0.2),
('skid_sound', self.foot_skid_sound, 20, 0.3),
('roll_sound', self.foot_roll_sound, 20, 3.0),
),
)
self.skid_sound = bs.getsound('gravelSkid')
self.spaz_material.add_actions(
conditions=('they_have_material', footing_material),
actions=(
('impact_sound', self.foot_impact_sounds, 20, 6),
('skid_sound', self.skid_sound, 2.0, 1),
('roll_sound', self.skid_sound, 2.0, 1),
),
)
self.shield_up_sound = bs.getsound('shieldUp')
self.shield_down_sound = bs.getsound('shieldDown')
self.shield_hit_sound = bs.getsound('shieldHit')
# We don't want to collide with stuff we're initially overlapping
# (unless its marked with a special region material).
self.spaz_material.add_actions(
conditions=(
(
('we_are_younger_than', 51),
'and',
('they_are_different_node_than_us',),
),
'and',
('they_dont_have_material', region_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.spaz_media: dict[str, Any] = {}
# Lets load some basic rules.
# (allows them to be tweaked from the master server)
self.shield_decay_rate = plus.get_v1_account_misc_read_val('rsdr', 10.0)
self.punch_cooldown = plus.get_v1_account_misc_read_val('rpc', 400)
self.punch_cooldown_gloves = plus.get_v1_account_misc_read_val(
'rpcg', 300
)
self.punch_power_scale = plus.get_v1_account_misc_read_val('rpp', 1.2)
self.punch_power_scale_gloves = plus.get_v1_account_misc_read_val(
'rppg', 1.4
)
self.max_shield_spillover_damage = plus.get_v1_account_misc_read_val(
'rsms', 500
)
[docs]
def get_style(self, character: str) -> str:
"""Return the named style for this character.
(this influences subtle aspects of their appearance, etc)
"""
assert bs.app.classic is not None
return bs.app.classic.spaz_appearances[character].style
[docs]
@classmethod
def get(cls) -> SpazFactory:
"""Return the shared bs.SpazFactory, creating it if necessary."""
# pylint: disable=cyclic-import
activity = bs.getactivity()
factory = activity.customdata.get(cls._STORENAME)
if factory is None:
factory = activity.customdata[cls._STORENAME] = SpazFactory()
assert isinstance(factory, SpazFactory)
return factory