• Do not use Discord to host any images you post, these links expire quickly! You can learn how to add images to your posts here.
Custom Abilities with no coding knowledge | Tutorial

v19 Custom Abilities with no coding knowledge | Tutorial 19.1

This resource pertains to version 19 of Pokémon Essentials.
Creating new abilities may be confusing for some people since we have to go to the game codes to set them up.
Don't be afraid of codes! I don't know how to code and I managed to create new abilities!

Here we are going to learn step by step how to set up a new ability. To do this without knowing much about coding, we are going to use some of the existing codes as a reference.

As an example, I'm going to create 2 custom abilities. Hopefully, with these 2 you'll get used to how to make new ones on your own.

The first one will be "Icy Look". It will work just like Intimidate, but instead of lowing Attack, it will low Speed. I'll also add that it increases the power of Ice attacks.

Then I'll create "Fire Start". This ability will burn the target if the user attacks on its first turn.

Step 0: Create a backup for your game. It is not mandatory, but it is always recommended to have a backup in case something goes really wrong, especially if you don't know exactly what you are doing. Just copy and paste your entire game.

Step 1: Create the new ability in the PBS folder. Very easy!

  • Go to your game folder --> PBS --> open abilities.txt (I recommend using Notepad++ to edit PBS files).
  • Scroll down to the last ability. I'm using the Gen 8 Project for v19.1 (which I recommend you a lot), so the current last ability I have here is the number 267 (As One - Ghost).
Now just add your new ability below this one and save. This means your new ability number should be 268. The format should be:
268,ABILITYNAME,AbilityName,"Description."
note: The ability name in capitals is the Internal name.

Screenshot_1.png

note: Keep descriptions short, even if you leave some information out. There is not much space.
With this done, your ability now just exists, this means you can give it to a Pokémon, but, for now, it does nothing.

Step 2: Go into your game's scripts, and search for an ability that works in a similar way.

- Open your game project and go to "Scripts"
Screenshot_2.png

- Press "Ctrl + Shift + F" and search for the existing ability that is similar to yours. In this case, I'm going to find Intimidate.

These are my results:
Screenshot_3.png

note: If you searched for another ability, you may have more or fewer results, don't worry about that.

Most abilities should be coded in the BattleHandlers_Abilities section. So there is where you should go. I'll jump into BattleHandlers_Abilities line 2538.

So here is where we are. I'm going to quickly explain a little about this section.
Screenshot_4.png

If you scroll up or down, you'll see there are multiple abilities coded here. Also, this section is divided into multiple ability handlers. Intimidate is in the "AbilityOnSwitchIn" part along with every ability that does something when switching in the Pokémon. For example, below Intimidate, there is Misty Surge, which also works when switching in. If you keep going down, you'll eventually find the next handler which is "AbilityOnSwitchOut", where the abilities that work when you return your Pokémon into its Pokéball are, like Natural Cure. Like these, there are many ability handlers, for abilities that multiply damage, trapping abilities, etc.

Step 3: Copying and editing codes

- Copy the Intimidate code and paste it at the bottom of the AbilityOnSwitchIn handler.
It should look like this: Just above the AbilityOnSwitchOut handler section.
Screenshot_5.png

Now I'm going to use this code as a reference, and edit it.

  • I'm going to replace INTIMIDATE with ICYLOOK (remember to use the internal name).
  • Now I'll change the stat ATTACK for SPEED
It should look like this:
Expand Collapse Copy
BattleHandlers::AbilityOnSwitchIn.add(:ICYLOOK,
  proc { |ability,battler,battle|
    battle.pbShowAbilitySplash(battler)
    battle.eachOtherSideBattler(battler.index) do |b|
      next if !b.near?(battler)
      b.pbLowerAttackStatStageIntimidate(battler)
      check_item = true
      if b.hasActiveAbility?(:CONTRARY)
        check_item = false if b.statStageAtMax?(:SPEED)
      else
        check_item = false if b.statStageAtMin?(:SPEED)
      end
      b.pbItemOnIntimidatedCheck if check_item
    end
    battle.pbHideAbilitySplash(battler)
  }
)

But we still have some Intimidate stuff in there, like pbLowerAttackStatStageIntimidate or pbItemOnIntimidateCheck. This last one was coded for Adrenaline Orb, which has a mechanic with Intimidate, so we can delete that whole line.
pbLowerAttackStatStageIntimidate means our ability is still lowering the target's attack.
What we have to do is to find (Ctrl + Shift + F) where is pbLowerAttackStatStageIntimidate defined. So we search for the result that has "def pbLowerAttackStatStageIntimidate".

We'll find that Intimidate's code is quite long due to having lots of interactions with other abilities like Contrary or moves like Substitute. Copy the code which is from lines 266 to 320 and paste it below. Keep one line blank between codes, so it's easier to see them:
Screenshot_7.png

I also added a Comment so it's easier to locate my ability code. You can do this by using "#", so the game doesn't consider that line as code.
  • Now we have to define our code, so let's replace pbLowerAttackStatStageIntimidate with something like pbLowerSpeedIcyLook.
  • Now replace the checks for ATTACK stat with SPEED.
  • I'll also remove the lines where it interacts with Rattled.
  • You can change "Intimidate" for "Icy Look" in the notes in green if you want, but it's not going to change anything really.
It should look like this:
Expand Collapse Copy
  def pbLowerSpeedIcyLook(user)
    return false if fainted?
    # NOTE: Substitute intentially blocks Icy Look even if self has Contrary.
    if @effects[PBEffects::Substitute]>0
      if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
        @battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis))
      else
        @battle.pbDisplay(_INTL("{1}'s substitute protected it from {2}'s {3}!",
           pbThis,user.pbThis(true),user.abilityName))
      end
      return false
    end
    # NOTE: These checks exist to ensure appropriate messages are shown if
    #       Icy Look is blocked somehow (i.e. the messages should mention the
    #       Icy Look ability by name).
    if !hasActiveAbility?(:CONTRARY)
      if pbOwnSide.effects[PBEffects::Mist]>0
        @battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by Mist!",
           pbThis,user.pbThis(true),user.abilityName))
        return false
      end
      if abilityActive?
        if BattleHandlers.triggerStatLossImmunityAbility(self.ability,self,:SPEED,@battle,false) ||
           BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable(self.ability,self,:SPEED,@battle,false) ||
           hasActiveAbility?(:INNERFOCUS) || hasActiveAbility?(:OWNTEMPO) ||
           hasActiveAbility?(:OBLIVIOUS) || hasActiveAbility?(:SCRAPPY)
          @battle.pbShowAbilitySplash(self) if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
          @battle.pbDisplay(_INTL("{1}'s {2} prevented {3}'s {4} from working!",
             pbThis,abilityName,user.pbThis(true),user.abilityName))
          @battle.pbHideAbilitySplash(self) if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
          return false
        end
      end
      eachAlly do |b|
        next if !b.abilityActive?
        if BattleHandlers.triggerStatLossImmunityAllyAbility(b.ability,b,self,:SPEED,@battle,false)
          @battle.pbShowAbilitySplash(b) if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
          @battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by {4}'s {5}!",
             pbThis,user.pbThis(true),user.abilityName,b.pbThis(true),b.abilityName))
          @battle.pbHideAbilitySplash(b) if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
          return false
        end
      end
    end
    return false if !pbCanLowerStatStage?(:SPEED,user)
    ret = false
    if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
      ret = pbLowerStatStageByAbility(:SPEED,1,user,false)
    else
      ret = pbLowerStatStageByCause(:SPEED,1,user,user.abilityName)
    end
    return ret
  end

- Now that we defined what pbLowerSpeedIcyLook does, we can go back to the battle handler.
You can now use Ctrl + Shift + F and find ICYLOOK or whatever your ability is called.

- Replace pbLowerAttackStatStageIntimidate with pbLowerSpeedIcyLook

Press Apply and Ok, and we are good.

Step 4: Setting up the damage increase in Ice attacks.

This part is much easier. You just have to find (Ctrl + Shift + F) an ability that somehow increases damage, like Transistor, which is Regieleki's ability.

Copy Transistor's code and paste it below. Then replace the internal name with your ability's and the move's type you want to increase power.

Here is how it should look like:
Expand Collapse Copy
BattleHandlers::DamageCalcUserAbility.add(:ICYLOOK,
  proc { |ability,user,target,move,mults,baseDmg,type|
    mults[:attack_multiplier] *= 1.5 if type == :ICE
  }
)
note: In this code, Ice attacks power are being multiplied by 1.5, you can change this number to whatever you want, for example, if you want to double the damage, just change it to 2.

Now Icy Looks is completely working!


Remember to push Ctrl to do a full compile when doing a Playtest.

Step 0:
Step 0 and 1 are the same as with Icy Look, so I'm going to skip those.

Step 2:
The way we are going to do it is similar to Icy Look, but we have to find an ability that inflicts a status problem when it hits. A Pokémon with Poison Touch has a 30% chance of poisoning the target when using a contact move. We can work with that base.
So, let's find POISONTOUCH (Ctrl + Shift + F):
Screenshot_8.png

Copy the code and paste it below.

- Replace POISONTOUCH with the internal name of your ability, in this case, it's FIRESTART. Now our ability does something already, it does the same as Poison Touch, but we want it to burn with its attacks only on the first turn.

- On line 1816, where it says "target.pbCanPoison?" it's checking if target can get poisoned. There are codes like this defined for every status, which means that "pbBurn" and "pbCanBurn?" exists (and the same with Sleep, Paralyze, Freeze, etc).
So, we are going to replace pbCanPoison? with pbCanBurn? on line 1816, and on line 1821, replace pbPoison with pbBurn.

For now, the code looks like this:
Expand Collapse Copy
BattleHandlers::UserAbilityOnHit.add(:FIRESTART,
  proc { |ability,user,target,move,battle|
    next if !move.contactMove?
    next if battle.pbRandom(100)>=30
    battle.pbShowAbilitySplash(user)
    if target.hasActiveAbility?(:SHIELDDUST) && !battle.moldBreaker
      battle.pbShowAbilitySplash(target)
      if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
        battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis))
      end
      battle.pbHideAbilitySplash(target)
    elsif target.pbCanBurn?(user,PokeBattle_SceneConstants::USE_ABILITY_SPLASH)
      msg = nil
      if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
        msg = _INTL("{1}'s {2} poisoned {3}!",user.pbThis,user.abilityName,target.pbThis(true))
      end
      target.pbBurn(user,msg)
    end
    battle.pbHideAbilitySplash(user)
  }
)

Where it says "next if !move.ContactMove?" the game is checking if the move that the user is using makes contact or not. We don't care if it makes contact, but we want the game to check if it's the first turn or not.

There is code that checks the battle turns: "turnCount" It is used for abilities like Speed Boost or the Timer Ball.

Replace "!move.ContactMove?" with "user.turnCount > 1".
note: "user" means it's counting the user's turn, if you instead type "target.turnCount", it will count the target's turn.

If you want you can keep the Contact Move check if it makes sense in your ability, then just write "next if user.turnCount > 1" on the next line.

The line with "battle.pbRandom(100)>=30" makes it so it has a 30% chance to burn, but I want it to be 100%, so I'll just remove that whole line.

Now my code looks like this:
Expand Collapse Copy
BattleHandlers::UserAbilityOnHit.add(:FIRESTART,
  proc { |ability,user,target,move,battle|
    next if user.turnCount > 1
    battle.pbShowAbilitySplash(user)
    if target.hasActiveAbility?(:SHIELDDUST) && !battle.moldBreaker
      battle.pbShowAbilitySplash(target)
      if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
        battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis))
      end
      battle.pbHideAbilitySplash(target)
    elsif target.pbCanBurn?(user,PokeBattle_SceneConstants::USE_ABILITY_SPLASH)
      msg = nil
      if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
        msg = _INTL("{1}'s {2} poisoned {3}!",user.pbThis,user.abilityName,target.pbThis(true))
      end
      target.pbBurn(user,msg)
    end
    battle.pbHideAbilitySplash(user)
  }
)

There is only one more thing to change, and that is the in-game text. On line 1834, replace "poisoned" with "burned". It doesn't change the effect of the ability, it just changes the text that shows up when the enemy burns due to Fire Start.

Now Fire Start works completely!

You can create tons of more custom abilities like these by using this copy, paste & edit method. You just need the creativity and find if there is a code that does something similar to what you want to do.
Credits
You don't have to give credits to anyone.
Author
Pillowbro
Views
2,687
First release
Last update

Ratings

0.00 star(s) 0 ratings
Back
Top