class BombFactory:
    """Wraps up media and other resources used by ba.Bombs.

    category: Gameplay Classes

    A single instance of this is shared between all bombs
    and can be retrieved via bastd.actor.bomb.get_factory().

    Attributes:

       bomb_model
          The ba.Model of a standard or ice bomb.

       sticky_bomb_model
          The ba.Model of a sticky-bomb.

       impact_bomb_model
          The ba.Model of an impact-bomb.

       land_mine_model
          The ba.Model of a land-mine.

       tnt_model
          The ba.Model of a tnt box.

       regular_tex
          The ba.Texture for regular bombs.

       ice_tex
          The ba.Texture for ice bombs.

       sticky_tex
          The ba.Texture for sticky bombs.

       impact_tex
          The ba.Texture for impact bombs.

       impact_lit_tex
          The ba.Texture for impact bombs with lights lit.

       land_mine_tex
          The ba.Texture for land-mines.

       land_mine_lit_tex
          The ba.Texture for land-mines with the light lit.

       tnt_tex
          The ba.Texture for tnt boxes.

       hiss_sound
          The ba.Sound for the hiss sound an ice bomb makes.

       debris_fall_sound
          The ba.Sound for random falling debris after an explosion.

       wood_debris_fall_sound
          A ba.Sound for random wood debris falling after an explosion.

       explode_sounds
          A tuple of ba.Sounds for explosions.

       freeze_sound
          A ba.Sound of an ice bomb freezing something.

       fuse_sound
          A ba.Sound of a burning fuse.

       activate_sound
          A ba.Sound for an activating impact bomb.

       warn_sound
          A ba.Sound for an impact bomb about to explode due to time-out.

       bomb_material
          A ba.Material applied to all bombs.

       normal_sound_material
          A ba.Material that generates standard bomb noises on impacts, etc.

       sticky_material
          A ba.Material that makes 'splat' sounds and makes collisions softer.

       land_mine_no_explode_material
          A ba.Material that keeps land-mines from blowing up.
          Applied to land-mines when they are created to allow land-mines to
          touch without exploding.

       land_mine_blast_material
          A ba.Material applied to activated land-mines that causes them to
          explode on impact.

       impact_blast_material
          A ba.Material applied to activated impact-bombs that causes them to
          explode on impact.

       blast_material
          A ba.Material applied to bomb blast geometry which triggers impact
          events with what it touches.

       dink_sounds
          A tuple of ba.Sounds for when bombs hit the ground.

       sticky_impact_sound
          The ba.Sound for a squish made by a sticky bomb hitting something.

       roll_sound
          ba.Sound for a rolling bomb.
    """

    _STORENAME = ba.storagename()

    @classmethod
    def get(cls) -> BombFactory:
        """Get/create a shared bastd.actor.bomb.BombFactory object."""
        activity = ba.getactivity()
        factory = activity.customdata.get(cls._STORENAME)
        if factory is None:
            factory = BombFactory()
            activity.customdata[cls._STORENAME] = factory
        assert isinstance(factory, BombFactory)
        return factory

    def random_explode_sound(self) -> ba.Sound:
        """Return a random explosion ba.Sound from the factory."""
        return self.explode_sounds[random.randrange(len(self.explode_sounds))]

    def __init__(self) -> None:
        """Instantiate a BombFactory.

        You shouldn't need to do this; call bastd.actor.bomb.get_factory()
        to get a shared instance.
        """
        shared = SharedObjects.get()

        self.bomb_model = ba.getmodel('bomb')
        self.sticky_bomb_model = ba.getmodel('bombSticky')
        self.impact_bomb_model = ba.getmodel('impactBomb')
        self.land_mine_model = ba.getmodel('landMine')
        self.tnt_model = ba.getmodel('tnt')

        self.regular_tex = ba.gettexture('bombColor')
        self.ice_tex = ba.gettexture('bombColorIce')
        self.sticky_tex = ba.gettexture('bombStickyColor')
        self.impact_tex = ba.gettexture('impactBombColor')
        self.impact_lit_tex = ba.gettexture('impactBombColorLit')
        self.land_mine_tex = ba.gettexture('landMine')
        self.land_mine_lit_tex = ba.gettexture('landMineLit')
        self.tnt_tex = ba.gettexture('tnt')

        self.hiss_sound = ba.getsound('hiss')
        self.debris_fall_sound = ba.getsound('debrisFall')
        self.wood_debris_fall_sound = ba.getsound('woodDebrisFall')

        self.explode_sounds = (ba.getsound('explosion01'),
                               ba.getsound('explosion02'),
                               ba.getsound('explosion03'),
                               ba.getsound('explosion04'),
                               ba.getsound('explosion05'))

        self.freeze_sound = ba.getsound('freeze')
        self.fuse_sound = ba.getsound('fuse01')
        self.activate_sound = ba.getsound('activateBeep')
        self.warn_sound = ba.getsound('warnBeep')

        # Set up our material so new bombs don't collide with objects
        # that they are initially overlapping.
        self.bomb_material = ba.Material()
        self.normal_sound_material = ba.Material()
        self.sticky_material = ba.Material()

        self.bomb_material.add_actions(
            conditions=(
                (
                    ('we_are_younger_than', 100),
                    'or',
                    ('they_are_younger_than', 100),
                ),
                'and',
                ('they_have_material', shared.object_material),
            ),
            actions=('modify_node_collision', 'collide', False),
        )

        # We want pickup materials to always hit us even if we're currently
        # not colliding with their node. (generally due to the above rule)
        self.bomb_material.add_actions(
            conditions=('they_have_material', shared.pickup_material),
            actions=('modify_part_collision', 'use_node_collide', False),
        )

        self.bomb_material.add_actions(actions=('modify_part_collision',
                                                'friction', 0.3))

        self.land_mine_no_explode_material = ba.Material()
        self.land_mine_blast_material = ba.Material()
        self.land_mine_blast_material.add_actions(
            conditions=(
                ('we_are_older_than', 200),
                'and',
                ('they_are_older_than', 200),
                'and',
                ('eval_colliding', ),
                'and',
                (
                    ('they_dont_have_material',
                     self.land_mine_no_explode_material),
                    'and',
                    (
                        ('they_have_material', shared.object_material),
                        'or',
                        ('they_have_material', shared.player_material),
                    ),
                ),
            ),
            actions=('message', 'our_node', 'at_connect', ImpactMessage()),
        )

        self.impact_blast_material = ba.Material()
        self.impact_blast_material.add_actions(
            conditions=(
                ('we_are_older_than', 200),
                'and',
                ('they_are_older_than', 200),
                'and',
                ('eval_colliding', ),
                'and',
                (
                    ('they_have_material', shared.footing_material),
                    'or',
                    ('they_have_material', shared.object_material),
                ),
            ),
            actions=('message', 'our_node', 'at_connect', ImpactMessage()),
        )

        self.blast_material = ba.Material()
        self.blast_material.add_actions(
            conditions=('they_have_material', shared.object_material),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('message', 'our_node', 'at_connect', ExplodeHitMessage()),
            ),
        )

        self.dink_sounds = (ba.getsound('bombDrop01'),
                            ba.getsound('bombDrop02'))
        self.sticky_impact_sound = ba.getsound('stickyImpact')
        self.roll_sound = ba.getsound('bombRoll01')

        # Collision sounds.
        self.normal_sound_material.add_actions(
            conditions=('they_have_material', shared.footing_material),
            actions=(
                ('impact_sound', self.dink_sounds, 2, 0.8),
                ('roll_sound', self.roll_sound, 3, 6),
            ))

        self.sticky_material.add_actions(actions=(('modify_part_collision',
                                                   'stiffness', 0.1),
                                                  ('modify_part_collision',
                                                   'damping', 1.0)))

        self.sticky_material.add_actions(
            conditions=(
                ('they_have_material', shared.player_material),
                'or',
                ('they_have_material', shared.footing_material),
            ),
            actions=('message', 'our_node', 'at_connect', SplatMessage()),
        )
class Scoreboard:
    """A display for player or team scores during a game.

    category: Gameplay Classes
    """

    _ENTRYSTORENAME = ba.storagename('entry')

    def __init__(self, label: ba.Lstr = None, score_split: float = 0.7):
        """Instantiate a scoreboard.

        Label can be something like 'points' and will
        show up on boards if provided.
        """
        self._flat_tex = ba.gettexture('null')
        self._entries: Dict[int, _Entry] = {}
        self._label = label
        self.score_split = score_split

        # For free-for-all we go simpler since we have one per player.
        self._pos: Sequence[float]
        if isinstance(ba.getsession(), ba.FreeForAllSession):
            self._do_cover = False
            self._spacing = 35.0
            self._pos = (17.0, -65.0)
            self._scale = 0.8
            self._flash_length = 0.5
        else:
            self._do_cover = True
            self._spacing = 50.0
            self._pos = (20.0, -70.0)
            self._scale = 1.0
            self._flash_length = 1.0

    def set_team_value(self,
                       team: ba.Team,
                       score: float,
                       max_score: float = None,
                       countdown: bool = False,
                       flash: bool = True,
                       show_value: bool = True) -> None:
        """Update the score-board display for the given ba.Team."""
        if not team.id in self._entries:
            self._add_team(team)

            # Create a proxy in the team which will kill
            # our entry when it dies (for convenience)
            assert self._ENTRYSTORENAME not in team.customdata
            team.customdata[self._ENTRYSTORENAME] = _EntryProxy(self, team)

        # Now set the entry.
        self._entries[team.id].set_value(score=score,
                                         max_score=max_score,
                                         countdown=countdown,
                                         flash=flash,
                                         show_value=show_value)

    def _add_team(self, team: ba.Team) -> None:
        if team.id in self._entries:
            raise RuntimeError('Duplicate team add')
        self._entries[team.id] = _Entry(self,
                                        team,
                                        do_cover=self._do_cover,
                                        scale=self._scale,
                                        label=self._label,
                                        flash_length=self._flash_length)
        self._update_teams()

    def remove_team(self, team_id: int) -> None:
        """Remove the team with the given id from the scoreboard."""
        del self._entries[team_id]
        self._update_teams()

    def _update_teams(self) -> None:
        pos = list(self._pos)
        for entry in list(self._entries.values()):
            entry.set_position(pos)
            pos[1] -= self._spacing * self._scale
Esempio n. 3
0
class SpazFactory:
    """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.

    Attributes:

       impact_sounds_medium
          A tuple of ba.Sounds for when a ba.Spaz hits something kinda hard.

       impact_sounds_hard
          A tuple of ba.Sounds for when a ba.Spaz hits something really hard.

       impact_sounds_harder
          A tuple of ba.Sounds for when a ba.Spaz hits something really
          really hard.

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

       punch_sound
          A standard punch ba.Sound.

       punch_sound_strong
          A tuple of stronger sounding punch ba.Sounds.

       punch_sound_stronger
          A really really strong sounding punch ba.Sound.

       swish_sound
          A punch swish ba.Sound.

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

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

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

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

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

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

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

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

    _STORENAME = ba.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
        # FIXME: should probably put these somewhere common so we don't
        # have to import them from a module that imports us.
        from bastd.actor.spaz import (PickupMessage, PunchHitMessage,
                                      CurseExplodeMessage)

        shared = SharedObjects.get()
        self.impact_sounds_medium = (ba.getsound('impactMedium'),
                                     ba.getsound('impactMedium2'))
        self.impact_sounds_hard = (ba.getsound('impactHard'),
                                   ba.getsound('impactHard2'),
                                   ba.getsound('impactHard3'))
        self.impact_sounds_harder = (ba.getsound('bigImpact'),
                                     ba.getsound('bigImpact2'))
        self.single_player_death_sound = ba.getsound('playerDeath')
        self.punch_sound = ba.getsound('punch01')
        self.punch_sound_strong = (ba.getsound('punchStrong01'),
                                   ba.getsound('punchStrong02'))
        self.punch_sound_stronger = ba.getsound('superPunch')
        self.swish_sound = ba.getsound('punchSwish')
        self.block_sound = ba.getsound('block')
        self.shatter_sound = ba.getsound('shatter')
        self.splatter_sound = ba.getsound('splatter')
        self.spaz_material = ba.Material()
        self.roller_material = ba.Material()
        self.punch_material = ba.Material()
        self.pickup_material = ba.Material()
        self.curse_material = ba.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 = (ba.getsound('footImpact01'),
                                   ba.getsound('footImpact02'),
                                   ba.getsound('footImpact03'))

        self.foot_skid_sound = ba.getsound('skid01')
        self.foot_roll_sound = ba.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 = ba.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 = ba.getsound('shieldUp')
        self.shield_down_sound = ba.getsound('shieldDown')
        self.shield_hit_sound = ba.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 = _ba.get_account_misc_read_val('rsdr', 10.0)
        self.punch_cooldown = _ba.get_account_misc_read_val('rpc', 400)
        self.punch_cooldown_gloves = (_ba.get_account_misc_read_val(
            'rpcg', 300))
        self.punch_power_scale = _ba.get_account_misc_read_val('rpp', 1.2)
        self.punch_power_scale_gloves = (_ba.get_account_misc_read_val(
            'rppg', 1.4))
        self.max_shield_spillover_damage = (_ba.get_account_misc_read_val(
            'rsms', 500))

    def get_style(self, character: str) -> str:
        """Return the named style for this character.

        (this influences subtle aspects of their appearance, etc)
        """
        return ba.app.spaz_appearances[character].style

    def get_media(self, character: str) -> dict[str, Any]:
        """Return the set of media used by this variant of spaz."""
        char = ba.app.spaz_appearances[character]
        if character not in self.spaz_media:
            media = self.spaz_media[character] = {
                'jump_sounds': [ba.getsound(s) for s in char.jump_sounds],
                'attack_sounds': [ba.getsound(s) for s in char.attack_sounds],
                'impact_sounds': [ba.getsound(s) for s in char.impact_sounds],
                'death_sounds': [ba.getsound(s) for s in char.death_sounds],
                'pickup_sounds': [ba.getsound(s) for s in char.pickup_sounds],
                'fall_sounds': [ba.getsound(s) for s in char.fall_sounds],
                'color_texture': ba.gettexture(char.color_texture),
                'color_mask_texture': ba.gettexture(char.color_mask_texture),
                'head_model': ba.getmodel(char.head_model),
                'torso_model': ba.getmodel(char.torso_model),
                'pelvis_model': ba.getmodel(char.pelvis_model),
                'upper_arm_model': ba.getmodel(char.upper_arm_model),
                'forearm_model': ba.getmodel(char.forearm_model),
                'hand_model': ba.getmodel(char.hand_model),
                'upper_leg_model': ba.getmodel(char.upper_leg_model),
                'lower_leg_model': ba.getmodel(char.lower_leg_model),
                'toes_model': ba.getmodel(char.toes_model)
            }
        else:
            media = self.spaz_media[character]
        return media

    @classmethod
    def get(cls) -> SpazFactory:
        """Return the shared ba.SpazFactory, creating it if necessary."""
        # pylint: disable=cyclic-import
        activity = ba.getactivity()
        factory = activity.customdata.get(cls._STORENAME)
        if factory is None:
            factory = activity.customdata[cls._STORENAME] = SpazFactory()
        assert isinstance(factory, SpazFactory)
        return factory
Esempio n. 4
0
class PowerupBoxFactory:
    """A collection of media and other resources used by ba.Powerups.

    Category: **Gameplay Classes**

    A single instance of this is shared between all powerups
    and can be retrieved via ba.Powerup.get_factory().
    """

    model: ba.Model
    """The ba.Model of the powerup box."""

    model_simple: ba.Model
    """A simpler ba.Model of the powerup box, for use in shadows, etc."""

    tex_bomb: ba.Texture
    """Triple-bomb powerup ba.Texture."""

    tex_punch: ba.Texture
    """Punch powerup ba.Texture."""

    tex_ice_bombs: ba.Texture
    """Ice bomb powerup ba.Texture."""

    tex_sticky_bombs: ba.Texture
    """Sticky bomb powerup ba.Texture."""

    tex_shield: ba.Texture
    """Shield powerup ba.Texture."""

    tex_impact_bombs: ba.Texture
    """Impact-bomb powerup ba.Texture."""

    tex_health: ba.Texture
    """Health powerup ba.Texture."""

    tex_land_mines: ba.Texture
    """Land-mine powerup ba.Texture."""

    tex_curse: ba.Texture
    """Curse powerup ba.Texture."""

    health_powerup_sound: ba.Sound
    """ba.Sound played when a health powerup is accepted."""

    powerup_sound: ba.Sound
    """ba.Sound played when a powerup is accepted."""

    powerdown_sound: ba.Sound
    """ba.Sound that can be used when powerups wear off."""

    powerup_material: ba.Material
    """ba.Material applied to powerup boxes."""

    powerup_accept_material: ba.Material
    """Powerups will send a ba.PowerupMessage to anything they touch
       that has this ba.Material applied."""

    _STORENAME = ba.storagename()

    def __init__(self) -> None:
        """Instantiate a PowerupBoxFactory.

        You shouldn't need to do this; call Powerup.get_factory()
        to get a shared instance.
        """
        from ba.internal import get_default_powerup_distribution
        shared = SharedObjects.get()
        self._lastpoweruptype: Optional[str] = None
        self.model = ba.getmodel('powerup')
        self.model_simple = ba.getmodel('powerupSimple')
        self.tex_bomb = ba.gettexture('powerupBomb')
        self.tex_punch = ba.gettexture('powerupPunch')
        self.tex_ice_bombs = ba.gettexture('powerupIceBombs')
        self.tex_sticky_bombs = ba.gettexture('powerupStickyBombs')
        self.tex_shield = ba.gettexture('powerupShield')
        self.tex_impact_bombs = ba.gettexture('powerupImpactBombs')
        self.tex_health = ba.gettexture('powerupHealth')
        self.tex_land_mines = ba.gettexture('powerupLandMines')
        self.tex_curse = ba.gettexture('powerupCurse')
        self.health_powerup_sound = ba.getsound('healthPowerup')
        self.powerup_sound = ba.getsound('powerup01')
        self.powerdown_sound = ba.getsound('powerdown01')
        self.drop_sound = ba.getsound('boxDrop')

        # Material for powerups.
        self.powerup_material = ba.Material()

        # Material for anyone wanting to accept powerups.
        self.powerup_accept_material = ba.Material()

        # Pass a powerup-touched message to applicable stuff.
        self.powerup_material.add_actions(
            conditions=('they_have_material', self.powerup_accept_material),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('message', 'our_node', 'at_connect', _TouchedMessage()),
            ))

        # We don't wanna be picked up.
        self.powerup_material.add_actions(
            conditions=('they_have_material', shared.pickup_material),
            actions=('modify_part_collision', 'collide', False),
        )

        self.powerup_material.add_actions(
            conditions=('they_have_material', shared.footing_material),
            actions=('impact_sound', self.drop_sound, 0.5, 0.1),
        )

        self._powerupdist: list[str] = []
        for powerup, freq in get_default_powerup_distribution():
            for _i in range(int(freq)):
                self._powerupdist.append(powerup)

    def get_random_powerup_type(self,
                                forcetype: str = None,
                                excludetypes: list[str] = None) -> str:
        """Returns a random powerup type (string).

        See ba.Powerup.poweruptype for available type values.

        There are certain non-random aspects to this; a 'curse' powerup,
        for instance, is always followed by a 'health' powerup (to keep things
        interesting). Passing 'forcetype' forces a given returned type while
        still properly interacting with the non-random aspects of the system
        (ie: forcing a 'curse' powerup will result
        in the next powerup being health).
        """
        if excludetypes is None:
            excludetypes = []
        if forcetype:
            ptype = forcetype
        else:
            # If the last one was a curse, make this one a health to
            # provide some hope.
            if self._lastpoweruptype == 'curse':
                ptype = 'health'
            else:
                while True:
                    ptype = self._powerupdist[random.randint(
                        0,
                        len(self._powerupdist) - 1)]
                    if ptype not in excludetypes:
                        break
        self._lastpoweruptype = ptype
        return ptype

    @classmethod
    def get(cls) -> PowerupBoxFactory:
        """Return a shared ba.PowerupBoxFactory object, creating if needed."""
        activity = ba.getactivity()
        if activity is None:
            raise ba.ContextError('No current activity.')
        factory = activity.customdata.get(cls._STORENAME)
        if factory is None:
            factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
        assert isinstance(factory, PowerupBoxFactory)
        return factory
class RespawnIcon:
    """An icon with a countdown that appears alongside the screen.

    category: Gameplay Classes

    This is used to indicate that a ba.Player is waiting to respawn.
    """

    _MASKTEXSTORENAME = ba.storagename('masktex')
    _ICONSSTORENAME = ba.storagename('icons')

    def __init__(self, player: ba.Player, respawn_time: float):
        """Instantiate with a ba.Player and respawn_time (in seconds)."""
        self._visible = True

        on_right, offs_extra, respawn_icons = self._get_context(player)

        # Cache our mask tex on the team for easy access.
        mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
        if mask_tex is None:
            mask_tex = ba.gettexture('characterIconMask')
            player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
        assert isinstance(mask_tex, ba.Texture)

        # Now find the first unused slot and use that.
        index = 0
        while (index in respawn_icons and respawn_icons[index]() is not None
               and respawn_icons[index]().visible):
            index += 1
        respawn_icons[index] = weakref.ref(self)

        offs = offs_extra + index * -53
        icon = player.get_icon()
        texture = icon['texture']
        h_offs = -10
        ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
        self._image: Optional[ba.NodeActor] = ba.NodeActor(
            ba.newnode('image',
                       attrs={
                           'texture': texture,
                           'tint_texture': icon['tint_texture'],
                           'tint_color': icon['tint_color'],
                           'tint2_color': icon['tint2_color'],
                           'mask_texture': mask_tex,
                           'position': ipos,
                           'scale': (32, 32),
                           'opacity': 1.0,
                           'absolute_scale': True,
                           'attach': 'topRight' if on_right else 'topLeft'
                       }))

        assert self._image.node
        ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})

        npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
        self._name: Optional[ba.NodeActor] = ba.NodeActor(
            ba.newnode('text',
                       attrs={
                           'v_attach': 'top',
                           'h_attach': 'right' if on_right else 'left',
                           'text': ba.Lstr(value=player.getname()),
                           'maxwidth': 100,
                           'h_align': 'center',
                           'v_align': 'center',
                           'shadow': 1.0,
                           'flatness': 1.0,
                           'color': ba.safecolor(icon['tint_color']),
                           'scale': 0.5,
                           'position': npos
                       }))

        assert self._name.node
        ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})

        tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
        self._text: Optional[ba.NodeActor] = ba.NodeActor(
            ba.newnode('text',
                       attrs={
                           'position': tpos,
                           'h_attach': 'right' if on_right else 'left',
                           'h_align': 'right' if on_right else 'left',
                           'scale': 0.9,
                           'shadow': 0.5,
                           'flatness': 0.5,
                           'v_attach': 'top',
                           'color': ba.safecolor(icon['tint_color']),
                           'text': ''
                       }))

        assert self._text.node
        ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})

        self._respawn_time = ba.time() + respawn_time
        self._update()
        self._timer: Optional[ba.Timer] = ba.Timer(1.0,
                                                   ba.WeakCall(self._update),
                                                   repeat=True)

    @property
    def visible(self) -> bool:
        """Is this icon still visible?"""
        return self._visible

    def _get_context(self, player: ba.Player) -> Tuple[bool, float, Dict]:
        """Return info on where we should be shown and stored."""
        activity = ba.getactivity()

        if isinstance(ba.getsession(), ba.DualTeamSession):
            on_right = player.team.id % 2 == 1

            # Store a list of icons in the team.
            icons = player.team.customdata.get(self._ICONSSTORENAME)
            if icons is None:
                player.team.customdata[self._ICONSSTORENAME] = icons = {}
            assert isinstance(icons, dict)

            offs_extra = -20
        else:
            on_right = False

            # Store a list of icons in the activity.
            icons = activity.customdata.get(self._ICONSSTORENAME)
            if icons is None:
                activity.customdata[self._ICONSSTORENAME] = icons = {}
            assert isinstance(icons, dict)

            if isinstance(activity.session, ba.FreeForAllSession):
                offs_extra = -150
            else:
                offs_extra = -20
        return on_right, offs_extra, icons

    def _update(self) -> None:
        remaining = int(round(self._respawn_time - ba.time()))
        if remaining > 0:
            assert self._text is not None
            if self._text.node:
                self._text.node.text = str(remaining)
        else:
            self._visible = False
            self._image = self._text = self._timer = self._name = None
Esempio n. 6
0
class SharedObjects:
    """Various common components for use in games.

    Category: Gameplay Classes

    Objects contained here are created on-demand as accessed and shared
    by everything in the current activity. This includes things such as
    standard materials.
    """

    _STORENAME = ba.storagename()

    def __init__(self) -> None:
        activity = ba.getactivity()
        if self._STORENAME in activity.customdata:
            raise RuntimeError('Use SharedObjects.get() to fetch the'
                               ' shared instance for this activity.')
        self._object_material: Optional[ba.Material] = None
        self._player_material: Optional[ba.Material] = None
        self._pickup_material: Optional[ba.Material] = None
        self._footing_material: Optional[ba.Material] = None
        self._attack_material: Optional[ba.Material] = None
        self._death_material: Optional[ba.Material] = None
        self._region_material: Optional[ba.Material] = None
        self._railing_material: Optional[ba.Material] = None

    @classmethod
    def get(cls) -> SharedObjects:
        """Fetch/create the instance of this class for the current activity."""
        activity = ba.getactivity()
        shobs = activity.customdata.get(cls._STORENAME)
        if shobs is None:
            shobs = SharedObjects()
            activity.customdata[cls._STORENAME] = shobs
        assert isinstance(shobs, SharedObjects)
        return shobs

    @property
    def player_material(self) -> ba.Material:
        """a ba.Material to be applied to player parts. Generally,
        materials related to the process of scoring when reaching a goal, etc
        will look for the presence of this material on things that hit them.
        """
        if self._player_material is None:
            self._player_material = ba.Material()
        return self._player_material

    @property
    def object_material(self) -> ba.Material:
        """A ba.Material that should be applied to any small,
        normal, physical objects such as bombs, boxes, players, etc. Other
        materials often check for the  presence of this material as a
        prerequisite for performing certain actions (such as disabling
        collisions between initially-overlapping objects)
        """
        if self._object_material is None:
            self._object_material = ba.Material()
        return self._object_material

    @property
    def pickup_material(self) -> ba.Material:
        """A ba.Material; collision shapes used for picking things
        up will have this material applied. To prevent an object from being
        picked up, you can add a material that disables collisions against
        things containing this material.
        """
        if self._pickup_material is None:
            self._pickup_material = ba.Material()
        return self._pickup_material

    @property
    def footing_material(self) -> ba.Material:
        """Anything that can be 'walked on' should have this
        ba.Material applied; generally just terrain and whatnot. A character
        will snap upright whenever touching something with this material so it
        should not be applied to props, etc.
        """
        if self._footing_material is None:
            self._footing_material = ba.Material()
        return self._footing_material

    @property
    def attack_material(self) -> ba.Material:
        """A ba.Material applied to explosion shapes, punch
        shapes, etc.  An object not wanting to receive impulse/etc messages can
        disable collisions against this material.
        """
        if self._attack_material is None:
            self._attack_material = ba.Material()
        return self._attack_material

    @property
    def death_material(self) -> ba.Material:
        """A ba.Material that sends a ba.DieMessage() to anything
        that touches it; handy for terrain below a cliff, etc.
        """
        if self._death_material is None:
            mat = self._death_material = ba.Material()
            mat.add_actions(
                ('message', 'their_node', 'at_connect', ba.DieMessage()))
        return self._death_material

    @property
    def region_material(self) -> ba.Material:
        """A ba.Material used for non-physical collision shapes
        (regions); collisions can generally be allowed with this material even
        when initially overlapping since it is not physical.
        """
        if self._region_material is None:
            self._region_material = ba.Material()
        return self._region_material

    @property
    def railing_material(self) -> ba.Material:
        """A ba.Material with a very low friction/stiffness/etc
        that can be applied to invisible 'railings' useful for gently keeping
        characters from falling off of cliffs.
        """
        if self._railing_material is None:
            mat = self._railing_material = ba.Material()
            mat.add_actions(('modify_part_collision', 'collide', False))
            mat.add_actions(('modify_part_collision', 'stiffness', 0.003))
            mat.add_actions(('modify_part_collision', 'damping', 0.00001))
            mat.add_actions(
                conditions=('they_have_material', self.player_material),
                actions=(
                    ('modify_part_collision', 'collide', True),
                    ('modify_part_collision', 'friction', 0.0),
                ),
            )
        return self._railing_material
Esempio n. 7
0
class FlagFactory:
    """Wraps up media and other resources used by ba.Flags.

    category: Gameplay Classes

    A single instance of this is shared between all flags
    and can be retrieved via bastd.actor.flag.get_factory().

    Attributes:

       flagmaterial
          The ba.Material applied to all ba.Flags.

       impact_sound
          The ba.Sound used when a ba.Flag hits the ground.

       skid_sound
          The ba.Sound used when a ba.Flag skids along the ground.

       no_hit_material
          A ba.Material that prevents contact with most objects;
          applied to 'non-touchable' flags.

       flag_texture
          The ba.Texture for flags.
    """

    _STORENAME = ba.storagename()

    def __init__(self) -> None:
        """Instantiate a FlagFactory.

        You shouldn't need to do this; call bastd.actor.flag.get_factory() to
        get a shared instance.
        """
        shared = SharedObjects.get()
        self.flagmaterial = ba.Material()
        self.flagmaterial.add_actions(
            conditions=(
                ('we_are_younger_than', 100),
                'and',
                ('they_have_material', shared.object_material),
            ),
            actions=('modify_node_collision', 'collide', False),
        )

        self.flagmaterial.add_actions(
            conditions=(
                'they_have_material',
                shared.footing_material,
            ),
            actions=(
                ('message', 'our_node', 'at_connect', 'footing', 1),
                ('message', 'our_node', 'at_disconnect', 'footing', -1),
            ),
        )

        self.impact_sound = ba.getsound('metalHit')
        self.skid_sound = ba.getsound('metalSkid')
        self.flagmaterial.add_actions(
            conditions=(
                'they_have_material',
                shared.footing_material,
            ),
            actions=(
                ('impact_sound', self.impact_sound, 2, 5),
                ('skid_sound', self.skid_sound, 2, 5),
            ),
        )

        self.no_hit_material = ba.Material()
        self.no_hit_material.add_actions(
            conditions=(
                ('they_have_material', shared.pickup_material),
                'or',
                ('they_have_material', shared.attack_material),
            ),
            actions=('modify_part_collision', 'collide', False),
        )

        # We also don't want anything moving it.
        self.no_hit_material.add_actions(
            conditions=(
                ('they_have_material', shared.object_material),
                'or',
                ('they_dont_have_material', shared.footing_material),
            ),
            actions=(('modify_part_collision', 'collide', False),
                     ('modify_part_collision', 'physical', False)),
        )

        self.flag_texture = ba.gettexture('flagColor')

    @classmethod
    def get(cls) -> FlagFactory:
        """Get/create a shared FlagFactory instance."""
        activity = ba.getactivity()
        factory = activity.customdata.get(cls._STORENAME)
        if factory is None:
            factory = FlagFactory()
            activity.customdata[cls._STORENAME] = factory
        assert isinstance(factory, FlagFactory)
        return factory