Aller au contenu principal
Voir

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. Le prepend fonctionne 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 EXPAfterCatchingPokemon est déclaré à l'intérieur de Battle::Logic. C'est une convention PSDK : le module de patch vit dans la classe qu'il modifie.
  • battle_phase_end_caught est 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.
  • super appelle l'implémentation originale de PSDK. Sans ce super, le comportement original serait entièrement supprimé.
  • prepend EXPAfterCatchingPokemon insère le module avant Battle::Logic dans la chaîne d'héritage. Tout appel à battle_phase_end_caught passe 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 chen et no_effect sont des gardes supplémentaires : on affiche un message et on retourne false pour bloquer l'utilisation.
  • util_attack_item_on_use_sequence est 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 condition est 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::PlayerChoiceAbstraction est 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_DATAS est un hash existant dans PSDK. On ne le recrée pas, on y ajoute des entrées via ||=.
  • module_function rend define_on_attack_item_use appelable comme PFM::ItemDescriptor.define_on_attack_item_use(...).
  • La classe Wrapper est 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_DATAS restent 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 < GenericBase crée une sous-classe qui n'affecte que la scène Mystery Gift.
  • Si on avait utilisé prepend sur GenericBase, 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. Oublier super casse silencieusement la chaîne.
  • Jamais de .freeze sur les constantes : .freeze empêche l'extension des structures via prepend ou 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 juste Patch ou Fix.
  • 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.
  • prepend est le mécanisme standard pour modifier une méthode PSDK existante. Il préserve la chaîne d'appel via super et 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 prepend quand 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.