Un SDK C++ moderne, header-only et de haut niveau pour le développement de plugins et de modules pour SA-MP.
- Português: README
- Deutsch: README
- English: README
- Español: README
- Italiano: README
- Polski: README
- Русский: README
- Svenska: README
- Türkçe: README
- SA-MP SDK
- Langues
- Table des matières
- 1. Introduction et philosophie de conception
- 2. Configuration et environnement
- 3. Guide d'utilisation complet de l'API
- 3.1. Le cycle de vie du plugin
- 3.2. Exportation des Fonctions du Plugin
- 3.3.
Plugin_Public: Interception des événements Pawn - 3.4.
Plugin_Native: Création de fonctions natives en C++ - 3.5.
Plugin_Native_Hook: Interception des natives SA-MP existantes - 3.6. Macros
Pawn_*: Appel de fonctions Pawn depuis le C++ - 3.7.
Plugin_Module: Gestion des modules dynamiques - 3.8.
Plugin_Call: Appel des natives internes du plugin - 3.9. Fonctions utilitaires du SDK
- 4. Compilation et déploiement
- Licence
L'API de plugins de SA-MP est une interface de programmation en C. Bien que fonctionnelle et fondamentale, elle présente les défis inhérents à la programmation de bas niveau :
- Gestion manuelle de la mémoire : Des fonctions comme
amx_Allotetamx_Releaseexigent que le développeur alloue et désalloue explicitement la mémoire dans le tas de l'AMX. C'est une source courante de fuites de mémoire et de pannes d'exécution. - Typage faible et conversions manuelles : Les paramètres sont passés sous forme de tableau de
cells, ce qui force des conversions explicites (et souvent dangereuses) entrecell,int,float, etchar*. - Verbosité et boilerplate : Extraire plusieurs paramètres d'un tableau
cell* params, gérer les tailles de chaînes et gérer la pile de l'AMX pour les rappels C++ vers Pawn exige un code répétitif. - Fragilité de l'interface : Le manque de vérification au moment de la compilation (type safety) signifie que les erreurs de passage de paramètres ou de types peuvent passer inaperçues jusqu'à l'exécution, provoquant des plantages ou des comportements indéfinis.
Le SA-MP SDK aborde ces problèmes en fournissant une puissante couche d'abstraction en C++ :
- RAII (Resource Acquisition Is Initialization) : Gestion automatique de la mémoire dans l'AMX.
Amx_Scoped_Memorygarantit que la mémoire allouée est libérée. - Sécurité des types : Conversion automatique et sécurisée des paramètres entre C++ et Pawn. Vous traitez directement avec
int,float,std::string. - Syntaxe concise et idiomatique : Les macros et les modèles fournissent une API propre qui ressemble davantage au C++ moderne qu'à l'API C traditionnelle.
- Interception robuste : Un moteur de hooking avancé permet l'interception transparente des callbacks Pawn (
Plugin_Public), la création de nouvelles natives (Plugin_Native), et le hooking et l'enchaînement des fonctions natives SA-MP existantes (Plugin_Native_Hook). - Haute performance : Utilisation intensive du hachage au moment de la compilation (FNV1a), de la mise en cache des fonctions et des optimisations
thread_localpour minimiser la surcharge des abstractions.
Le SDK est composé exclusivement de fichiers d'en-tête (.hpp, .h).
- Avantages :
- Intégration simplifiée : Il n'y a pas de bibliothèques à construire, lier ou distribuer. Il suffit d'inclure les en-têtes.
- Optimisations du compilateur : Le compilateur a une visibilité complète du code du SDK et de votre plugin, ce qui permet un inlining agressif et des optimisations au moment de l'édition des liens, ce qui peut se traduire par des binaires plus rapides.
- Implications :
- Temps de compilation : Pour les très grands projets, la compilation peut prendre plus de temps en raison de l'inclusion répétée du code du SDK. Cela est atténué par les gardes d'inclusion et la nature "n'inclure que ce qui est nécessaire" du C++.
- Macros d'implémentation : La nécessité de la macro
SAMP_SDK_IMPLEMENTATIONest une conséquence du modèle header-only pour éviter les redéfinitions de symboles.
- Compilateur C++ : Compatible avec C++17 ou supérieur.
- GCC (version 8+)
- Clang (version 5+)
- MSVC (Visual Studio 2017+)
- Architecture : x86 (32 bits). SA-MP fonctionne exclusivement sur cette architecture. Le SDK inclut des vérifications dans
platform.hppqui généreront des erreurs de compilation si une architecture incorrecte est détectée. - Système d’exploitation : Windows ou Linux.
Pour plus de clarté et d'organisation, il est courant d'organiser le SDK dans un sous-dossier samp-sdk.
my_plugin/
├── samp-sdk/
│ ├── // Other SDK files
│ └── samp_sdk.hpp // The main header to include
│
├── src/
│ ├── main.cpp // Where SAMP_SDK_IMPLEMENTATION is defined
│ └── my_custom_logic.cpp // Optional, to organize code
│
└── CMakeLists.txt (or .vcxproj, Makefile)
Définissez toujours ces macros avant d'inclure samp_sdk.hpp.
- Objectif : Indique au compilateur que ce fichier
.cppdoit générer les implémentations des fonctions d'exportation du plugin (Supports,Load,Unload,AmxLoad,AmxUnload,ProcessTick). Le SDK gère automatiquement l'exportation de ces fonctions, éliminant le besoin de fichiers.def(sous Windows) ou de déclarations__attribute__((visibility("default")))(sous Linux) pour ces fonctions. - Impact technique : Sans cette macro, l'éditeur de liens ne trouvera pas les exportations nécessaires, et le serveur SA-MP ne pourra pas charger votre plugin.
- Règle fondamentale : Définissez cette macro dans UN SEUL fichier
.cppde tout votre projet. La définir dans plus d'un fichier provoquera une erreur de liaison de "symbole dupliqué".
// main.cpp
#define SAMP_SDK_IMPLEMENTATION // The macro makes the SDK export the necessary functions automatically.
#include "samp-sdk/samp_sdk.hpp"
// ... your plugin code ...- Objectif : Active les callbacks de cycle de vie du script Pawn (
OnAmxLoad,OnAmxUnload) et la fonctionnalité de création de nouvelles natives en C++ (Plugin_Native). - Fonctionnalités activées :
- Callbacks
OnAmxLoad(AMX* amx)etOnAmxUnload(AMX* amx). - Déclaration et enregistrement de nouvelles natives C++ utilisant
Plugin_Native. - La macro
Plugin_Callpour invoquer des natives créées avecPlugin_Nativeau sein de votre propre plugin.
- Callbacks
- Impact technique : Lorsque cette macro est définie, le SDK collecte automatiquement toutes vos
Plugin_Natives. Dans la fonctionSupports(), le drapeauSUPPORTS_AMX_NATIVESest automatiquement ajouté s'il y a desPlugin_Natives dans votre projet. - Flexibilité : Vous pouvez définir cette macro dans plusieurs fichiers
.cpp. Le système d'enregistrement statique du SDK consolidera toutes les natives de différentes unités de compilation en une seule liste globale.
// my_natives.cpp (can be a separate file from main.cpp)
#define SAMP_SDK_WANT_AMX_EVENTS // Only to enable Plugin_Native and the OnAmxLoad/OnAmxUnload callbacks
#include "samp-sdk/samp_sdk.hpp"
Plugin_Native(MyCustomNative, AMX* amx, cell* params) {
// ...
return 0;
}- Objectif : Active le callback
OnProcessTick(), qui est invoqué régulièrement par le serveur. - Impact technique : Ajoute automatiquement le drapeau
SUPPORTS_PROCESS_TICKdans la fonctionSupports().
// main.cpp
#define SAMP_SDK_IMPLEMENTATION
#define SAMP_SDK_WANT_PROCESS_TICK
#include "samp-sdk/samp_sdk.hpp"
void OnProcessTick() {
// Logic executed at each server "tick" (e.g. 20ms)
// Be careful with heavy operations here!
}Ce fichier fournit toutes les constantes et limites connues de SA-MP, telles que MAX_PLAYERS, INVALID_PLAYER_ID, PLAYER_STATE_ONFOOT, WEAPON_DEAGLE, DIALOG_STYLE_LIST, etc. Il est automatiquement inclus par samp_sdk.hpp et doit être utilisé pour garantir la compatibilité et la lisibilité du code.
Les fonctions suivantes sont les points d'entrée et de sortie de votre plugin, exportées automatiquement par le SDK.
- Description : Première fonction appelée par le serveur SA-MP après le chargement réussi de votre plugin en mémoire.
- Utilisation : Idéal pour initialiser tout système, charger des configurations, ouvrir des connexions à des bases de données ou charger des modules (
Plugin_Module). - Retour :
true: Le plugin a été initialisé avec succès et le chargement se poursuit.false: Le plugin a échoué à s'initialiser. Le chargement sera annulé et le plugin sera déchargé.
// main.cpp
bool OnLoad() {
Samp_SDK::Log("Initializing MyPlugin v1.0...");
// Example: Load a module (more details in section 3.6)
if (!Plugin_Module("my_database_module", "plugins/db_connector", "Database module loaded.")) {
Samp_SDK::Log("ERROR: Failed to load database module!");
return false; // Aborts loading of the main plugin
}
return true;
}- Description : Dernière fonction appelée par le serveur SA-MP avant de décharger votre plugin de la mémoire.
- Utilisation : Idéal pour nettoyer les ressources, fermer les connexions, sauvegarder les états et s'assurer qu'aucune ressource n'est fuite. Le SDK gère automatiquement le déchargement des modules (
Plugin_Module).
// main.cpp
void OnUnload() {
Samp_SDK::Log("MyPlugin unloaded. Releasing resources...");
// No manual action is needed for modules loaded via Plugin_Module;
// the SDK takes care of it.
}- Description : Informe le serveur SA-MP des fonctionnalités que votre plugin prend en charge et souhaite utiliser.
- Utilisation : Retournez toujours
SUPPORTS_VERSION(ouSAMP_PLUGIN_VERSION). Les flagsSUPPORTS_AMX_NATIVESetSUPPORTS_PROCESS_TICKsont automatiquement ajoutés par le SDK s'il y a desPlugin_Natives, et/ou si la macroSAMP_SDK_WANT_PROCESS_TICKest définie, respectivement. Cela simplifie la maintenance et évite les erreurs.
// main.cpp
unsigned int GetSupportFlags() {
return SUPPORTS_VERSION; // The SDK adds the necessary flags automatically.
}- Requis :
SAMP_SDK_WANT_AMX_EVENTS - Description : Appelée chaque fois qu'un nouveau script Pawn (un Gamemode ou Filterscript) est chargé et initialisé sur le serveur.
- Utilisation : Si vous avez besoin d'une logique spécifique pour chaque script AMX, ou d'initialiser des données spécifiques par script.
// main.cpp (with SAMP_SDK_WANT_AMX_EVENTS defined)
void OnAmxLoad(AMX* amx) {
// amx represents the new loaded script instance.
Samp_SDK::Log("AMX script loaded: %p", (void*)amx);
}- Requis :
SAMP_SDK_WANT_AMX_EVENTS - Description : Appelée lorsqu'un script Pawn est déchargé du serveur.
- Utilisation : Pour nettoyer les ressources spécifiques que vous avez allouées ou associées à cet
AMX*particulier.
// main.cpp (with SAMP_SDK_WANT_AMX_EVENTS defined)
void OnAmxUnload(AMX* amx) {
Samp_SDK::Log("AMX script unloaded: %p", (void*)amx);
}- Requis :
SAMP_SDK_WANT_PROCESS_TICK - Description : Appelée de manière répétée à chaque "tick" du serveur (généralement 20 fois par seconde, soit toutes les 50ms).
- Utilisation : Pour la logique de fond continue, les minuteries, les mises à jour d'état qui ne dépendent pas des événements du joueur, ou la synchronisation des données.
- Attention : Évitez les opérations bloquantes ou gourmandes en calcul ici, car elles peuvent provoquer des latences sur le serveur.
// main.cpp (with SAMP_SDK_WANT_PROCESS_TICK defined)
static int tick_count = 0;
void OnProcessTick() {
tick_count++;
if (tick_count % 200 == 0) // Every 10 seconds (20 ticks/sec * 10 sec = 200 ticks)
Samp_SDK::Log("Server active for %d seconds.", tick_count / 20);
}Pour que le serveur SA-MP puisse appeler les fonctions de votre plugin (Load, Supports, etc.), elles doivent être "exportées" du fichier DLL (Windows) ou SO (Linux). Le SDK automatise l'exportation des fonctions de cycle de vie standard, mais fournit également des outils pour que vous puissiez exporter vos propres fonctions personnalisées, si vous avez besoin d'interopérabilité avec d'autres programmes.
La méthode pour exporter des fonctions varie selon le compilateur.
Sous Windows avec MSVC, le moyen le plus simple d'exporter des fonctions personnalisées est d'utiliser la macro Export_Plugin, définie dans exports.hpp.
- Syntaxe:
Export_Plugin("Fonction", "Pile") Fonction: Le nom exact de la fonction à exporter.Pile: La quantité totale d'octets que les paramètres de la fonction occupent dans la pile. Pour la convention__stdcall(standard du SDK sous Windows), le calcul est4 * (Nombre de Paramètres).
#include "samp-sdk/exports.hpp"
// Exemple: Exportation d'une fonction personnalisée qui pourrait être appelée
// par un autre programme ou plugin qui connaît sa signature.
const char* SAMP_SDK_CALL GetPluginVersion() {
return "1.0.0";
}
Export_Plugin("GetPluginVersion", "0");Warning
Limitation de Export_Plugin
Cette macro fonctionne uniquement avec le compilateur MSVC (Visual Studio). Elle utilise une directive #pragma spécifique à Microsoft qui est ignorée par d'autres compilateurs comme GCC et Clang.
Pour GCC et Clang (sous Windows ou Linux), l'exportation est gérée par la macro SAMP_SDK_EXPORT, définie dans platform.hpp. Vous la placez simplement avant la définition de la fonction.
- Mécanisme: Sous Linux, elle ajoute
__attribute__((visibility("default"))). Sous Windows avec GCC/Clang, elle ajoute__attribute__((dllexport)). - Utilisation: Le SDK applique déjà
SAMP_SDK_EXPORTà toutes les fonctions de cycle de vie (Load,Unload, etc.), donc leur exportation est entièrement automatique pour ces compilateurs. Pour vos fonctions personnalisées, faites simplement la même chose.
// Pour GCC/Clang, la définition de la fonction avec SAMP_SDK_EXPORT est suffisante.
SAMP_SDK_EXPORT const char* SAMP_SDK_CALL GetPluginVersion() {
return "1.0.0";
}La macro Plugin_Public est le pont principal pour recevoir les rappels Pawn dans votre code C++.
Plugin_Public(NomDeLaPublic, Type1 Param1, Type2 Param2, ...)- Le nom de la fonction C++ que vous déclarez doit être le même que le callback Pawn (ex :
OnPlayerConnect). - Les types de paramètres C++ (
int,float,std::string) sont automatiquement convertis par le SDK.
// Intercepts OnPlayerText(playerid, text[])
Plugin_Public(OnPlayerText, int playerid, std::string text) {
Samp_SDK::Log("Player %d said: %s", playerid, text.c_str());
return PUBLIC_CONTINUE;
}Le SDK gère automatiquement la lecture de la cell stack de l'AMX et la conversion vers les types C++ spécifiés :
int: Converti directement decell.float: Converti decellen utilisantamx::AMX_CTOF.std::string: Le SDK lit l'adresse de la chaîne dans l'AMX, alloue unstd::stringen C++ et copie le contenu.
La valeur retournée par votre fonction Plugin_Public est cruciale et détermine le flux d'exécution du callback :
return PUBLIC_CONTINUE;(valeur1) : Indique que l'exécution du callback doit continuer. S'il y a d'autres plugins qui interceptent également ce callback, ils seront appelés (dans l'ordre inverse de chargement). Ensuite, lapublicoriginale dans le script Pawn sera invoquée.return PUBLIC_STOP;(valeur0) : Indique que l'exécution du callback doit être interrompue. Aucun autre plugin (avec une priorité inférieure) ou lapublicoriginale dans le script Pawn ne sera invoqué pour cet événement. C'est idéal pour implémenter un système qui "remplace" ou "bloque" un comportement par défaut de SA-MP.
// main.cpp
Plugin_Public(OnPlayerCommandText, int playerid, std::string cmdtext) {
if (cmdtext == "/freeze") {
Pawn_Native(TogglePlayerControllable, playerid, 0); // Freezes the player
Pawn_Native(SendClientMessage, playerid, -1, Plugin_Format("Player %d frozen.", playerid));
return PUBLIC_STOP; // Prevents the command from being processed by other scripts.
}
return PUBLIC_CONTINUE; // Allows other commands to be processed.
}Une caractéristique avancée de Plugin_Public est le support des "Ghost Callbacks". Cela signifie que vous pouvez intercepter un callback Pawn même s'il n'est pas présent dans le script .amx du gamemode ou du filterscript. Le SDK "trompe" le serveur pour qu'il appelle votre hook C++ de toute façon. C'est utile pour les callbacks internes ou pour créer de nouvelles fonctionnalités sans dépendre de la présence d'une public dans le script Pawn.
// You can define a callback that the Pawn script does not have, but your plugin will hear it.
Plugin_Public(OnMyCustomInternalEvent, int data1, float data2) {
Samp_SDK::Log("Custom internal event received: %d, %.2f", data1, data2);
return PUBLIC_CONTINUE;
}
// To "fire" this event from another point in your C++ code:
// Pawn_Public(OnMyCustomInternalEvent, 123, 45.67f);
// The call will go to your Plugin_Public above, even if there is no OnMyCustomInternalEvent in Pawn.Plugin_Native vous permet d'étendre la fonctionnalité de Pawn avec du code C++ haute performance.
Plugin_Native(NomeDaNativa, AMX* amx, cell* params)- La fonction C++ doit avoir exactement cette signature :
cell NomeDaNativa(AMX* amx, cell* params). NomeDaNativaest le nom que les scripts Pawn utiliseront.
// Pawn: native GetPlayerPingAverage(playerid);
Plugin_Native(GetPlayerPingAverage, AMX* amx, cell* params) {
// ... Implementation ...
return 0;
}Note
Vous n'avez pas besoin d'appeler amx_Register manuellement. Le SDK détecte toutes vos Plugin_Natives (dans n'importe quel fichier .cpp qui a inclus samp_sdk.hpp et défini SAMP_SDK_WANT_AMX_EVENTS) et les enregistre automatiquement dans chaque script AMX chargé (OnAmxLoad).
- Description : Aide plus simple pour extraire plusieurs paramètres séquentiellement.
- Utilisation :
Register_Parameters(variable1, variable2, ...) - Limitations : Pour les paramètres d'entrée. Ne gère pas les paramètres facultatifs ou l'accès par index.
- Types pris en charge :
int,float,std::string.
// Pawn: native SetPlayerSkinAndMoney(playerid, skinid, money);
Plugin_Native(SetPlayerSkinAndMoney, AMX* amx, cell* params) {
int playerid, skinid, money;
Register_Parameters(playerid, skinid, money); // Extracts the 3 parameters
Pawn_Native(SetPlayerSkin, playerid, skinid);
Pawn_Native(GivePlayerMoney, playerid, money);
return 1;
}- Description : Une classe wrapper qui fournit une interface orientée objet pour accéder aux paramètres d'une native. Plus puissante pour les scénarios complexes.
- Construction :
Native_Params p(amx, params);
- Description : Retourne le nombre de paramètres passés à la native.
- Utilisation : Essentiel pour gérer les paramètres facultatifs.
- Description : Extrait un paramètre d'entrée par index et le convertit au type
T. - Types pris en charge :
int,float,std::string.
// Pawn: native GetPlayerWeaponAmmo(playerid, weaponid = -1);
Plugin_Native(GetPlayerWeaponAmmo, AMX* amx, cell* params) {
Native_Params p(amx, params);
int playerid = p.Get<int>(0);
int weaponid = (p.Count() > 1) ? p.Get<int>(1) : Pawn_Native(GetPlayerWeapon, playerid);
return Pawn_Native(GetPlayerAmmo, playerid, weaponid);
}- Description : Obtient la valeur d'un paramètre de référence (pointeur Pawn) et la stocke dans
out_value. - Utilisation : Pour lire des valeurs qui ont été passées par référence depuis Pawn.
- Retour :
truesi l'adresse AMX est valide,falsesinon.
// Pawn: native CheckPlayerStats(playerid, &Float:health, &money);
Plugin_Native(CheckPlayerStats, AMX* amx, cell* params) {
Native_Params p(amx, params);
int playerid = p.Get<int>(0);
float health = 0.0f;
int money = 0;
// Gets the values from the references (Pawn passed addresses)
p.Get_REF(1, health); // Reads the value of Float:health
p.Get_REF(2, money); // Reads the value of money
Samp_SDK::Log("Player %d, Health: %.1f, Money: %d", playerid, health, money);
// Now, modify them
health = 50.0f;
money += 100;
// And writes them back to Pawn's memory
p.Set_REF(1, health);
p.Set_REF(2, money);
return 1;
}- Description : Retourne un
std::optional<T>pour lire un paramètre de référence. Plus sûr pour C++17 et supérieur.
- Description : Écrit une valeur
Tdans un paramètre de référence Pawn (l'adresse que Pawn a passée). - Utilisation : Pour modifier des valeurs qui ont été passées par référence, faisant en sorte que Pawn voit la modification.
- Retour :
truesi l'écriture a réussi,falsesinon.
- Votre fonction
Plugin_Nativedoit retourner uncell. - Pour retourner un
intoubool, utilisez un cast verscell. - Pour retourner un
float, utilisezamx::AMX_FTOC(mon_float).
// Returns a bool
Plugin_Native(IsPlayerSpawned, AMX* amx, cell* params) {
int playerid;
Register_Parameters(playerid);
return (Pawn_Native(GetPlayerState, playerid) == PLAYER_STATE_SPAWNED) ? 1 : 0;
}
// Returns a float
Plugin_Native(GetPlayerMaxHealth, AMX* amx, cell* params) {
return amx::AMX_FTOC(100.0f); // Returns 100.0f
}La macro Plugin_Native_Hook vous permet d'intercepter et de modifier le comportement de toute fonction native SA-MP existante ou d'autres plugins. C'est un mécanisme puissant pour étendre ou modifier la logique standard du serveur.
Plugin_Native_Hook(NomDeLaNative, AMX* amx, cell* params)- La fonction C++ doit avoir exactement cette signature :
cell NomDeLaNative(AMX* amx, cell* params). NomDeLaNativedoit être le nom exact de la native que vous souhaitez hooker (ex :SendClientMessage,SetPlayerPos).
// Intercepts the SendClientMessage native
Plugin_Native_Hook(SendClientMessage, AMX* amx, cell* params) {
// ...
return Call_Original_Native(SendClientMessage); // Important to call the original
}Note
Vous n'avez pas besoin d'appeler amx_Register manuellement ou de définir SAMP_SDK_WANT_AMX_EVENTS spécifiquement pour Plugin_Native_Hook. Le SDK détecte et enregistre automatiquement vos hooks. Lors de la première exécution d'un script AMX, le SDK remplace le pointeur de la native dans la table par un "trampoline" qui redirige vers votre fonction Plugin_Native_Hook. Ce processus garantit l'enchaînement sécurisé des hooks de plusieurs plugins.
Dans votre fonction Plugin_Native_Hook, vous DEVEZ utiliser la macro Call_Original_Native(NomDeLaNative) pour invoquer la fonction originale (ou le hook suivant dans la chaîne). C'est vital pour :
- Préserver la fonctionnalité : Si vous n'appelez pas l'original, la native hookée cessera simplement de fonctionner pour les autres plugins ou pour le serveur.
- Enchaînement des hooks : Permet à plusieurs plugins de hooker la même native et à tous les hooks d'être exécutés séquentiellement.
- Manipulation du retour : Vous pouvez inspecter et même modifier la valeur de retour de
Call_Original_Nativeavant de la retourner depuis votre fonction de hook.
// Example: Blocking SendClientMessage if it contains a specific word
Plugin_Native_Hook(SendClientMessage, AMX* amx, cell* params) {
Native_Params p(amx, params);
// Extracts parameters for analysis
int playerid = p.Get<int>(0);
// int color = p.Get<int>(1); // We don't need the color for this logic
std::string message = p.Get_String(2); // Gets the message string
if (message.find("BADWORD") != std::string::npos) {
Samp_SDK::Log("MESSAGE BLOCKED for playerid %d: %s", playerid, message.c_str());
return 0; // Returns 0 (false) to Pawn, indicating the message was not sent.
// And most importantly, we do NOT call Call_Original_Native, blocking the message.
}
// If the message does not contain the forbidden word, we call the original native
// and return its value, ensuring the message is sent normally
// and that other hooks (if any) are executed.
return Call_Original_Native(SendClientMessage);
}#define SAMP_SDK_IMPLEMENTATION
// SAMP_SDK_WANT_AMX_EVENTS is not strictly necessary for hooks, but it's common to have OnAmxLoad/Unload
// #define SAMP_SDK_WANT_AMX_EVENTS
#include "samp-sdk/samp_sdk.hpp"
// Hook for the CreateVehicle native
Plugin_Native_Hook(CreateVehicle, AMX* amx, cell* params) {
Native_Params p(amx, params);
// Extracts the parameters of the CreateVehicle native for inspection
int modelid = p.Get<int>(0);
float x = p.Get<float>(1);
float y = p.Get<float>(2);
float z = p.Get<float>(3);
float angle = p.Get<float>(4);
int color1 = p.Get<int>(5);
int color2 = p.Get<int>(6);
int respawn_delay = p.Get<int>(7);
bool addsiren = p.Get<bool>(8);
Samp_SDK::Log("HOOK: CreateVehicle called! Model: %d, Pos: (%.2f, %.2f, %.2f)", modelid, x, y, z);
// Example of how to *modify* an input parameter
// If the model is 400 (Landstalker), we change it to 401 (Bravura)
if (modelid == 400) {
// We directly modify the 'params' array for the original call
params[1] = static_cast<cell>(401); // The model is at position 0 in the parameters array (params[1])
Samp_SDK::Log(" -> Model 400 changed to 401 before creation.");
}
// We call the original native (or the next hook in the chain) with the potentially modified parameters
cell original_retval = Call_Original_Native(CreateVehicle);
Samp_SDK::Log("HOOK: CreateVehicle returned: %d (Vehicle ID)", (int)original_retval);
// You can modify the return value here before returning it to Pawn.
// Example: if vehicle creation failed, return a custom invalid ID.
if ((int)original_retval == INVALID_VEHICLE_ID) {
Samp_SDK::Log(" -> Vehicle creation failed in the original native.");
return -1; // Returns a different value to Pawn.
}
return original_retval; // Returns the value that the original native returned (or the modified one above).
}
unsigned int GetSupportFlags() {
return SUPPORTS_VERSION;
}
// Minimal implementations for the lifecycle
bool OnLoad() {
Samp_SDK::Log("Native Hook Example Plugin loaded!");
return true;
}
void OnUnload() {
Samp_SDK::Log("Native Hook Example Plugin unloaded!");
}
// These callbacks will only be present if SAMP_SDK_WANT_AMX_EVENTS is defined
/*void OnAmxLoad(AMX* amx) {
Samp_SDK::Log("AMX Load detected: %p", (void*)amx);
}
void OnAmxUnload(AMX* amx) {
Samp_SDK::Log("AMX Unload detected: %p", (void*)amx);
}*/Warning
La manipulation directe du tableau cell* params pour modifier les paramètres d'entrée nécessite de la prudence. Assurez-vous de comprendre l'ordre et le type des paramètres. Pour la plupart des cas d'utilisation, p.Get(...) pour inspecter et Call_Original_Native(...) pour continuer la chaîne est suffisant. La modification directe de params ne doit être effectuée que si vous savez que le paramètre est une valeur et doit être modifié pour l'appel original. Pour les chaînes et les tableaux, la modification est plus complexe et implique généralement amx::Set_String pour écrire à l'adresse existante ou réallouer, ce qui peut être plus facile à gérer en appelant la native via Pawn_Native avec les nouvelles valeurs et en retournant 0 de votre hook pour annuler l'appel original.
Ces macros sont l'inverse de Plugin_Public et Plugin_Native : elles permettent à votre code C++ d'invoquer des fonctions Pawn.
- Objectif : La manière recommandée d'appeler des fonctions natives SA-MP (ou d'autres plugins) depuis C++.
- Mécanisme : Recherche le pointeur de la native dans le cache interne du SDK (rempli par
Amx_Register_Detour). Si trouvé, exécute la native dans unAmx_Sandbox(une instance AMX fausse et isolée). - Performance : La plus efficace, car elle évite la recherche coûteuse des
publicset interagit directement avec le pointeur de la native.
- Objectif : Appelle une fonction publique spécifique dans un script Pawn.
- Mécanisme : Parcourt les instances
AMX*gérées par l'Amx_Manager, recherche lapublicpar son nom et l'exécute. - Performance : Moins efficace que
Pawn_Nativeen raison de la recherche et du véritableamx_Exec. Généralement, lespublicssont plus lentes que lesnatives. - Utilisation : Idéal pour invoquer des événements personnalisés dans vos Gamemode/Filterscripts qui ne sont pas des natives.
- Objectif : Une macro de commodité qui tente de deviner si la fonction est une native ou une publique.
- Mécanisme : D'abord, tente d'appeler comme
Pawn_Native. Si cela échoue (la native n'est pas trouvée), tente d'appeler commePawn_Public. - Performance : Peut être légèrement plus lente que
Pawn_Nativesi la fonction est native, en raison de la double tentative de recherche. Pour lespublics, les performances sont les mêmes quePawn_Public. - Utilisation : Pour les fonctions dont vous n'êtes pas sûr qu'elles soient natives ou publiques, ou pour éviter la répétition de code en essayant l'une puis l'autre.
- Nom de la fonction : Utilisez toujours le nom de la fonction Pawn directement, sans guillemets. Le SDK le convertira en chaîne de caractères en interne.
- Paramètres : Passez les paramètres C++ directement.
// Correct:
Pawn_Native(SetPlayerPos, playerid, 100.0f, 200.0f, 300.0f);
// Incorrect (but would technically work due to hashing, avoid):
Pawn_Native("SetPlayerPos", playerid, 100.0f, 200.0f, 300.0f); Le SDK convertit vos types C++ au format cell de l'AMX, gérant la mémoire selon les besoins :
int,bool,long,enum->cellfloat,double->cell(en utilisantamx::AMX_FTOC)const char*,std::string,std::string_view(C++17+) ->cell(alloue de la mémoire dans l'AMX, copie la chaîne et passe l'adresseamx_addr)
void Send_Formatted_Message(int playerid, const std::string& msg) {
Pawn_Native(SendClientMessage, playerid, 0xFFFFFFFF, msg);
}Ceci est une fonctionnalité clé pour la commodité et la sécurité. Pour les fonctions Pawn qui attendent un pointeur (référence), le SDK automatise tout le processus d'allocation/désallocation de mémoire et de copie des données.
- Comment utiliser : Il suffit de passer votre variable par référence (
&). - Mécanisme : Le SDK alloue de la mémoire sur le tas de l'AMX, passe l'adresse AMX à la fonction Pawn, attend que la fonction Pawn remplisse cette adresse, relit la valeur et libère la mémoire de l'AMX. Tout cela de manière transparente.
- Avec
std::string&: Le SDK alloue un buffer standard (256 cellules) dans l'AMX pour la chaîne.
void Get_Player_Location(int playerid) {
float x, y, z;
int interiorid, worldid;
std::string name;
Pawn_Native(GetPlayerPos, playerid, x, y, z);
Pawn_Native(GetPlayerInterior, playerid, interiorid);
Pawn_Native(GetPlayerVirtualWorld, playerid, worldid);
Pawn_Native(GetPlayerName, playerid, name, MAX_PLAYER_NAME);
Samp_SDK::Log("Location of %s (ID:%d): Pos(%.2f, %.2f, %.2f) Interior:%d World:%d", name.c_str(), playerid, x, y, z, interiorid, worldid);
}Tous les appels Pawn_* renvoient un objet Callback_Result. Cet objet est un wrapper sécurisé pour le résultat de l'appel Pawn.
Callback_Result() noexcept: Constructeur par défaut, indique l'échec (success_ = false).Callback_Result(bool success, cell value) noexcept: Constructeur pour succès ou échec avec valeur.explicit operator bool() const: Permet d'utiliserif (result)pour vérifier si l'appel a réussi.operator cell() const: Permet de convertir le résultat encellpour obtenir la valeur.float As_Float() const: Commodité pour obtenir le résultat sous forme defloat.cell Value() const: Retourne la valeur brutecell.bool Success() const: Retournetruesi l'appel Pawn a réussi.int Get_Amx_Error() const: Retourne le code d'erreur de l'AMX si l'appel a échoué (0 pour succès).
// Example: Getting a player's health.
// The native GetPlayerHealth(playerid, &Float:health) expects a playerid and a reference to the health.
int playerid = 0; // Example player ID
float player_health = 0.0f;
// We call GetPlayerHealth, passing playerid and player_health by reference.
// The SDK will handle marshalling for the 'health' output parameter.
Callback_Result result = Pawn_Native(GetPlayerHealth, playerid, player_health);
if (result) { // Checks if the call was successful (operator bool)
// The value returned by result.As_Float() or result (operator cell)
// would be the return value of the *native*, not the output parameter.
// The health value has already been updated in 'player_health' due to output parameter marshalling.
Samp_SDK::Log("Player %d has %.1f health.", playerid, player_health);
}
else {
// The call failed, perhaps the player does not exist or the native was not found.
Samp_SDK::Log("Error getting player %d health. AMX code: %d", playerid, result.Get_Amx_Error());
}
// For natives that return a value and use output parameters (less common, but possible),
// you would use both:
// Callback_Result other_result = Pawn_Native(SomeNative, param1, output_param, param2);
// if (other_result) {
// cell returned_value = other_result;
// // output_param is already updated
// }La macro Plugin_Module permet à votre plugin d'agir comme un "chargeur" pour d'autres plugins, créant une architecture modulaire et extensible. Un module chargé de cette manière est traité comme un plugin de première classe, avec son propre cycle de vie d'événements géré par le plugin hôte.
Plugin_Module(const char* nom_du_fichier_de_base, const char* repertoire_du_module, const char* message_de_succes_optionnel)nom_du_fichier_de_base: Le nom de base du fichier du module, sans l'extension (ex : pourmy_module.dlloumy_module.so, utilisez"my_module"). Le SDK ajoutera automatiquement l'extension.dllou.soappropriée.repertoire_du_module: Le chemin du répertoire où se trouve le fichier du module (ex :"plugins/my_custom_modules"). N'incluez pas le nom du fichier ici. Le SDK concaténera le chemin complet (repertoire_du_module/nom_du_fichier_de_base.ext).message_de_succes_optionnel: Un message facultatif à afficher dans la console du serveur si le module se charge avec succès.
// main.cpp, inside OnLoad()
// Loads the 'core_logic.dll' (or 'core_logic.so') module
// located in the server's 'modules/custom/' folder.
if (!Plugin_Module("core_logic", "modules/custom", "Core Logic Module loaded successfully!"))
return (Samp_SDK::Log("FATAL ERROR: Failed to load 'core_logic' module!"), false);
// Loads the 'admin_system.dll' (or 'admin_system.so') module
// located directly in the server's 'plugins/' folder.
if (!Plugin_Module("admin_system", "plugins", "Administration Module activated."))
Samp_SDK::Log("WARNING: Administration Module could not be loaded.");Un module doit exporter les fonctions Load, Unload et Supports, tout comme un plugin normal. Le SDK gère le cycle de vie du module comme suit :
-
Chargement : Lorsque
Plugin_Moduleest appelé, le SDK :- Construit le chemin complet du fichier (ex :
modules/custom/core_logic.dll). - Utilise
Dynamic_Library(LoadLibrary/dlopen) pour charger le binaire. - Obtient les pointeurs de TOUTES les fonctions de cycle de vie du module :
- Obligatoires :
Load,Unload,Supports. Si l'une d'elles manque, le chargement du module échoue. - Optionnelles :
AmxLoad,AmxUnload,ProcessTick.
- Obligatoires :
- Appelle la fonction
Loaddu module, en passantppDatadu plugin principal. - Si
Loadretournetrue, le module est ajouté à la liste interne des modules chargés.
- Construit le chemin complet du fichier (ex :
-
Transfert d'événements : Le plugin hôte transfère automatiquement les événements à tous les modules chargés.
Important
Pour que les événements soient transférés correctement, le plugin hôte (celui qui appelle Plugin_Module) doit être configuré pour recevoir ces événements.
- Pour que
AmxLoadetAmxUnloadfonctionnent dans les modules, le plugin hôte doit définir la macroSAMP_SDK_WANT_AMX_EVENTS. - Pour que
ProcessTickfonctionne dans les modules, le plugin hôte doit définir la macroSAMP_SDK_WANT_PROCESS_TICK.
- Déchargement : Lors du
OnUnloadde votre plugin principal, le SDK décharge tous les modules qui ont été chargés viaPlugin_Module. Cela se fait dans l'ordre inverse du chargement (le dernier chargé est le premier déchargé), ce qui est crucial pour gérer les dépendances et garantir la libération correcte des ressources.
- Organisation du code : Divisez les gros plugins en composants plus petits et gérables, chacun dans son propre fichier de module.
- Réutilisabilité : Créez des modules génériques (ex : un module de base de données, un module de système de journalisation avancé) qui peuvent être utilisés par différents plugins, favorisant la réutilisation du code.
- Composants indépendants : Créez des modules qui sont totalement orientés événements et indépendants. Un module peut avoir ses propres
Plugin_Natives, intercepter desPlugin_Publics et avoir sa propre logiqueOnProcessTick, fonctionnant comme un plugin autonome, mais chargé par un hôte. - Mises à jour dynamiques : Dans des scénarios contrôlés, permet la mise à jour de parties de votre système (en remplaçant un
.dllou un.sode module) sans avoir besoin de recompiler et de redémarrer le plugin principal ou l'ensemble du serveur (bien que cela nécessite une gestion rigoureuse des versions et de la compatibilité).
Utilisez Plugin_Call pour invoquer une Plugin_Native définie au sein de votre propre plugin.
Plugin_Call(NomeDaNativa, Param1, Param2, ...)- Avantage : Évite la surcharge de la recherche de la native dans le tableau des natives de l'AMX. Le SDK maintient une carte directe des hachages de noms vers les pointeurs de fonction pour ses propres natives, ce qui en fait le moyen le plus rapide de les appeler en interne.
- Requis :
SAMP_SDK_WANT_AMX_EVENTS.
// main.cpp
Plugin_Native(InternalCheckPlayerLevel, AMX* amx, cell* params) {
int playerid;
Register_Parameters(playerid);
// Logic to check the level
return (playerid % 2 == 0) ? 1 : 0; // Example: even level for even IDs
}
void Check_All_Players_Level() {
for (int i = 0; i < MAX_PLAYERS; ++i) {
if (Pawn_Native(IsPlayerConnected, i)) {
if (Plugin_Call(InternalCheckPlayerLevel, i)) // Calls its own native
Samp_SDK::Log("Player %d is at a high level!", i);
}
}
}- Description : Imprime des messages sur la console du serveur et dans le fichier
server_log.txt. Un wrapper sécurisé pourlogprintf. - Utilisation : Pour le débogage, les messages d'état et les erreurs.
- Mécanisme : En interne, le SDK obtient le pointeur de
logprintfviappData[PLUGIN_DATA_LOGPRINTF]. La fonction gère le formatage de la chaîne de manière sécurisée.
// Anywhere in your plugin
Samp_SDK::Log("The plugin was initialized with a value %d and a string '%s'.", 123, "test");- Description : Formate une chaîne de caractères de manière sécurisée (similaire à
sprintf) et renvoie unstd::string. C'est le moyen recommandé et le plus idiomatique de formater des chaînes de caractères pour une utilisation dans votre plugin. - Utilisation : Idéal pour construire des messages formatés avant de les passer à
Samp_SDK::Log,Pawn_Native(SendClientMessage, ...), ou pour tout autre besoin de chaîne de caractères dans votre code C++. - Mécanisme : En interne,
Plugin_Formatest une macro qui appelleSamp_SDK::Format. Il utilisevsnprintfpour déterminer la taille exacte de la chaîne formatée et alloue unstd::stringavec une capacité suffisante, évitant les débordements de tampon.
int playerid = 0; // Example ID
int health = 50;
Pawn_Native(SendClientMessage, playerid, 0xFFFFFFFF, Plugin_Format("Player %d, your current health is %d.", playerid, health));
// Can also be used for internal logs
Samp_SDK::Log(Plugin_Format("DEBUG: Processing status for ID %d", playerid));- Description : La fonction d'implémentation sous-jacente pour le formatage des chaînes, située dans le namespace
Samp_SDK. - Utilisation : Généralement non appelée directement par l'utilisateur. La macro
Plugin_Formatest fournie comme une commodité pour cette fonction, s'alignant sur la convention de nommage des autres macros du SDK (Plugin_Public,Plugin_Native). Vous ne l'appelleriez directement que si vous vouliez éviter la macroPlugin_Formatpour une raison spécifique.
// Example of how Samp_SDK::Format works, but prefer Plugin_Format
std::string raw_status = Samp_SDK::Format("For internal use only: %d.", 42);- Description : Convertit une adresse de chaîne de l'AMX (
cell amx_addr) en unstd::stringC++. - Utilisation : Principalement à l'intérieur de
Plugin_NativeetPlugin_Native_Hooklorsque vous devez accéder à des chaînes qui ne sont pas automatiquement converties parRegister_ParametersouNative_Params(par exemple, si le paramètre Pawn est uneconststringet n'a pas été déclaré commestd::stringdans votrePlugin_NativeouPlugin_Publicpour le marshalling automatique).
Plugin_Native(PrintRawAmxString, AMX* amx, cell* params) {
Native_Params p(amx, params);
cell amx_string_addr = p.Get<cell>(0); // Gets the string address in AMX
std::string cpp_string = Samp_SDK::Get_String(amx, amx_string_addr);
Samp_SDK::Log("String from AMX: %s", cpp_string.c_str());
return 1;
}- Votre plugin DOIT être compilé pour l'architecture x86 (32 bits).
- Plateformes prises en charge : Windows (.dll) et Linux (.so).
- Créez un nouveau projet de "Dynamic-Link Library (DLL)".
- Dans les paramètres du projet, définissez la "Plateforme de solution" sur x86.
- Assurez-vous que le standard de langage C++ est au moins C++17.
# For a plugin named 'my_plugin.so' from 'main.cpp'
g++ -m32 -shared -std=c++17 -O2 -fPIC -Wall -Wextra -Wl,--no-undefined main.cpp -o my_plugin.so-m32: Compile pour 32 bits.-shared: Crée une bibliothèque partagée (.so).-std=c++17: Définit la norme C++ comme C++17 (peut êtrec++20, mais C++17 est le minimum).-O2: Niveau d'optimisation 2.-fPIC: Génère du code indépendant de la position, nécessaire pour les bibliothèques partagées.-Wall -Wextra: Active des avertissements supplémentaires pour aider à détecter les erreurs.-Wl,--no-undefined: Empêche la création de la bibliothèque s'il y a des symboles non définis.
# For a plugin named 'my_plugin.dll' from 'main.cpp'
g++ -m32 -shared -std=c++17 -O2 -static-libstdc++ -static-libgcc -Wl,--no-undefined main.cpp -o my_plugin.dll-static-libstdc++: Lie la bibliothèque standard C++ statiquement. Essentiel pour éviter que votre plugin ne dépende de DLLs d'exécution spécifiques au compilateur qui pourraient ne pas être présentes sur le système de l'utilisateur.-static-libgcc: Lie la bibliothèque GCC statiquement.
- Nom du fichier : Votre plugin doit avoir l'extension
.dll(Windows) ou.so(Linux). Ex :my_plugin.dll. - Emplacement : Placez le fichier compilé dans le dossier
plugins/de votre serveur SA-MP. - server.cfg : Ajoutez le nom de votre plugin (si Windows, sans l'extension) à la ligne
pluginsdans leserver.cfg.plugins my_plugin (if Linux, my_plugin.so)
Copyright © AlderGrounds
Ce logiciel est sous licence selon les termes de la Licence MIT ("Licence"); vous pouvez utiliser ce logiciel conformément aux conditions de la Licence. Une copie de la Licence peut être obtenue à: MIT License
La présente licence accorde gratuitement à toute personne obtenant une copie de ce logiciel et des fichiers de documentation associés les droits suivants:
- Utiliser, copier, modifier, fusionner, publier, distribuer, sous-licencier et/ou vendre des copies du logiciel sans restriction
- Permettre aux personnes à qui le logiciel est fourni de faire de même, sous réserve des conditions suivantes
Toutes les copies ou parties substantielles du logiciel doivent inclure:
- L'avis de droit d'auteur ci-dessus
- Cet avis d'autorisation
- L'avis de non-responsabilité ci-dessous
Le logiciel et toute la documentation associée sont protégés par les lois sur le droit d'auteur. La AlderGrounds conserve la propriété des droits d'auteur originaux du logiciel.
LE LOGICIEL EST FOURNI "TEL QUEL", SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU IMPLICITE, Y COMPRIS MAIS NON LIMITÉ AUX GARANTIES DE COMMERCIALISATION, D'ADÉQUATION À UN USAGE PARTICULIER ET DE NON-VIOLATION.
EN AUCUN CAS LES AUTEURS OU LES DÉTENTEURS DES DROITS D'AUTEUR NE SERONT RESPONSABLES DE TOUTE RÉCLAMATION, DOMMAGE OU AUTRE RESPONSABILITÉ, QUE CE SOIT DANS UNE ACTION DE CONTRAT, UN DÉLIT OU AUTRE, DÉCOULANT DE, HORS DE OU EN RELATION AVEC LE LOGICIEL OU L'UTILISATION OU D'AUTRES TRANSACTIONS DANS LE LOGICIEL.
Pour des informations détaillées sur la Licence MIT, consultez: https://opensource.org/licenses/MIT