• 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.
Fluid Badges

v20.1 Fluid Badges 2022-12-07

This resource pertains to version 20.1 of Pokémon Essentials.
Pokémon Essentials Version
v20.1 ➖
1670412675731.png

Script to let players earn badges in any order they like. Badges will be displayed in the order received.

This is currently only written for default Essentials UI. It's totally possible to adapt it for another layout, you'll just need to work to integrate it into the other script.​

Code​

If you prefer plug-and-play scripts, you can paste this in a new script section above Main.
Ruby:
Expand Collapse Copy
class GameStats
  alias oldinitialize initialize
 
  def initialize
    oldinitialize
    @times_to_get_badges           = {}   # Set with set_time_to_badge(number) in Gym Leader events
  end
end

class Player < Trainer
  alias oldinitialize initialize
 
  def initialize(name, trainer_type)
    oldinitialize(name, trainer_type)
    @badges                = []
  end
 
  # @return [Integer] the number of Gym Badges owned by the player
  def badge_count
    return @badges.length
  end
 
end


def pbGiveBadge(badge)
  raise "#{badge.to_s.capitalize} Badge has no graphic" if !pbResolveBitmap("Graphics/Pictures/Trainer Card/#{badge.to_s}")
  $player.badges.push(badge)
end

def pbHasBadge?(badge)
  return $player.badges.include?(badge)
end

def pbCheckHiddenMoveBadge(badge = -1, showmsg = true)
  if (badge.is_a?(Integer))
    return true if badge < 0   # No badge requirement
  end
  return true if $DEBUG
  if (badge.is_a?(Symbol)) ? $player.badges.include?(badge) : $player.badge_count >= badge
    return true
  end
  msg = (badge.is_a?(Symbol)) ? _INTL("Sorry, a new Badge is required.") : _INTL("Sorry, more Badges are required.")
  pbMessage(msg) if showmsg
  return false
end

class PokemonTrainerCard_Scene
  def pbDrawTrainerCardFront
    overlay = @sprites["overlay"].bitmap
    overlay.clear
    baseColor   = Color.new(72, 72, 72)
    shadowColor = Color.new(160, 160, 160)
    totalsec = $stats.play_time.to_i
    hour = totalsec / 60 / 60
    min = totalsec / 60 % 60
    time = (hour > 0) ? _INTL("{1}h {2}m", hour, min) : _INTL("{1}m", min)
    $PokemonGlobal.startTime = pbGetTimeNow if !$PokemonGlobal.startTime
    starttime = _INTL("{1} {2}, {3}",
                      pbGetAbbrevMonthName($PokemonGlobal.startTime.mon),
                      $PokemonGlobal.startTime.day,
                      $PokemonGlobal.startTime.year)
    textPositions = [
      [_INTL("Name"), 34, 70, 0, baseColor, shadowColor],
      [$player.name, 302, 70, 1, baseColor, shadowColor],
      [_INTL("ID No."), 332, 70, 0, baseColor, shadowColor],
      [sprintf("%05d", $player.public_ID), 468, 70, 1, baseColor, shadowColor],
      [_INTL("Money"), 34, 118, 0, baseColor, shadowColor],
      [_INTL("${1}", $player.money.to_s_formatted), 302, 118, 1, baseColor, shadowColor],
      [_INTL("Pokédex"), 34, 166, 0, baseColor, shadowColor],
      [sprintf("%d/%d", $player.pokedex.owned_count, $player.pokedex.seen_count), 302, 166, 1, baseColor, shadowColor],
      [_INTL("Time"), 34, 214, 0, baseColor, shadowColor],
      [time, 302, 214, 1, baseColor, shadowColor],
      [_INTL("Started"), 34, 262, 0, baseColor, shadowColor],
      [starttime, 302, 262, 1, baseColor, shadowColor]
    ]
    pbDrawTextPositions(overlay, textPositions)
    x = 72
    for i in 0...$player.badges.length
      badge = $player.badges[i].to_s
      @sprites["badge#{i}"] = IconSprite.new(x, 310, @viewport)
      @sprites["badge#{i}"].setBitmap("Graphics/Pictures/Trainer Card/#{badge}")
      x += 48
    end
  end

end


MenuHandlers.add(:debug_menu, :set_badges, {
  "name"        => _INTL("Set Badges"),
  "parent"      => :player_menu,
  "description" => _INTL("Toggle possession of Gym Badges."),
  "effect"      => proc {
    badgecmd = 0
    loop do
      badgecmds = []
      badgecmds.push(_INTL("Give individual Badge"))
      badgecmds.push(_INTL("Remove all"))
      badgecmd = pbShowCommands(nil, badgecmds, -1, badgecmd)
      break if badgecmd < 0
      case badgecmd
      when 0   # Give specific badge
        badge = pbMessageFreeText("Give which Badge?",_INTL(""),false,20)
        badge = badge.upcase
        if pbResolveBitmap("Graphics/Pictures/Trainer Card/#{badge}")
          pbGiveBadge(badge.to_sym)
          pbMessage(_INTL("Gave the {1} Badge.", badge.capitalize))
        else
          pbMessage(_INTL("{1} Badge does not exist.", badge.capitalize))
        end
      when 1   # Remove all
        $player.badges = []
          pbMessage(_INTL("Cleared all Badges."))
      end
    end
  }
})
The one thing this doesn't include is correcting the stat-boosting badge mechanic, because aliasing that was more hassle than it was worth. (especially seeing as it's never used) If you want to include that, see the Stat Boosts (optional) section in Instructions.

Instructions​

If you'd prefer to add this by editing the scripts yourself. (Useful if you're worried about conflicting plugins/external scripts)

These instructions will be broken into four parts -
In Game_Stats, change line 134 -
Ruby:
Expand Collapse Copy
@times_to_get_badges           = []
to
Ruby:
Expand Collapse Copy
@times_to_get_badges           = {}
In Player, change lines 84-86
Ruby:
Expand Collapse Copy
  def badge_count
    return @badges.count { |badge| badge == true }
  end
to
Ruby:
Expand Collapse Copy
  def badge_count
    return @badges.length
  end
And change line 108 -
Ruby:
Expand Collapse Copy
@badges                = [false] * 8
to
Ruby:
Expand Collapse Copy
@badges                = []

In Overworld_FieldMoves, find lines 55-63.
Ruby:
Expand Collapse Copy
def pbCheckHiddenMoveBadge(badge = -1, showmsg = true)
  return true if badge < 0   # No badge requirement
  return true if $DEBUG
  if (Settings::FIELD_MOVES_COUNT_BADGES) ? $player.badge_count >= badge : $player.badges[badge]
    return true
  end
  pbMessage(_INTL("Sorry, a new Badge is required.")) if showmsg
  return false
end
Change them to
Ruby:
Expand Collapse Copy
def pbCheckHiddenMoveBadge(badge = -1, showmsg = true)
  if (badge.is_a?(Integer))
    return true if badge < 0   # No badge requirement
  end
  return true if $DEBUG
  if (badge.is_a?(Symbol)) ? $player.badges.include?(badge) : $player.badge_count >= badge
    return true
  end
  msg = (badge.is_a?(Symbol)) ? _INTL("Sorry, a new Badge is required.") : _INTL("Sorry, more Badges are required.")
  pbMessage(msg) if showmsg
  return false
end

Finally, paste these utilities anywhere. (Simplest thing would be in their own script section)
Ruby:
Expand Collapse Copy
def pbGiveBadge(badge)
  raise "#{badge.to_s.capitalize} Badge has no graphic" if !pbResolveBitmap("Graphics/Pictures/Trainer Card/#{badge.to_s}")
  $player.badges.push(badge)
end

def pbHasBadge?(badge)
  return $player.badges.include?(badge)
end
In UI_TrainerCard, find this section -
Ruby:
Expand Collapse Copy
    x = 72
    region = pbGetCurrentRegion(0) # Get the current region
    imagePositions = []
    8.times do |i|
      if $player.badges[i + (region * 8)]
        imagePositions.push(["Graphics/Pictures/Trainer Card/icon_badges", x, 310, i * 32, region * 32, 32, 32])
      end
      x += 48
    end
    pbDrawImagePositions(overlay, imagePositions)
Replace it with this -
Ruby:
Expand Collapse Copy
    x = 72
    for i in 0...$player.badges.length
      badge = $player.badges[i].to_s
      @sprites["badge#{i}"] = IconSprite.new(x, 310, @viewport)
      @sprites["badge#{i}"].setBitmap("Graphics/Pictures/Trainer Card/#{badge}")
      x += 48
    end
In Debug_MenuCommands, find this section -
Ruby:
Expand Collapse Copy
MenuHandlers.add(:debug_menu, :set_badges, {
  "name"        => _INTL("Set Badges"),
  "parent"      => :player_menu,
  "description" => _INTL("Toggle possession of each Gym Badge."),
  "effect"      => proc {
    badgecmd = 0
    loop do
      badgecmds = []
      badgecmds.push(_INTL("Give all"))
      badgecmds.push(_INTL("Remove all"))
      24.times do |i|
        badgecmds.push(_INTL("{1} Badge {2}", $player.badges[i] ? "[Y]" : "[  ]", i + 1))
      end
      badgecmd = pbShowCommands(nil, badgecmds, -1, badgecmd)
      break if badgecmd < 0
      case badgecmd
      when 0   # Give all
        24.times { |i| $player.badges[i] = true }
      when 1   # Remove all
        24.times { |i| $player.badges[i] = false }
      else
        $player.badges[badgecmd - 2] = !$player.badges[badgecmd - 2]
      end
    end
  }
})
Replace it with -
Ruby:
Expand Collapse Copy
MenuHandlers.add(:debug_menu, :set_badges, {
  "name"        => _INTL("Set Badges"),
  "parent"      => :player_menu,
  "description" => _INTL("Toggle possession of Gym Badges."),
  "effect"      => proc {
    badgecmd = 0
    loop do
      badgecmds = []
      badgecmds.push(_INTL("Give individual Badge"))
      badgecmds.push(_INTL("Remove all"))
      badgecmd = pbShowCommands(nil, badgecmds, -1, badgecmd)
      break if badgecmd < 0
      case badgecmd
      when 0   # Give specific badge
        badge = pbMessageFreeText("Give which Badge?",_INTL(""),false,20)
        badge = badge.upcase
        if pbResolveBitmap("Graphics/Pictures/Trainer Card/#{badge}")
          pbGiveBadge(badge.to_sym)
          pbMessage(_INTL("Gave the {1} Badge.", badge.capitalize))
        else
          pbMessage(_INTL("{1} Badge does not exist.", badge.capitalize))
        end
      when 1   # Remove all
        $player.badges = []
          pbMessage(_INTL("Cleared all Badges."))
      end
    end
  }
})
In Move_UsageCalculations, find this section -
Ruby:
Expand Collapse Copy
    if @battle.internalBattle
      if user.pbOwnedByPlayer?
        if physicalMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_ATTACK
          multipliers[:attack_multiplier] *= 1.1
        elsif specialMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPATK
          multipliers[:attack_multiplier] *= 1.1
        end
      end
      if target.pbOwnedByPlayer?
        if physicalMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE
          multipliers[:defense_multiplier] *= 1.1
        elsif specialMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF
          multipliers[:defense_multiplier] *= 1.1
        end
      end
    end
At each point where it checks @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_(STAT), you're going to change it so that it instead checks @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_(STAT)), like so -
Ruby:
Expand Collapse Copy
    if @battle.internalBattle
      if user.pbOwnedByPlayer?
        if physicalMove? && @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_ATTACK)
          multipliers[:attack_multiplier] *= 1.1
        elsif specialMove? && @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_SPATK)
          multipliers[:attack_multiplier] *= 1.1
        end
      end
      if target.pbOwnedByPlayer?
        if physicalMove? && @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_DEFENSE)
          multipliers[:defense_multiplier] *= 1.1
        elsif specialMove? && @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_SPDEF)
          multipliers[:defense_multiplier] *= 1.1
        end
      end
    end
In the script section AI_Move_Utilities, you'll do the same thing with this section here:
Ruby:
Expand Collapse Copy
    # Badge multipliers
    if skill >= PBTrainerAI.highSkill && @battle.internalBattle && target.pbOwnedByPlayer?
      if move.physicalMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE
        multipliers[:defense_multiplier] *= 1.1
      elsif move.specialMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF
        multipliers[:defense_multiplier] *= 1.1
      end
    end
It should look like this:
Ruby:
Expand Collapse Copy
    # Badge multipliers
    if skill >= PBTrainerAI.highSkill && @battle.internalBattle && target.pbOwnedByPlayer?
      if move.physicalMove?(type) && @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_DEFENSE)
        multipliers[:defense_multiplier] *= 1.1
      elsif move.specialMove?(type) && @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_SPDEF)
        multipliers[:defense_multiplier] *= 1.1
      end
    end
And in the script section Battle_Battler, you'll do the same with this line -
Ruby:
Expand Collapse Copy
    # Badge multiplier
    if @battle.internalBattle && pbOwnedByPlayer? &&
       @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPEED
      speedMult *= 1.1
    end
It should now look like this -
Ruby:
Expand Collapse Copy
    # Badge multiplier
    if @battle.internalBattle && pbOwnedByPlayer? &&
       @battle.pbPlayer.badges.include?(Settings::NUM_BADGES_BOOST_SPEED)
      speedMult *= 1.1
    end



Using this Script​

1670411838450.png
There's a fair number of changes here!

Badges are no longer on one spritesheet all together. Instead, they're going to be their own individual sprites, with their name in all caps, just like how items and Pokémon are named now. (Just the badge name, not the word "Badge") For example, the Boulder Badge would be a file named "BOULDER.png". They'll still be placed in Graphics/Pictures/Trainer Card.

The script checks to see that the graphic exists when giving the player a badge - if it doesn't, it'll give an error saying "(Name) Badge has no graphic".

Instead of $player.badges[0] = true, you'll do pbGiveBadge(:BADGE). Wherever you refer to a badge, you'll do so with a symbol (all caps, : in front) rather than a number.

For example, in the default maps' Brock event, instead of doing this -
Ruby:
Expand Collapse Copy
$stats.set_time_to_badge(0)
$player.badges[0] = true
You'd do
Ruby:
Expand Collapse Copy
$stats.set_time_to_badge(:BOULDER)
pbGiveBadge(:BOULDER)

FIELD_MOVES_COUNT_BADGES is no longer used. Your settings for HM use/stat boost can depend on the player's total number of badges or whether they have a specific badge - a number requires a total of badges, a symbol requires a specific badge.

For example, this is how the settings are in default Essentials -
Ruby:
Expand Collapse Copy
  BADGE_FOR_CUT       = 1
  BADGE_FOR_FLASH     = 2
  BADGE_FOR_ROCKSMASH = 3
  BADGE_FOR_SURF      = 4
  BADGE_FOR_FLY       = 5
  BADGE_FOR_STRENGTH  = 6
  BADGE_FOR_DIVE      = 7
  BADGE_FOR_WATERFALL = 8
The player needs 1 Badge to use Cut, 2 to use Flash, 3 to use Rock Smash, and so on. This will still be the case when using this script.

Ruby:
Expand Collapse Copy
  BADGE_FOR_CUT       = 1
  BADGE_FOR_FLASH     = :THUNDER
  BADGE_FOR_ROCKSMASH = :BOULDER
  BADGE_FOR_SURF      = :CASCADE
  BADGE_FOR_FLY       = 2
  BADGE_FOR_STRENGTH  = :EARTH
  BADGE_FOR_DIVE      = 5
  BADGE_FOR_WATERFALL = :VOLCANO
With this setup, the player would need 1 badge to use Cut, 2 to use Fly, and 5 to use Dive. But they would need to have the Cascade Badge specifically to use Surf, the Thunder Badge specifically to use Flash, and so on. When the player tries to use an HM without meeting the requirements, the game will say specifically "more Badges are required" if it's a matter of number, and "a new Badge is required" if it's a specific badge.

You can check $player.badge_count >= badge to see if the player has a total number of badges, and you can check pbHasBadge?(:badge) to see if the player has a specific badge.

Badge mechanics for obedience and losing money have not changed, they're still just the total number of badges.

You can actually earn duplicate badges with this script, since they're no longer true/false.

You can have more than 8 badges, but you'll need to tweak the trainer card display to get them to appear properly.

Graphics​

Since badges aren't all on a spritesheet together, you don't need to worry about having a specific badge size anymore! The game will just display the image for the badge at the given coordinates. Currently, that's at (x,310), where x is 72+(48*(Badge number - 1)). Don't let that formula scare you - all that means is that the first badge is at (72,310), the second is at (120,310), the third at (168,310), and so on.

If you want to adjust this, you're going to be looking at this section of code:
Ruby:
Expand Collapse Copy
    x = 72
    for i in 0...$player.badges.length
      badge = $player.badges[i].to_s
      @sprites["badge#{i}"] = IconSprite.new(x, 310, @viewport)
      @sprites["badge#{i}"].setBitmap("Graphics/Pictures/Trainer Card/#{badge}")
      x += 48
    end
(This is in UI_TrainerCard if you added it directly)

I tried to break the code up here a bit to make it easier to adjust values - x = 72 is where the first badge is displayed, and x +=48 is the space between badges. If you'd like to have multiple rows of badges, you'll just have to add something similar for the Y value - but you'll need to do a bit more math to set the coordinates right.

For example, say I want to just add an extra row of badges 48 pixels below the first one. I'm going to start by making Y a value set before the badges are drawn, just like X.

Ruby:
Expand Collapse Copy
    x = 72
    y = 310
    for i in 0...$player.badges.length
      badge = $player.badges[i].to_s
      @sprites["badge#{i}"] = IconSprite.new(x, y, @viewport)
      @sprites["badge#{i}"].setBitmap("Graphics/Pictures/Trainer Card/#{badge}")
      x += 48
    end
Now I'll increase Y by 48 as well - but unlike X, which should increase for each badge, I only want Y to increase when it's time to go to the next row. That means I'll want to do it on the 9th badge, when i is 8.
Ruby:
Expand Collapse Copy
    x = 72
    y = 310
    for i in 0...$player.badges.length
      badge = $player.badges[i].to_s
      @sprites["badge#{i}"] = IconSprite.new(x, y, @viewport)
      @sprites["badge#{i}"].setBitmap("Graphics/Pictures/Trainer Card/#{badge}")
      x += 48
      y += 48 if i == 8
    end
(If you had even more rows, you could do i%8 == 0 - that would check if i could be divided by 8 with no remainder. )

But wait, there's one more step! X is increasing with each badge, so I need to move X back to the beginning if I'm starting a new row!
Ruby:
Expand Collapse Copy
    x = 72
    y = 310
    for i in 0...$player.badges.length
      badge = $player.badges[i].to_s
      @sprites["badge#{i}"] = IconSprite.new(x, y, @viewport)
      @sprites["badge#{i}"].setBitmap("Graphics/Pictures/Trainer Card/#{badge}")
      x += 48
      y += 48 if i == 8
      x = 72 if i == 8
    end

Debug Menu​


1670409505243.png
Badges can still be cleared as normal. If you select Give Badge, you'll be asked to enter the name of a badge. (like the filenames, don't include the word "badge" - just put in "Boulder" for the Boulder Badge.) Capitalization doesn't matter.

Right now, there's no option to give the player all badges at once. I could, hypothetically, change things up so that the badges are in a subfolder, and then the game goes through each file in the subfolder. I'm not going to bother with that now, because you can only do this in Debug, and Debug is going to override all badge requirements anyways, but if someone's interested in the idea, I might try to do it.

Adding to a Released Game​

I really recommend you do this at the start of development - adding this partway through is going to shake up a lot of gameplay and story. But if you really want to introduce it midway, you can take an extra step to let past saves remain compatible.

The way Essentials is currently set up, player badges are stored as an array of true/false Booleans. You're going to take that and create an array of symbols based on the order the badges would normally be obtained in. That sounds complicated, but all that means is that you're going to go through the current array, check if each entry is true, and add the badge to a new array if it is, like this -
Ruby:
Expand Collapse Copy
newbadges = []
newbadges.push(:BOULDER) if $player.badges[0] == true
newbadges.push(:CASCADE) if $player.badges[1] == true
and so on, until finishing it out with
Ruby:
Expand Collapse Copy
$player.badges = newbadges
If you recorded times_to_get_badges, it'll be a similar thing, you'll just be making a hash instead.
Ruby:
Expand Collapse Copy
newtimes = {}
newtimes[:BOULDER] = $stats.times_to_get_badges[0]
newtimes[:CASCADE] = $stats.times_to_get_badges[1]
and so on, and finishing it out with
Ruby:
Expand Collapse Copy
$stats.times_to_get_badges = newtimes

Related Resources​

Prodigy3332 has a pack of badges in the Gen 3 style here!
Credits
TechSkylander1518
Smithereens - Bug fixes
Author
TechSkylander1518
Views
4,157
First release
Last update

Ratings

0.00 star(s) 0 ratings

More resources from TechSkylander1518

Back
Top