Create a custom evolution
Pokémon Studio's evolution editor covers the classic conditions, but a fangame regularly needs one it cannot express. Studio's Depending on a function field is the escape hatch: it calls a Ruby method you write, and because that method is arbitrary Ruby, it can express any condition at all. This guide shows how to write one, how to drive a counter-based evolution with evolve_var, and how to trigger an evolution by hand.
The problem: a condition the editor cannot express
Say your starter is meant to evolve only once it has proven itself in combat: it takes fifty hits, and then it evolves. Open the evolution editor and nothing fits. There is a minimum level, a held item, a moment of the day, a map, but nothing counts anything. The condition simply does not exist in the list.
This is the normal case rather than the exception. The built-in conditions describe the official games, and a fangame invents rules the official games never had.
PSDK's answer is not to extend the editor but to let the condition escape into Ruby. Studio keeps storing the evolution, with its target and its other conditions, and the part it cannot express becomes a method in your own scripts. The engine calls that method when it tests the evolution.
This guide assumes you already know how to fill in an evolution in the editor. If not, start with Configure a Pokémon's evolutions. The examples below live in your own scripts, never in the engine files, and the first one needs nothing more than a new method.
The func condition
When you type a method name into Studio's Depending on a function field, the engine calls that method on the Pokémon while it tests the evolution. The method takes no argument and returns true or false. Nothing more, and that is the whole mechanism.
The engine itself uses this for about twenty official special cases, which are the best possible examples because they are real. Hitmonlee compares two stats:
def elv_kicklee
atk > dfe
end
Sylveon looks through the move set:
def elv_nymphali
return @skills_set.any? { |skill| skill&.type?(data_type(:fairy).id) }
end
All of them are named elv_* by convention. Nothing enforces the prefix, but following it keeps your methods recognisable next to the engine's.
To add your own, reopen PFM::Pokemon and define the method. There is no super here because you are adding a method rather than overriding one:
module PFM
class Pokemon
# Evolve once the creature has taken 50 hits in battle
# @return [Boolean] if the condition is valid
def elv_battle_scarred
return (@evolve_var || 0) >= 50
end
end
end
Then type elv_battle_scarred in the Depending on a function field of the evolution. Studio does not check that the method exists, so a typo crashes the game with an error called NoMethodError the moment the engine tests the evolution.
Inside the method you have the whole Pokémon at hand: its level, its stats, its moves, the party, the game state. Anything you can read from Ruby, you can turn into an evolution condition. The counter this particular example reads is the one case that needs a little setup, and it is the next section.
Two engine rules are independent of the method you write. A Pokémon holding an Everstone never evolves, the engine stops before testing any condition. And when a Pokémon has several evolutions, the engine keeps the first in the list whose conditions all pass, so a broader evolution placed above yours can shadow it. Ordering is covered in Configure a Pokémon's evolutions.
The engine knows a few extra condition types the editor does not show, but Studio cannot set them and does not recognise them. Adding one by hand-editing the files in Data/Studio makes the whole project fail to open, with only a generic error pointing to the logs. There is never a need to: Depending on a function already covers anything those could do.
Counter-based evolutions with evolve_var
@evolve_var is the general-purpose counter behind every counting evolution. increase_evolve_var adds to it, reset_evolve_var clears it, and the engine already counts a handful of things into it.
One rule governs everything below:
Every place the engine increases evolve_var only runs for creatures whose evolution uses the exact func name declared in Studio: the step counter for elv_1000steps, the critical-hit counter for elv_sirfetchd, and so on. Declare one of those names and the counting is already done for you. Invent a fresh name and nothing counts it, you supply the plumbing yourself.
Reuse an engine counter on your own Pokémon (no code)
The simplest counter-based evolution writes no Ruby at all: in Studio, give your creature's evolution one of the function names the engine already counts. Its counters loop over every creature whose evolution declares that name, so yours is swept in too.
Steps are counted out of battle. step_evolution_update runs on every step and increments each party member whose evolution uses elv_1000steps:
def step_evolution_update
return unless $game_switches[Yuki::Sw::FM_Enabled]
if $game_switches[Yuki::Sw::FollowMe_LetsGoMode]
return unless $storage.lets_go_follower&.evolution_condition_function?(:elv_1000steps)
$storage.lets_go_follower.increase_evolve_var
else
return if $game_variables[Yuki::Var::FM_N_Pokem] <= 0
$actors.each_with_index do |creature, index|
break if index >= $game_variables[Yuki::Var::FM_N_Pokem]
next unless creature.evolution_condition_function?(:elv_1000steps)
creature.increase_evolve_var
end
end
end
You never write this, the engine calls it. Declaring elv_1000steps on your creature's evolution is what opts it into that loop, and the function evolves at a thousand:
def elv_1000steps
return (@evolve_var || 0) >= 1000
end
Battle events work the same way. handle_elv_sirfetchd_event runs after damage and increments the attacker when it lands a critical hit and its evolution uses elv_sirfetchd:
def handle_elv_sirfetchd_event(hp, target, launcher, skill)
return if launcher.nil?
return unless launcher.evolution_condition_function?(:elv_sirfetchd)
return unless skill&.critical_hit?
launcher.increase_evolve_var
end
The engine also carries the value back onto the real Pokémon at the end of the fight, so declaring elv_sirfetchd gives you a working "evolves after three critical hits" with no code. The names worth borrowing this way are elv_1000steps (steps), elv_sirfetchd (critical hits) and elv_basculin (recoil damage, at 294).
Change the numbers (patch the function)
To keep an engine trigger but a different threshold, patch the elv_* method. Because this replaces an existing method rather than adding a new one, it goes in a prepend module, the monkey-patching convention, never an alias. This one makes the step evolution fire at five hundred instead of a thousand:
module PFM
class Pokemon
module HalfStepEvolution
def elv_1000steps
return (@evolve_var || 0) >= 500
end
end
prepend HalfStepEvolution
end
end
The name is shared with the engine's own species, so this moves their threshold too. That is fine in a fangame that does not feature that species; if it does, or if you need to count something else entirely, give your evolution a fresh name and its own plumbing.
Count something the engine does not (your own plumbing), advanced
If one of the engine's existing triggers already matches what you need, you can stop here: this section is only for counting something the engine never tracks, hits taken for instance. It is the fifty-hits evolution from the top of the guide, and its elv_battle_scarred method is already written, so all that is left is the counting.
Out of battle there is no difficulty: call increase_evolve_var on the creature wherever your rule counts, from an event or a quest reward. In battle the engine works on a temporary PFM::PokemonBattler, a scratch copy of the creature used only during the fight, so the counting has to happen on that copy. This first patch adds up every hit taken, mirroring the engine's own handle_elv_sirfetchd_event, and only touches creatures whose evolution uses your function:
module Battle
class Logic
class DamageHandler
# Counts every hit taken by a creature whose evolution uses elv_battle_scarred
module BattleScarredCounter
def handle_post_damage_events(hp, target, launcher, skill)
target.increase_evolve_var if target.evolution_condition_function?(:elv_battle_scarred)
super
end
end
prepend BattleScarredCounter
end
end
end
That alone is enough for the evolution to happen. The engine copies your counter back onto the real Pokémon at the end of each fight and reloads it at the start of the next, so the hits add up across battles, and once fifty is reached the creature evolves at its next level-up.
To make it evolve the instant the battle with its fiftieth hit ends, rather than waiting for that level-up, add a second patch that asks the engine to check right there, exactly as the engine does for its own species. Pushing the creature into logic.evolve_request is the request:
module Battle
class Logic
class BattleEndHandler
# Asks the engine to check the evolution as soon as the battle ends
module BattleScarredPersistence
def handle_battle_end_events(players_creatures)
super
players_creatures.each do |creature|
next unless creature.evolution_condition_function?(:elv_battle_scarred)
creature.original.evolve_var = creature.evolve_var || 0
logic.evolve_request << creature
end
end
end
prepend BattleScarredPersistence
end
end
end
Trigger an evolution manually
Sometimes the evolution should happen at a moment the engine does not test, at the end of a quest, or after a dialogue. Ask evolve_check for the target, then hand it to the evolution scene:
pokemon = $actors.first
id, form = pokemon.evolve_check(:level_up)
GamePlay.make_pokemon_evolve(pokemon, id, form, true) if id
evolve_check still applies every condition, so this triggers the evolution the data describes rather than forcing an arbitrary one, and it returns false when nothing matches, hence the if id guard.
The last argument of make_pokemon_evolve is forced. Left at false, the player can cancel the evolution by pressing B, which is the level-up behaviour. Set to true, the evolution cannot be refused, which is what the engine does for stones and trades.
Conclusion
funcis the route: a true-or-false method you write onPFM::Pokemon, namedelv_*by convention, selected in Studio through Depending on a function. Being plain Ruby, it can express any condition at all.- An evolution fires only if no Everstone is held and it is the first in the list whose conditions all pass, so mind the ordering.
- Do not hand-edit the project files to add condition types the editor does not show: Studio rejects them and the project stops opening. Depending on a function already covers them.
evolve_vardrives counter-based evolutions three ways: reuse an engine name (elv_1000steps,elv_sirfetchd) for its counting with no code, patch that method to change the numbers, or write your own plumbing for a trigger the engine does not have.GamePlay.make_pokemon_evolve(pokemon, id, form, forced)triggers an evolution by hand,forceddeciding whether the player can cancel with B.