• 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.
  • Reminder: AI-generated content is not allowed on the forums per the Rules and Regulations. Please contact us if you have any questions!
(SPINNERS) The Ultimate Trainer Event Plugin

v21.1 (SPINNERS) The Ultimate Trainer Event Plugin 1.0

This resource pertains to version 21.1 of Pokémon Essentials.
Pokémon Essentials Version
v21.1 ✅
The Ultimate Trainer Event Plugin (works in v21.1)

Pokemon Trainers in essentials can get funky. We want it to be “smooth” in all scenarios. I’ve created some edits to base essentials that makes trainer events work PERFECTLY!



Features:
  1. Spinner Trainers that can detect if the player is running. You can also customize the directions the spinner is allowed to spin in case you only want them spinning in 2 or 3 directions ignoring a different one. The Spinners are always idle spinning randomly, but IF the player is in close proximity, AND running, they will look toward the player, or in the direction that is nearest to the player that you allow them to turn to. (simple guide below)
  2. New trainer events that restore BGM properly even when a double battle is triggered through another trainer event.
  3. Event pages that stop a trainer’s custom move route after the battle, but continues normally when the map is reloaded or game is reset. (trainer still is considered defeated and won’t be rebattlable)
  4. Option for trainer rebattles made simple. Simply reload the map to trigger a rebattle.
  5. Customize your backdrop and base no matter what map or tile you’re standing on (if you want)
  6. Proper trainer intro BGM, proper BGM fadeout, and better timing overall on everything.

Step 1: Find in your script section (search with ctrl+shift+f)
Ruby:
Expand Collapse Copy
if $game_temp.waiting_trainer

Step 2: Replace that whole function with this:
Ruby:
Expand Collapse Copy
    if $game_temp.waiting_trainer
      new_args = args + [$game_temp.waiting_trainer[0]]
      outcome = TrainerBattle.start_core(*new_args)
      if outcome == 1
        event_id = $game_temp.waiting_trainer[1]
        pbMapInterpreter.pbSetSelfSwitch(event_id, "A", true)
        pbMapInterpreter.setTempSwitchOn("A", event_id)
      end
      $game_temp.waiting_trainer = nil
    else
      outcome = TrainerBattle.start_core(*args)
    end

Step 3: Find in your script section (search with ctrl+shift+f) Make sure it's the one in Interpreter, not Game_Event
Ruby:
Expand Collapse Copy
def settempswitchon(c)

Step 4: Replace that whole definition with this:
Ruby:
Expand Collapse Copy
  def setTempSwitchOn(c, event_id = nil)
    event = event_id ? $game_map.events[event_id] : get_self
    return if !event
    event.setTempSwitchOn(c)
  end

NOTE:
This allows you to call settempswitch on any event in the map by specifying the event ID
(ex. Script: setTempSwitchOn(c, 5) will set the temp switch on for event 5. Use this wherever if you want. You can still use the original setTempSwitchOn(c) without the event ID as needed.

Step 5: Insert this script above ‘main’ in your script section. Main is at the very bottom.
Ruby:
Expand Collapse Copy
if defined?(PluginManager) && !PluginManager.installed?("Ultimate Trainer Events by TastyKenter")
  PluginManager.register({                                                 
    :name    => "Ultimate Trainer Events",                                       
    :version => "1.0",                                                     
    :link    => "https://www.youtube.com/@TastyKenter",
    :credits => "TastyKenter"
  })
end

class SpinnerBehavior
  attr_reader :event

  def initialize(event, allowed_dirs = [2,4,6,8], radius = 4)
    @event = event
    @allowed_dirs = allowed_dirs
    @radius = radius
    @next_spin_time = 0
  end

  def update
    return if !@event || (@event.respond_to?(:erased?) && @event.erased?) || !$game_map

    if player_in_range? && player_running?
      face_player_if_possible
    else
      random_spin
    end
  end

  #===============================================================================
  # Player detection
  #===============================================================================
  def player_in_range?
    px = $game_player.x
    py = $game_player.y
    ex = @event.x
    ey = @event.y

    dx = (px - ex).abs
    dy = (py - ey).abs

    return dx <= @radius && dy <= @radius
  end

  #===============================================================================
  # Turn toward player
  #===============================================================================
  def face_player_if_possible
    dir = direction_to_player

    if @allowed_dirs.include?(dir)
      apply_direction(dir)
    else
      apply_direction(nearest_allowed_direction(dir))
    end
  end

  def direction_to_player
    px = $game_player.x
    py = $game_player.y
    ex = @event.x
    ey = @event.y

    dx = px - ex
    dy = py - ey

    if dx.abs > dy.abs
      return dx > 0 ? 6 : 4
    else
      return dy > 0 ? 2 : 8
    end
  end

  #===============================================================================
  # Time-based idle spinning
  #===============================================================================
  def random_spin
    now = $stats.play_time
    return if now < @next_spin_time

    # Spin happens every 0.5 to 2.3 seconds
    @next_spin_time = now + rand(0.5..2.3)

    apply_direction(@allowed_dirs.sample)
  end

  #===============================================================================
  # change dir
  #===============================================================================
  def apply_direction(dir)
    return if !@event

    case dir
    when 2 then @event.turn_down
    when 4 then @event.turn_left
    when 6 then @event.turn_right
    when 8 then @event.turn_up
    end
  end

  #===============================================================================
  # Nearest allowed direction
  #===============================================================================
  def nearest_allowed_direction(target_dir)
    return target_dir if @allowed_dirs.include?(target_dir)

    case target_dir
    when 2, 8
      return (@allowed_dirs & [4,6]).sample || @allowed_dirs.sample
    when 4, 6
      return (@allowed_dirs & [2,8]).sample || @allowed_dirs.sample
    end

    return @allowed_dirs.sample
  end
 
  def player_running?
    return false if !$game_player
    return $game_player.move_speed >= 4
  end
 
end

Step 6: Time to make the event! The best part about these events is that they are 100% copy-and-pastable no matter the type of trainer you’re trying to make (ex. Spinner, static, potential double, move-route) Make sure you copy these and only change the stuff you need to change for your project.

BASIC FIXED TRAINER EVENT PAGE 1


  1. SET THIS VARIABLE^^^, I FORGOT IT IN THE SCREENSHOTS BELOW
  2. pbGlobalLock will freeze all other events unless a double battle is occurring
  3. Show the Exclaim animation + SFX
  4. Fade out Map BGM, wait 10, play YOUR encounter theme, wait 25
  5. Use “pbMoveTowardPlayer($game_map.events[@event_id])”
  6. “Your dialogue”
  7. OPTIONAL set the background and base of the battle
  8. Fade out BGM again so the encounter music wont continue after battle ends
  9. Trigger Battle, outcome is saved in variable 1.
  10. 1 = win, so if variable 1=1, then we can turn these switches on
  11. “pbBGMPlay($game_map.bgm)” and “setTempSwitchOn("A")”
  12. Don’t forget self switch A, and “pbTrainerEnd”


BASIC FIXED TRAINER EVENT PAGE 2
  1. Check self switch a for this page, it just displays after win dialogue. Nothing else needs to be changed.


BASIC FIXED TRAINER EVENT PAGE 3
  1. Check Switch 21 “s:tsOn?("A")” instead of self switch. Also just displays the same dialogue from Page 2. You can change this if you want, as THIS will be the dialogue directly after winning, and Page 2 will be the dialogue after reloading the map.


HOW TO DO MOVEMENT/SPINNERS AND REBATTLES

Rebattles are easy. You alter page 2 of your event to look something like this. The rebattle will trigger if you've defeaten the trainer once, and you reload the map. After winning the rematch, you will have to reload the map again to trigger the next rematch. You can use variables from here to change the trainer for every time you battle them using conditional branches.



Movement is also easy.
On page 1, and 2 of your event, you will put identical autonomous movement commands. Make sure frequency is ALWAYS set to 6.



Spinners!
This script will be your best friend for ALL spinners in your game. You will paste this DIRECTLY into your autonomous movement commands list when using a custom move route. DO THIS ON PAGE 1 AND 2, but ALWAYS LEAVE PAGE 3 FIXED.

Put:
Ruby:
Expand Collapse Copy
@spinner ||= SpinnerBehavior.new(self, [2,4,6,8], 4); @spinner.update
in your move command list. It's important that frequency is always set to 6 because @spinner.update needs to run as much as possible, or your spinners are going to spin slow, and miss the player running.

Directions allowed for spinners to spin

2 then @event.turn_down
4 then @event.turn_left
6 then @event.turn_right
8 then @event.turn_up

In this example, the spinner will spin randomly in direction 6, and 2. It will never spin in direction 4 or 8, and if the player runs near the event, they will look toward the player, OR in the nearest direction that the player is that THEY ARE ALLOWED to turn towards. For example, if the player runs above this event, the event WILL not turn up, only right or down depending on distance.


Static Animation: If you want the trainer to have an idle animation (whether spinning or not) Just make sure "Stop Animation" is checked on page 1 and 2.

In this example, setTempSwitchOn activates successfully for both trainers even though there's only one event acting. That's why after the battle, both events stop their move animation. But when you re-enter the map, their move animation will play again. You can put these double battles anywhere with the bases we created above.


Combine them all!
You now have trainers that feel smooth, work as intended, no background music bugs, you can make them spinners, have idle animation, make any of them double battles, their move routes STOP after battle, but return when you reload the map via saving/exiting, or re-entering the area.



For understanding:
1. The randomized time can be edited in def random_spin. Right now, it's set at 0.5-2.3 secs
2. The radius of player detection can be increased by changing the number 4 in your call to anything you'd like.
3. We use pbBGMPlay($game_map.bgm) instead of memorizing and restoring background music because when doing double battles, you'll end up memorizing the 2nd spotters encounter music and restoring that post battle.
4. We use pbMoveTowardPlayer($game_map.events[@event_id]) in case you're using Voltseon's Multiplayer Solution like I am. It'll work with or without Voltseon's Multiplayer Solution.
5. pbGlobalLock is a method that basically freezes all events on map until pbTrainerEnd hits, where it will call pbGlobalUnlock. This is why it's important to not forget pbTrainerEnd, since we're basically rewriting pbTrainerIntro.
6. Why not just edit pbTrainerIntro? Well, depending on your circumstances, you might want to change the settings, wait frames, and add in whatever else. More customizable I guess.
Credits
TastyKenter for scripts
Author
Tastykenter
Views
80
First release
Last update

Ratings

0.00 star(s) 0 ratings
Back
Top