Modding Guide Episode 4: Custom Powerups - efroemling/ballistica GitHub Wiki

Modding Guide Top Level

In this tutorial, we're gonna learn how to make a custom powerup that makes spaz invincible for 1/4th of the default powerup time!

Now, remember Episode 2 ? where we learned how to create a plugin for modding purposes? Well, let's throw that all out of the window because making powerups using plugins is currently a VERY difficult task, so instead, we're going to edit the game files (sorry android folks 🙁). In the future, hopefully this sort of thing will become easier with plugins.

Let's open our Python scripts folder, this should be located under ba_data/python in your BombSquad directory, this is where all the magic happens

Now we have 3 things to do:

  1. Make PowerupBox and PowerupBoxFactory aware of this new powerup type
  2. Put this new powerup in the default powerup distribution roster
  3. Make Spaz react to this new powerup

So let's begin, shall we?

Powerup Box

PowerupBox and PowerupBoxFactory are located under bastd/actor/powerupbox.py, first find this line in the PowerupBoxFactory class

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

and add this line under it

    tex_inv: ba.Texture

that is going to be our texture, now find this line in the factory's init

        self.tex_curse = ba.gettexture('powerupCurse')

add a line under it and use it to describe the texture of it using ba.gettexture, we're going to use the 'levelIcon' in this tutorial

        self.tex_inv = ba.gettexture('levelIcon')

now we tell this to the PowerupBox class, find this line in it's init

        elif poweruptype == 'curse':
            tex = factory.tex_curse

right under it, add

        elif poweruptype == 'inv':
            tex = factory.tex_inv

WARNING: Make sure the new line gets added before the else statement

        elif poweruptype == 'curse':
            tex = factory.tex_curse
        elif poweruptype == 'inv':
            tex = factory.tex_inv
        else:
            raise ValueError('invalid poweruptype: ' + str(poweruptype))

We're done with this part, now let's add this powerup box to the default powerup distribution roster.

Default Powerup Roster

Head on to ba/_powerup.py, this file contains some message classes regarding powerup functionality, and the default powerup distribution rosters which is exactly what we need; find this line

def get_default_powerup_distribution() -> Sequence[tuple[str, int]]:
    """Standard set of powerups."""
    return (
        ('triple_bombs', 3),
        ('ice_bombs', 3),
        ('punch', 3),
        ('impact_bombs', 3),
        ('land_mines', 2),
        ('sticky_bombs', 3),
        ('shield', 2),
        ('health', 1),
        ('curse', 1),
    )

add ('inv', 1), under the curse, it should now look like this now

def get_default_powerup_distribution() -> Sequence[tuple[str, int]]:
    """Standard set of powerups."""
    return (
        ('triple_bombs', 3),
        ('ice_bombs', 3),
        ('punch', 3),
        ('impact_bombs', 3),
        ('land_mines', 2),
        ('sticky_bombs', 3),
        ('shield', 2),
        ('health', 1),
        ('curse', 1),
        ('inv', 1),
    )

this adds the "inv" powerup in the default powerup roster, "1" here is the probability of the powerup dropping. The greater the number is, the higher the chance of the powerup dropping is; try different numbers to see how the gameplay is affected.

Tip: Always set a high probability number for your custom powerup or remove all other powerups from the roster when testing your powerup to reduce the time spent testing

That's done now, so let's head over to bastd/actor/spaz.py and add some actual function to our powerup.

Spaz Functionality

First let's add a timer variable in Spaz's init, find

        self._curse_timer: ba.Timer | None = None

now add this under it

        self._inv_wear_off_timer: ba.Timer | None = None
        self._inv_wear_off_flash_timer: ba.Timer | None = None

now let's handle "wearing off" the powerup

    def _inv_wear_off_flash(self) -> None:
        if self.node:
            self.node.billboard_texture = PowerupBoxFactory.get().tex_inv
            self.node.billboard_opacity = 1.0
            self.node.billboard_cross_out = True

    def _inv_wear_off(self) -> None:
        if self.node:
            self.node.invincible = False
            ba.playsound(PowerupBoxFactory.get().powerdown_sound,
                         position=self.node.position)
            self.node.billboard_opacity = 0.0

_inv_wear_off_flash "flashes" the powerup texture before it runs out, and _inv_wear_off handles the actual functionality, now let's actually give the powerup to our poor spaz, inside Spaz's handlemessage, under

        elif isinstance(msg, ba.PowerupMessage):

find

            elif msg.poweruptype == 'health':
                if self._cursed and not self._mcursed:
                    self._cursed = False

                    # Remove cursed material.
                    factory = SpazFactory.get()
                    for attr in ['materials', 'roller_materials']:
                        materials = getattr(self.node, attr)
                        if factory.curse_material in materials:
                            setattr(
                                self.node,
                                attr,
                                tuple(
                                    m
                                    for m in materials
                                    if m != factory.curse_material
                                ),
                            )
                    self.node.curse_death_time = 0
                self.hitpoints = self.hitpoints_max
                self._flash_billboard(PowerupBoxFactory.get().tex_health)
                self.node.hurt = 0
                self._last_hit_time = None
                self._num_times_hit = 0

right under that, write this

            elif msg.poweruptype == 'inv':
                tex = PowerupBoxFactory.get().tex_inv
                self._flash_billboard(tex)
                self.node.invincible = True
                self._inv_wear_off_flash_timer = (ba.Timer(
                    ((POWERUP_WEAR_OFF_TIME/4) - 2000),
                    ba.WeakCall(self._inv_wear_off_flash),
                    timeformat=ba.TimeFormat.MILLISECONDS))
                self._inv_wear_off_timer = (ba.Timer(
                    POWERUP_WEAR_OFF_TIME/4,
                    ba.WeakCall(self._inv_wear_off),
                    timeformat=ba.TimeFormat.MILLISECONDS))

in this function, we flash the powerup texture, then we make spaz invincible, and after that, we set a ba.Timer to reverse the actions of the powerup after 1/4th of the default powerup time, and we call the flash timer to flash the powerup running out texture 2 seconds beforehand.

Well, that should do it (hopefully), I hope you learned something new, also, if you end up making something cool with this and you'd like to share it with everyone, you can head over to The Official BombSquad/Ballistica Discord Server and share it with all of us! Happy modding!