Le monkey-patch : principe et application
PSDK est un moteur qui se met à jour régulièrement. Si on modifie directement un fichier du moteur pour changer un comportement, la prochaine mise à jour écrasera les modifications. Le monkey-patching permet de modifier le comportement de PSDK depuis ses propres scripts, sans toucher au code source du moteur. Ce guide couvre les différentes techniques de monkey-patching utilisées dans PSDK : prepend pour modifier globalement une méthode, l'héritage pour un changement local à une seule scène, et la réouverture de module pour les constantes et fonctions d'enregistrement.
Qu'est-ce que le monkey-patching ?
Le monkey-patching consiste à modifier, à l'exécution, le comportement d'un code existant sans toucher à son fichier source. En Ruby, c'est possible parce que les classes et les modules sont ouverts : on peut les rouvrir à tout moment pour ajouter, remplacer ou enrichir des méthodes.
Dans PSDK, le monkey-patching est indispensable. Le code du moteur n'est pas dans le projet — il est chargé en interne par PSDK. On ne peut pas (et on ne doit pas) le modifier directement. On travaille donc depuis ses propres scripts, placés dans scripts/, qui sont chargés après le moteur.
Trois techniques permettent de monkey-patcher dans PSDK :
prepend— insérer un module avant une classe ou un module dans la chaîne d'héritage, pour intercepter ou enrichir une méthode existante. C'est la technique principale.- Réouverture de module — rouvrir un module PSDK pour y ajouter des méthodes, des constantes ou des fonctions d'enregistrement, sans modifier l'existant.
- Héritage — créer une sous-classe pour un changement local à une seule scène ou un seul composant, sans affecter le reste du jeu.
prepend est privilégié par rapport à la réouverture directe de classe (redéfinir une méthode sans module) grâce à sa composabilité : si plusieurs plugins modifient la même méthode, chaque super passe le relais au suivant dans la chaîne. Redéfinir la méthode directement écraserait les patches des autres plugins.
Où placer ses monkey-patches
Tous les scripts utilisateur vont dans le dossier scripts/ à la racine du projet. C'est le seul endroit où PSDK charge du code personnalisé. La structure typique :
scripts/
my-project/
001 Patches/
000 BattleLogic.rb <- monkey-patch sur Battle::Logic
001 ItemUsage.rb <- monkey-patch sur Util::Item
002 Features/
...
- Les scripts dans
scripts/sont toujours chargés après le code interne de PSDK. Leprependfonctionne donc naturellement : la classe ciblée existe déjà au moment du chargement. - On regroupe les patches dans un sous-dossier dédié pour les retrouver facilement.
Prepend simple : intercepter et déléguer
Le cas le plus courant : on veut intercepter un appel à une méthode PSDK, vérifier une condition, et soit court-circuiter le comportement soit laisser PSDK faire son travail via super.
Exemple : bloquer l'EXP après capture selon la génération
module Battle
class Logic
# Patch to prevent EXP distribution after catching when using gen-based EXP sharing
module EXPAfterCatchingPokemon
# Function that process the battle end when Pokemon was caught
def battle_phase_end_caught
return if $game_variables[Yuki::Var::GEN_EXP_SHARE].between?(1, 5)
super
end
end
prepend EXPAfterCatchingPokemon
end
end
module Battle/class Logic: on rouvre la classe PSDK existante. On ne crée pas une nouvelle classe, on étend celle qui existe déjà dans le moteur.- Le module
EXPAfterCatchingPokemonest déclaré à l'intérieur deBattle::Logic. C'est une convention PSDK : le module de patch vit dans la classe qu'il modifie. battle_phase_end_caughtest la méthode PSDK originale qu'on intercepte. Il faut connaître son nom exact (en lisant le code PSDK ou la documentation).return if ...court-circuite la méthode : si la condition est vraie, on sort immédiatement sans distribuer d'EXP.superappelle l'implémentation originale de PSDK. Sans cesuper, le comportement original serait entièrement supprimé.prepend EXPAfterCatchingPokemoninsère le module avantBattle::Logicdans la chaîne d'héritage. Tout appel àbattle_phase_end_caughtpasse d'abord par le module.
Prepend complexe : enrichir avant de déléguer
Quand on veut gérer un nouveau cas qui n'existe pas dans PSDK, puis laisser PSDK gérer tous les autres cas normalement.
Exemple : intercepter l'utilisation d'items offensifs
module Util
module Item
# Patch to handle attack items in battle
module AttackItemPatch
# Use an item in a GamePlay::Base child class
# @param item_id [Integer] ID of the item in the database
# @return [PFM::ItemDescriptor::Wrapper, false] item descriptor wrapper if the item could be used
def util_item_useitem(item_id, &result_process)
item_wrapper = PFM::ItemDescriptor.actions(item_id)
return super unless item_wrapper.attack_item && $game_temp.in_battle
if item_wrapper.chen
display_message(parse_text(22, 43))
return false
elsif item_wrapper.no_effect
display_message(parse_text(22, 108))
return false
end
return util_attack_item_on_use_sequence(item_wrapper, result_process)
end
end
prepend AttackItemPatch
end
end
return super unless item_wrapper.attack_item && $game_temp.in_battle: si l'item n'est pas un item offensif ou qu'on n'est pas en combat, on délègue entièrement à PSDK. Le patch est alors invisible pour tous les cas existants.- Les cas
chenetno_effectsont des gardes supplémentaires : on affiche un message et on retournefalsepour bloquer l'utilisation. util_attack_item_on_use_sequenceest une nouvelle méthode définie ailleurs dans nos scripts (pas dans le module de patch). Le module de patch ne contient que l'interception.- Le pattern
return super unless conditionest idiomatique dans PSDK : on traite son nouveau cas, et tout le reste passe par le comportement original.
Prepend sur un module inclus
La même technique fonctionne sur les modules PSDK qui sont inclus dans des classes. On rouvre le module, on déclare le patch, et on prepend.
Exemple : rediriger les items offensifs dans l'UI de combat
module BattleUI
module PlayerChoiceAbstraction
# Patch to redirect attack items to the attack item choice flow
module AttackItemShortcutPatch
# Redirect attack items to the attack item choice flow
# @param item [Studio::Item] the item to use
def use_item(item)
item_wrapper = PFM::ItemDescriptor.actions(item.id)
return super unless item_wrapper.attack_item
scene.attack_item_shortcut = item_wrapper
@result = :bag
end
end
prepend AttackItemShortcutPatch
end
end
BattleUI::PlayerChoiceAbstractionest un module PSDK inclus dans plusieurs classes de scène de combat. Le prepend affecte toutes les classes qui incluent ce module.- Le pattern est identique :
return super unless condition, puis le traitement spécifique.
Extension d'une API d'enregistrement
Quand PSDK expose un hash ou une structure d'enregistrement via module_function, on peut étendre cette structure sans prepend. On rouvre le module et on ajoute de nouvelles fonctions d'enregistrement.
Exemple : ajouter une API d'enregistrement d'items offensifs
module PFM
module ItemDescriptor
module_function
# Define a usage of an attack item from the bag
# @param klass [Class<Studio::Item>, Symbol] class or db_symbol of the item
# @yieldparam item [Studio::Item] the item to use
# @yieldparam scene [GamePlay::Base]
def define_on_attack_item_use(klass, &block)
raise 'Block is mandatory' unless block_given?
EXTEND_DATAS[klass] ||= Wrapper.new
EXTEND_DATAS[klass].on_attack_item_use = block
EXTEND_DATAS[klass].attack_item = true
end
# Wrapper extension for attack items
class Wrapper
# Tell if the item is an attack item
# @return [Boolean]
attr_accessor :attack_item
# Register the on_use block
attr_writer :on_attack_item_use
# Call the on_use block
# @param scene [GamePlay::Base]
def on_attack_item_use(scene)
@on_attack_item_use&.call(@item, scene)
end
end
end
end
EXTEND_DATASest un hash existant dans PSDK. On ne le recrée pas, on y ajoute des entrées via||=.module_functionrenddefine_on_attack_item_useappelable commePFM::ItemDescriptor.define_on_attack_item_use(...).- La classe
Wrapperest rouverte pour y ajouter de nouveaux attributs. Cela fonctionne parce que les constantes PSDK ne sont pas gelées avec.freeze. - Cette approche est non-destructive : les enregistrements existants dans
EXTEND_DATASrestent intacts.
Héritage : monkey-patching local
Le prepend est global : il affecte toutes les instances de la classe dans tout le jeu. Quand on veut un changement local (une seule scène, un seul composant), l'héritage est la technique de monkey-patching à privilégier.
Exemple : personnaliser GenericBase pour une scène spécifique
module UI
# Custom base UI for the Mystery Gift scene
class MysteryGiftBase < GenericBase
private
# Return the background filename
# @return [String]
def background_filename
return 'mystery_gift/background'
end
# Disable the background scroll animation
def create_background_animation; end
end
end
MysteryGiftBase < GenericBasecrée une sous-classe qui n'affecte que la scène Mystery Gift.- Si on avait utilisé
prependsurGenericBase, toutes les scènes du jeu auraient été affectées. - Règle : si le changement est propre à une seule scène et ne doit pas affecter les autres, préférer l'héritage au
prepend.
Règles de survie
Quelques règles essentielles pour un monkey-patching propre dans PSDK :
- Toujours appeler
super: sauf si le but explicite est de bloquer le comportement original. Oubliersupercasse silencieusement la chaîne. - Jamais de
.freezesur les constantes :.freezeempêche l'extension des structures viaprependou réouverture. Il faudrait alors supprimer et reconstruire la constante au lieu de simplement ajouter. - Un module par classe patchée : si on patche trois méthodes d'une même classe, on peut les grouper dans un seul module de patch. Mais ne jamais mélanger des patches de classes différentes dans un même module.
- Nommer le module de patch explicitement : le nom doit décrire ce que fait le patch (
EXPAfterCatchingPokemon,AttackItemPatch), pas justePatchouFix. - Documenter les méthodes patchées : noter quelque part (README, commentaire en tête de fichier) quelles méthodes PSDK sont modifiées. Lors d'une mise à jour de PSDK, cela permet de vérifier rapidement si les méthodes patchées ont changé.
Conclusion
- Le monkey-patching est nécessaire dans PSDK : modifier directement le moteur serait écrasé à la prochaine mise à jour. On travaille toujours depuis ses propres scripts.
prependest le mécanisme standard pour modifier une méthode PSDK existante. Il préserve la chaîne d'appel viasuperet permet à plusieurs patches de coexister.- Le pattern idiomatique est
return super unless condition: on traite son nouveau cas, et tout le reste passe par le comportement original. - Pour étendre une API d'enregistrement, on rouvre le module et on ajoute de nouvelles fonctions sans écraser l'existant.
- L'héritage est préférable au
prependquand le changement est local à une seule scène ou composant. - Ne jamais oublier
super, ne jamais utiliser.freeze, et toujours documenter les méthodes patchées.