- Português: README
- Deutsch: README
- English: README
- Français: README
- Italiano: README
- Polski: README
- Русский: README
- Svenska: README
- Türkçe: README
- SA-MP SDK
- Idiomas
- Índice
- 1. Introducción y Filosofía de Diseño
- 2. Configuración y Ambiente
- 3. Guía de Uso Abrangente de la API
- 3.1. El Ciclo de Vida del Plugin
- 3.2. Exportando Funciones del Plugin
- 3.3.
Plugin_Public: Interceptando Eventos de Pawn - 3.4.
Plugin_Native: Creando Funciones Nativas en C++ - 3.5.
Plugin_Native_Hook: Interceptando Nativas Existentes de SA-MP - 3.6.
Pawn_*Macros: Llamando Funciones de Pawn desde C++ - 3.7.
Plugin_Module: Administración de Módulos Dinámicos - 3.8.
Plugin_Call: Llamando Nativas Internas del Plugin - 3.9. Funciones Utilitarias del SDK
- 4. Compilación y Despliegue
- Licencia
La API de plugins de SA-MP es una interfaz de programación en C. Aunque funcional y fundamental, presenta los desafíos inherentes a la programación de bajo nivel:
- Administración Manual de Memoria: Funciones como
amx_Allotyamx_Releaseexigen que el desarrollador asigne y desasigne explícitamente la memoria en el heap de la AMX. Esto es una fuente común de fugas de memoria y fallos de runtime. - Tipado Débil y Conversiones Manuales: Los parámetros se pasan como un array de
cells, forzando conversiones explícitas (y muchas veces inseguras) entrecell,int,floatychar*. - Verbosidad y Código Repetitivo (Boilerplate): Extraer múltiples parámetros de un array
cell* params, manejar tamaños de strings y administrar la pila de la AMX para las llamadas de retorno de C++ a Pawn requiere código repetitivo. - Fragilidad de la Interfaz: La falta de verificación en tiempo de compilación (type safety) significa que los errores en el paso de parámetros o tipos pueden pasar desapercibidos hasta la ejecución, causando bloqueos o comportamientos indefinidos.
El SA-MP SDK aborda estos problemas proporcionando una potente capa de abstracción en C++:
- RAII (Resource Acquisition Is Initialization): Administración automática de memoria en la AMX.
Amx_Scoped_Memorygarantiza que la memoria asignada sea liberada. - Seguridad de Tipos: Conversión automática y segura de parámetros entre C++ y Pawn. Usted trabaja con
int,float,std::stringdirectamente. - Sintaxis Concisa e Idiomática: Las macros y las plantillas proporcionan una API limpia que se asemeja más al C++ moderno que a la API C tradicional.
- Interceptación Robusta: Un motor de hooking avanzado permite la interceptación transparente de callbacks de Pawn (
Plugin_Public), la creación de nuevas nativas (Plugin_Native), y el hooking y encadenamiento de funciones nativas existentes de SA-MP (Plugin_Native_Hook). - Alto Rendimiento: Uso extensivo de hashing en tiempo de compilación (FNV1a), caching de funciones y optimizaciones de
thread_localpara minimizar la sobrecarga de las abstracciones.
El SDK está compuesto exclusivamente por archivos de cabecera (.hpp, .h).
- Ventajas:
- Integración Simplificada: No hay bibliotecas para construir, vincular o distribuir. Simplemente incluya los headers.
- Optimizaciones del Compilador: El compilador tiene visibilidad completa del código del SDK y de su plugin, permitiendo inlining agresivo y optimizaciones de tiempo de enlazado, lo que puede resultar en binarios más rápidos.
- Implicaciones:
- Tiempo de Compilación: Para proyectos muy grandes, la compilación puede llevar más tiempo debido a la inclusión repetida del código del SDK. Esto se mitiga con guards de inclusión y por la naturaleza de "incluir solo lo necesario" de C++.
- Macros de Implementación: La necesidad de la macro
SAMP_SDK_IMPLEMENTATIONes una consecuencia del modelo header-only para evitar redefiniciones de símbolos.
- Compilador C++: Compatible con C++17 o superior.
- GCC (versión 8+)
- Clang (versión 5+)
- MSVC (Visual Studio 2017+)
- Arquitectura: x86 (32 bits). SA-MP opera exclusivamente en esta arquitectura. El SDK incluye comprobaciones en
platform.hppque emitirán errores de compilación si se detecta una arquitectura incorrecta. - Sistema Operativo: Windows o Linux.
Para mayor claridad y organización, es común organizar el SDK en una subcarpeta samp-sdk.
my_plugin/
├── samp-sdk/
│ ├── // Otros archivos del SDK
│ └── samp_sdk.hpp // El principal header a incluir
│
├── src/
│ ├── main.cpp // Donde se define SAMP_SDK_IMPLEMENTATION
│ └── my_custom_logic.cpp // Opcional, para organizar el código
│
└── CMakeLists.txt (o .vcxproj, Makefile)
Siempre defina estas macros antes de incluir samp_sdk.hpp.
- Propósito: Señaliza al compilador que este archivo
.cppdebe generar las implementaciones de las funciones de exportación del plugin (Supports,Load,Unload,AmxLoad,AmxUnload,ProcessTick). El SDK se encarga de la exportación de estas funciones automáticamente, eliminando la necesidad de archivos.def(en Windows) o declaraciones__attribute__((visibility("default")))(en Linux) para estas funciones. - Impacto Técnico: Sin esta macro, el enlazador no encontrará las exportaciones necesarias, y el servidor SA-MP no podrá cargar su plugin.
- Regla Fundamental: Defina esta macro en SÓLO UN archivo
.cppen todo su proyecto. Definirla en más de un archivo causará un error de enlazado de "símbolo duplicado".
// main.cpp
#define SAMP_SDK_IMPLEMENTATION // La macro hace que el SDK exporte las funciones necesarias automáticamente.
#include "samp-sdk/samp_sdk.hpp"
// ... su código de plugin ...- Propósito: Habilita los callbacks de ciclo de vida del script Pawn (
OnAmxLoad,OnAmxUnload) y la funcionalidad de crear nuevas nativas en C++ (Plugin_Native). - Funcionalidades Activadas:
- Callbacks
OnAmxLoad(AMX* amx)yOnAmxUnload(AMX* amx). - Declaración y registro de nuevas nativas C++ usando
Plugin_Native. - La macro
Plugin_Callpara invocar nativas creadas conPlugin_Nativedentro de su propio plugin.
- Callbacks
- Impacto Técnico: Cuando se define esta macro, el SDK recolecta automáticamente todas sus
Plugin_Natives. En la funciónSupports(), la flagSUPPORTS_AMX_NATIVESse añade automáticamente si hay algunaPlugin_Nativeen su proyecto. - Flexibilidad: Puede definir esta macro en múltiples archivos
.cpp. El sistema de registro estático del SDK consolidará todas las nativas de diferentes unidades de compilación en una única lista global.
// my_natives.cpp (puede ser un archivo separado de main.cpp)
#define SAMP_SDK_WANT_AMX_EVENTS // Solo para habilitar Plugin_Native y los callbacks OnAmxLoad/OnAmxUnload
#include "samp-sdk/samp_sdk.hpp"
Plugin_Native(MyCustomNative, AMX* amx, cell* params) {
// ...
return 0;
}- Propósito: Habilita el callback
OnProcessTick(), que es invocado regularmente por el servidor. - Impacto Técnico: Añade automáticamente la flag
SUPPORTS_PROCESS_TICKen la funciónSupports().
// main.cpp
#define SAMP_SDK_IMPLEMENTATION
#define SAMP_SDK_WANT_PROCESS_TICK
#include "samp-sdk/samp_sdk.hpp"
void OnProcessTick() {
// Lógica ejecutada en cada "tick" del servidor (ej: 20ms)
// ¡Cuidado con operaciones pesadas aquí!
}Este archivo proporciona todas las constantes y límites conocidos de SA-MP, como MAX_PLAYERS, INVALID_PLAYER_ID, PLAYER_STATE_ONFOOT, WEAPON_DEAGLE, DIALOG_STYLE_LIST, etc. Se incluye automáticamente por samp_sdk.hpp y debe usarse para garantizar la compatibilidad y legibilidad del código.
Las siguientes funciones son los puntos de entrada y salida de su plugin, exportadas automáticamente por el SDK.
- Descripción: Primera función llamada por el servidor SA-MP después de la carga exitosa de su plugin en memoria.
- Uso: Ideal para inicializar cualquier sistema, cargar configuraciones, abrir conexiones a bases de datos o cargar módulos (
Plugin_Module). - Retorno:
true: El plugin se inicializó con éxito y la carga continúa.false: El plugin falló al inicializar. La carga se abortará y el plugin se descargará.
// main.cpp
bool OnLoad() {
Samp_SDK::Log("Inicializando MeuPlugin v1.0...");
// Ejemplo: Cargar un módulo (más detalles en la sección 3.6)
if (!Plugin_Module("my_database_module", "plugins/db_connector", "Módulo de Base de Datos cargado.")) {
Samp_SDK::Log("ERROR: ¡Fallo al cargar el módulo de base de datos!");
return false; // Aborta la carga del plugin principal
}
return true;
}- Descripción: Última función llamada por el servidor SA-MP antes de descargar su plugin de la memoria.
- Uso: Ideal para limpiar recursos, cerrar conexiones, guardar estados y garantizar que no se filtren recursos. El SDK administra la descarga de módulos (
Plugin_Module) automáticamente.
// main.cpp
void OnUnload() {
Samp_SDK::Log("MeuPlugin descargado. Liberando recursos...");
// No se requiere ninguna acción manual para los módulos cargados a través de Plugin_Module;
// el SDK se encarga de ello.
}- Descripción: Informa al servidor SA-MP qué recursos soporta y desea utilizar su plugin.
- Uso: Siempre devuelva
SUPPORTS_VERSION(oSAMP_PLUGIN_VERSION). Las flagsSUPPORTS_AMX_NATIVESySUPPORTS_PROCESS_TICKson añadidas automáticamente por el SDK si hayPlugin_Natives, y/o si la macroSAMP_SDK_WANT_PROCESS_TICKestá definida, respectivamente. Esto simplifica el mantenimiento y evita errores.
// main.cpp
unsigned int GetSupportFlags() {
return SUPPORTS_VERSION; // El SDK añade las flags necesarias automáticamente.
}- Requiere:
SAMP_SDK_WANT_AMX_EVENTS - Descripción: Llamada cada vez que se carga e inicializa un nuevo script Pawn (un Gamemode o Filterscript) en el servidor.
- Uso: Si necesita lógica específica para cada script AMX, o inicializar datos específicos por script.
// main.cpp (con SAMP_SDK_WANT_AMX_EVENTS definido)
void OnAmxLoad(AMX* amx) {
// amx representa la nueva instancia del script cargado.
Samp_SDK::Log("Script AMX cargado: %p", (void*)amx);
}- Requiere:
SAMP_SDK_WANT_AMX_EVENTS - Descripción: Llamada cuando un script Pawn se descarga del servidor.
- Uso: Para limpiar cualquier recurso específico que haya asignado o asociado a esa
AMX*en particular.
// main.cpp (con SAMP_SDK_WANT_AMX_EVENTS definido)
void OnAmxUnload(AMX* amx) {
Samp_SDK::Log("Script AMX descargado: %p", (void*)amx);
}- Requiere:
SAMP_SDK_WANT_PROCESS_TICK - Descripción: Llamada repetidamente en cada "tick" del servidor (generalmente 20 veces por segundo, o cada 50ms).
- Uso: Para lógica de fondo continua, temporizadores, actualizaciones de estado que no dependen de eventos del jugador, o sincronización de datos.
- Cuidado: Evite operaciones de bloqueo o computacionalmente pesadas aquí, ya que pueden causar lag en el servidor.
// main.cpp (con SAMP_SDK_WANT_PROCESS_TICK definido)
static int tick_count = 0;
void OnProcessTick() {
tick_count++;
if (tick_count % 200 == 0) // Cada 10 segundos (20 ticks/seg * 10 seg = 200 ticks)
Samp_SDK::Log("Servidor activo por %d segundos.", tick_count / 20);
}Para que el servidor SA-MP pueda llamar a las funciones de tu plugin (Load, Supports, etc.), estas necesitan ser "exportadas" del archivo DLL (Windows) o SO (Linux). El SDK automatiza la exportación de las funciones de ciclo de vida estándar, pero también proporciona herramientas para que puedas exportar tus propias funciones personalizadas, en caso de que necesites interoperabilidad con otros programas.
El método para exportar funciones varía según el compilador.
En Windows con MSVC, la forma más fácil de exportar funciones personalizadas es usando el macro Export_Plugin, definido en exports.hpp.
- Sintaxis:
Export_Plugin("Función", "Pila") Función: El nombre exacto de la función a exportar.Pila: La cantidad total de bytes que los parámetros de la función ocupan en la pila. Para la convención__stdcall(estándar del SDK en Windows), el cálculo es4 * (Cantidad de Parámetros).
#include "samp-sdk/exports.hpp"
// Ejemplo: Exportando una función personalizada que podría ser llamada
// por otro programa o plugin que conozca su firma.
const char* SAMP_SDK_CALL GetPluginVersion() {
return "1.0.0";
}
Export_Plugin("GetPluginVersion", "0");Warning
Limitación de Export_Plugin
Este macro funciona solo con el compilador MSVC (Visual Studio). Utiliza una directiva #pragma específica de Microsoft que es ignorada por otros compiladores como GCC y Clang.
Para GCC y Clang (en Windows o Linux), la exportación es gestionada por el macro SAMP_SDK_EXPORT, definido en platform.hpp. Simplemente lo colocas antes de la definición de la función.
- Mecanismo: En Linux, añade
__attribute__((visibility("default"))). En Windows con GCC/Clang, añade__attribute__((dllexport)). - Uso: El SDK ya aplica
SAMP_SDK_EXPORTa todas las funciones de ciclo de vida (Load,Unload, etc.), por lo que la exportación de estas es totalmente automática para estos compiladores. Para tus funciones personalizadas, simplemente haz lo mismo.
// Para GCC/Clang, la definición de la función con SAMP_SDK_EXPORT es suficiente.
SAMP_SDK_EXPORT const char* SAMP_SDK_CALL GetPluginVersion() {
return "1.0.0";
}La macro Plugin_Public es el puente principal para recibir callbacks de Pawn en su código C++.
Plugin_Public(NombreDeLaPublic, Tipo1 Param1, Tipo2 Param2, ...)- El nombre de la función C++ que usted declara debe ser el mismo que el callback de Pawn (ej:
OnPlayerConnect). - Los tipos de parámetros C++ (
int,float,std::string) son automáticamente convertidos por el SDK.
// Intercepta OnPlayerText(playerid, text[])
Plugin_Public(OnPlayerText, int playerid, std::string text) {
Samp_SDK::Log("Jugador %d dijo: %s", playerid, text.c_str());
return PUBLIC_CONTINUE;
}El SDK maneja automáticamente la lectura de la cell stack de la AMX y la conversión a los tipos C++ especificados:
int: Convertido directamente decell.float: Convertido decellusandoamx::AMX_CTOF.std::string: El SDK lee la dirección de la string en la AMX, asigna unastd::stringen C++ y copia el contenido.
El valor devuelto por su función Plugin_Public es crucial y determina el flujo de ejecución del callback:
return PUBLIC_CONTINUE;(valor1): Indica que la ejecución del callback debe continuar. Si hay otros plugins que también interceptan este callback, se les llamará (en orden inverso de carga). Luego, se invocará lapublicoriginal en el script Pawn.return PUBLIC_STOP;(valor0): Indica que la ejecución del callback debe ser interrumpida. Ningún otro plugin (con menor prioridad) o lapublicoriginal en el script Pawn será invocado para este evento. Esto es ideal para implementar un sistema que "sobrescribe" o "bloquea" un comportamiento estándar de SA-MP.
// main.cpp
Plugin_Public(OnPlayerCommandText, int playerid, std::string cmdtext) {
if (cmdtext == "/freeze") {
Pawn_Native(TogglePlayerControllable, playerid, 0); // Congela al jugador
Pawn_Native(SendClientMessage, playerid, -1, Plugin_Format("Jugador %d congelado.", playerid));
return PUBLIC_STOP; // Impide que el comando sea procesado por otros scripts.
}
return PUBLIC_CONTINUE; // Permite que otros comandos sean procesados.
}Una característica avanzada de Plugin_Public es el soporte a "Ghost Callbacks". Esto significa que puede interceptar un callback de Pawn incluso si no está presente en el script .amx del gamemode o filterscript. El SDK "engaña" al servidor para que llame a su hook C++ de todas formas. Esto es útil para callbacks internos o para crear nuevas funcionalidades sin depender de la presencia de una public en el script Pawn.
// Puede definir un callback que el script Pawn no posee, pero su plugin lo escuchará.
Plugin_Public(OnMyCustomInternalEvent, int data1, float data2) {
Samp_SDK::Log("Evento interno personalizado recibido: %d, %.2f", data1, data2);
return PUBLIC_CONTINUE;
}
// Para "disparar" este evento desde otro punto de su código C++:
// Pawn_Public(OnMyCustomInternalEvent, 123, 45.67f);
// La llamada irá a su Plugin_Public anterior, incluso si no hay OnMyCustomInternalEvent en Pawn.Plugin_Native le permite extender la funcionalidad de Pawn con código C++ de alto rendimiento.
Plugin_Native(NombreDeLaNative, AMX* amx, cell* params)- La función C++ debe tener exactamente esta firma:
cell NombreDeLaNative(AMX* amx, cell* params). NombreDeLaNativees el nombre que utilizarán los scripts Pawn.
// Pawn: native GetPlayerPingAverage(playerid);
Plugin_Native(GetPlayerPingAverage, AMX* amx, cell* params) {
// ... Implementación ...
return 0;
}Note
No necesita llamar a amx_Register manualmente. El SDK detecta todas sus Plugin_Natives (en cualquier archivo .cpp que haya incluido samp_sdk.hpp y definido SAMP_SDK_WANT_AMX_EVENTS) y las registra automáticamente en cada script AMX cargado (OnAmxLoad).
- Descripción: Un asistente más simple para extraer múltiples parámetros secuencialmente.
- Uso:
Register_Parameters(variable1, variable2, ...) - Limitaciones: Para parámetros de entrada. No maneja parámetros opcionales o acceso por índice.
- Tipos Soportados:
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); // Extrae los 3 parámetros
Pawn_Native(SetPlayerSkin, playerid, skinid);
Pawn_Native(GivePlayerMoney, playerid, money);
return 1;
}- Descripción: Una clase wrapper que proporciona una interfaz orientada a objetos para acceder a los parámetros de una nativa. Más potente para escenarios complejos.
- Construcción:
Native_Params p(amx, params);
- Descripción: Devuelve el número de parámetros pasados a la nativa.
- Uso: Esencial para manejar parámetros opcionales.
- Descripción: Extrae un parámetro de entrada por índice y lo convierte al tipo
T. - Tipos Soportados:
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);
}- Descripción: Obtiene el valor de un parámetro de referencia (puntero Pawn) y lo almacena en
out_value. - Uso: Para leer valores que fueron pasados por referencia desde Pawn.
- Retorno:
truesi la dirección AMX es válida,falseen caso contrario.
// 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;
// Obtiene los valores de las referencias (Pawn pasó direcciones)
p.Get_REF(1, health); // Lee el valor de Float:health
p.Get_REF(2, money); // Lee el valor de money
Samp_SDK::Log("Jugador %d, Health: %.1f, Money: %d", playerid, health, money);
// Ahora, modificarlos
health = 50.0f;
money += 100;
// Y escribirlos de vuelta en la memoria de Pawn
p.Set_REF(1, health);
p.Set_REF(2, money);
return 1;
}- Descripción: Devuelve un
std::optional<T>para leer un parámetro de referencia. Más seguro para C++17 y superior.
- Descripción: Escribe un valor
Ten un parámetro de referencia de Pawn (la dirección que Pawn pasó). - Uso: Para modificar valores que fueron pasados por referencia, haciendo que Pawn vea el cambio.
- Retorno:
truesi la escritura fue exitosa,falseen caso contrario.
- Su función
Plugin_Nativedebe retornar unacell. - Para retornar un
intobool, use un cast acell. - Para retornar un
float, useamx::AMX_FTOC(mi_float).
// Retorna un bool
Plugin_Native(IsPlayerSpawned, AMX* amx, cell* params) {
int playerid;
Register_Parameters(playerid);
return (Pawn_Native(GetPlayerState, playerid) == PLAYER_STATE_SPAWNED) ? 1 : 0;
}
// Retorna un float
Plugin_Native(GetPlayerMaxHealth, AMX* amx, cell* params) {
return amx::AMX_FTOC(100.0f); // Retorna 100.0f
}La macro Plugin_Native_Hook le permite interceptar y modificar el comportamiento de cualquier función nativa existente de SA-MP o de otros plugins. Este es un mecanismo potente para extender o alterar la lógica estándar del servidor.
Plugin_Native_Hook(NombreDeLaNative, AMX* amx, cell* params)- La función C++ debe tener exactamente esta firma:
cell NombreDeLaNative(AMX* amx, cell* params). NombreDeLaNativedebe ser el nombre exacto de la native que desea hookear (ej:SendClientMessage,SetPlayerPos).
// Intercepta la native SendClientMessage
Plugin_Native_Hook(SendClientMessage, AMX* amx, cell* params) {
// ...
return Call_Original_Native(SendClientMessage); // Importante llamar a la original
}Note
No necesita llamar a amx_Register manualmente o definir SAMP_SDK_WANT_AMX_EVENTS específicamente para Plugin_Native_Hook. El SDK detecta y registra automáticamente sus hooks. En la primera ejecución de un script AMX, el SDK reemplaza el puntero de la native en la tabla por un "trampoline" que redirige a su función Plugin_Native_Hook. Este proceso garantiza el encadenamiento seguro de hooks de múltiples plugins.
Dentro de su función Plugin_Native_Hook, DEBE usar la macro Call_Original_Native(NombreDeLaNative) para invocar la función original (o el siguiente hook en la cadena). Esto es vital para:
- Preservar la Funcionalidad: Si no llama a la original, la native hookeada simplemente dejará de funcionar para otros plugins o para el servidor.
- Encadenamiento de Hooks: Permite que múltiples plugins hookeen la misma native y que todos los hooks se ejecuten en una secuencia.
- Manipulación de Retorno: Puede inspeccionar e incluso modificar el valor de retorno de
Call_Original_Nativeantes de retornarlo desde su función de hook.
// Ejemplo: Bloqueando SendClientMessage si contiene una palabra específica
Plugin_Native_Hook(SendClientMessage, AMX* amx, cell* params) {
Native_Params p(amx, params);
// Extrae los parámetros para análisis
int playerid = p.Get<int>(0);
// int color = p.Get<int>(1); // No necesitamos el color para esta lógica
std::string message = p.Get_String(2); // Obtiene la string del mensaje
if (message.find("BADWORD") != std::string::npos) {
Samp_SDK::Log("MENSAJE BLOQUEADO para playerid %d: %s", playerid, message.c_str());
return 0; // Retorna 0 (false) a Pawn, indicando que el mensaje no fue enviado.
// Y lo que es más importante, NO llamamos a Call_Original_Native, bloqueando el mensaje.
}
// Si el mensaje no contiene la palabra prohibida, llamamos a la native original
// y retornamos su valor, garantizando que el mensaje se envíe normalmente
// y que otros hooks (si existen) se ejecuten.
return Call_Original_Native(SendClientMessage);
}#define SAMP_SDK_IMPLEMENTATION
// SAMP_SDK_WANT_AMX_EVENTS no es estrictamente necesario para hooks, pero es común tener OnAmxLoad/Unload
// #define SAMP_SDK_WANT_AMX_EVENTS
#include "samp-sdk/samp_sdk.hpp"
// Hook para la native CreateVehicle
Plugin_Native_Hook(CreateVehicle, AMX* amx, cell* params) {
Native_Params p(amx, params);
// Extrae los parámetros de la native CreateVehicle para inspección
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 llamado! Model: %d, Pos: (%.2f, %.2f, %.2f)", modelid, x, y, z);
// Ejemplo de cómo *modificar* un parámetro de entrada
// Si el modelo es 400 (Landstalker), lo cambiamos a 401 (Bravura)
if (modelid == 400) {
// Modificamos directamente el array 'params' para la llamada original
params[1] = static_cast<cell>(401); // El modelo está en la posición 0 del array de parámetros (params[1])
Samp_SDK::Log(" -> Modelo 400 cambiado a 401 antes de la creación.");
}
// Llamamos a la native original (o el siguiente hook en la cadena) con los parámetros posiblemente modificados
cell original_retval = Call_Original_Native(CreateVehicle);
Samp_SDK::Log("HOOK: CreateVehicle retornó: %d (ID del vehículo)", (int)original_retval);
// Puede modificar el valor de retorno aquí antes de retornarlo a Pawn.
// Ejemplo: si el vehículo falló en la creación, retorne un ID inválido personalizado.
if ((int)original_retval == INVALID_VEHICLE_ID) {
Samp_SDK::Log(" -> La creación del vehículo falló en la native original.");
return -1; // Retorna un valor diferente a Pawn.
}
return original_retval; // Retorna el valor que la native original retornó (o el modificado anteriormente).
}
unsigned int GetSupportFlags() {
return SUPPORTS_VERSION;
}
// Implementaciones mínimas para el ciclo de vida
bool OnLoad() {
Samp_SDK::Log("¡Plugin de Ejemplo de Native Hook cargado!");
return true;
}
void OnUnload() {
Samp_SDK::Log("¡Plugin de Ejemplo de Native Hook descargado!");
}
// Estos callbacks solo estarán presentes si SAMP_SDK_WANT_AMX_EVENTS está definido
/*void OnAmxLoad(AMX* amx) {
Samp_SDK::Log("AMX Load detectado: %p", (void*)amx);
}
void OnAmxUnload(AMX* amx) {
Samp_SDK::Log("AMX Unload detectado: %p", (void*)amx);
}*/Warning
La manipulación directa del array cell* params para alterar parámetros de entrada exige cautela. Asegúrese de entender el orden y el tipo de los parámetros. Para la mayoría de los casos de uso, p.Get(...) para inspeccionar y Call_Original_Native(...) para continuar la cadena es suficiente. La alteración directa de params debe hacerse solo si sabe que el parámetro es un valor y necesita ser modificado para la llamada original. Para strings y arrays, la modificación es más compleja y generalmente implica amx::Set_String para escribir en la dirección existente o reasignar, lo que puede ser más fácil de manejar llamando a la native a través de Pawn_Native con los nuevos valores y retornando 0 de su hook para cancelar la llamada original.
Estas macros son el inverso de Plugin_Public y Plugin_Native: permiten que su código C++ invoque funciones de Pawn.
- Propósito: La forma recomendada de llamar funciones nativas de SA-MP (o de otros plugins) desde C++.
- Mecanismo: Busca el puntero de la nativa en el caché interno del SDK (rellenado por
Amx_Register_Detour). Si se encuentra, ejecuta la nativa en un entornoAmx_Sandbox(una instancia AMX falsa y aislada). - Rendimiento: La más eficiente, ya que evita la costosa búsqueda de
publicse interactúa directamente con el puntero de la nativa.
- Propósito: Llama a una función pública específica en un script Pawn.
- Mecanismo: Recorre las instancias
AMX*administradas por elAmx_Manager, busca lapublicpor su nombre y la ejecuta. - Rendimiento: Menos eficiente que
Pawn_Nativedebido a la búsqueda y alamx_Execreal. Generalmente, laspublicsson más lentas que lasnatives. - Uso: Ideal para invocar eventos personalizados en su Gamemode/Filterscripts que no son nativas.
- Propósito: Una macro de conveniencia que intenta adivinar si la función es una nativa o una pública.
- Mecanismo: Primero, intenta llamar como
Pawn_Native. Si falla (no se encuentra la nativa), intenta llamar comoPawn_Public. - Rendimiento: Puede ser un poco más lenta que
Pawn_Nativesi la función es nativa, debido al intento de búsqueda doble. Parapublics, el rendimiento es el mismo quePawn_Public. - Uso: Para funciones donde no está seguro si son nativas o públicas, o para evitar el código repetitivo de intentar una y luego la otra.
- Nombre de la Función: Siempre use el nombre de la función Pawn directamente, sin comillas. El SDK lo convertirá a string internamente.
- Parámetros: Pase los parámetros C++ directamente.
// Correcto:
Pawn_Native(SetPlayerPos, playerid, 100.0f, 200.0f, 300.0f);
// Incorrecto (pero técnicamente funcionaría debido al hash, evítelo):
Pawn_Native("SetPlayerPos", playerid, 100.0f, 200.0f, 300.0f); El SDK convierte sus tipos C++ al formato cell de la AMX, administrando la memoria según sea necesario:
int,bool,long,enum->cellfloat,double->cell(usandoamx::AMX_FTOC)const char*,std::string,std::string_view(C++17+) ->cell(asigna memoria en la AMX, copia la string y pasa la direcciónamx_addr)
void Send_Formatted_Message(int playerid, const std::string& msg) {
Pawn_Native(SendClientMessage, playerid, 0xFFFFFFFF, msg);
}Esta es una funcionalidad clave para la comodidad y seguridad. Para funciones de Pawn que esperan un puntero (referencia), el SDK automatiza todo el proceso de asignación/liberación de memoria y copia de datos.
- Cómo usar: Simplemente pase su variable por referencia (
&). - Mecanismo: El SDK asigna memoria en el heap de la AMX, pasa la dirección AMX a la función de Pawn, espera que la función de Pawn rellene esa dirección, lee el valor de vuelta y libera la memoria de la AMX. Todo de forma transparente.
- Con
std::string&: El SDK asigna un búfer estándar (256 celdas) en la AMX para la string.
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("Ubicación de %s (ID:%d): Pos(%.2f, %.2f, %.2f) Interior:%d World:%d", name.c_str(), playerid, x, y, z, interiorid, worldid);
}Todas las llamadas Pawn_* devuelven un objeto Callback_Result. Este objeto es un envoltorio seguro para el resultado de la llamada de Pawn.
Callback_Result() noexcept: Constructor predeterminado, indica fallo (success_ = false).Callback_Result(bool success, cell value) noexcept: Constructor para éxito o fallo con valor.explicit operator bool() const: Permite usarif (result)para verificar si la llamada fue exitosa.operator cell() const: Permite convertir el resultado acellpara obtener el valor.float As_Float() const: Conveniencia para obtener el resultado comofloat.cell Value() const: Retorna el valorcellbruto.bool Success() const: Retornatruesi la llamada de Pawn fue exitosa.int Get_Amx_Error() const: Retorna el código de error de la AMX si la llamada falló (0 para éxito).
// Ejemplo: Obteniendo la vida de un jugador.
// La nativa GetPlayerHealth(playerid, &Float:health) espera un playerid y una referencia para la vida.
int playerid = 0; // Ejemplo de ID de jugador
float player_health = 0.0f;
// Llamamos a GetPlayerHealth, pasando playerid y player_health por referencia.
// El SDK se encargará del marshalling para el parámetro de salida 'health'.
Callback_Result result = Pawn_Native(GetPlayerHealth, playerid, player_health);
if (result) { // Verifica si la llamada fue exitosa (operator bool)
// El valor retornado por result.As_Float() o result (operator cell)
// sería el valor de retorno de la *nativa*, no el parámetro de salida.
// El valor de la vida ya se actualizó en 'player_health' debido al marshalling del parámetro de salida.
Samp_SDK::Log("Jugador %d tiene %.1f de vida.", playerid, player_health);
}
else {
// La llamada falló, quizás el jugador no existe o la nativa no fue encontrada.
Samp_SDK::Log("Error al obtener vida del jugador %d. Código AMX: %d", playerid, result.Get_Amx_Error());
}
// Para nativas que devuelven un valor y usan parámetros de salida (menos común, pero posible),
// usted usaría ambos:
// Callback_Result other_result = Pawn_Native(SomeNative, param1, output_param, param2);
// if (other_result) {
// cell returned_value = other_result;
// // output_param ya está actualizado
// }La macro Plugin_Module permite que su plugin actúe como un "cargador" para otros plugins, creando una arquitectura modular y extensible. Un módulo cargado de esta forma es tratado como un plugin de primera clase, con su propio ciclo de vida de eventos administrado por el plugin anfitrión.
Plugin_Module(const char* nombre_del_archivo_base, const char* directorio_del_modulo, const char* mensaje_exito_opcional)nombre_del_archivo_base: El nombre base del archivo del módulo, sin la extensión (ej: paramy_module.dllomy_module.so, use"my_module"). El SDK agregará automáticamente la extensión.dllo.soapropiada.directorio_del_modulo: La ruta del directorio donde se encuentra el archivo del módulo (ej:"plugins/my_custom_modules"). No incluya el nombre del archivo aquí. El SDK concatenará la ruta completa (directorio_del_modulo/nombre_del_archivo_base.ext).mensaje_exito_opcional: Un mensaje opcional que se registrará en la consola del servidor si el módulo se carga con éxito.
// main.cpp, dentro de OnLoad()
// Carga el módulo 'core_logic.dll' (o 'core_logic.so')
// que está ubicado en la carpeta 'modules/custom/' del servidor.
if (!Plugin_Module("core_logic", "modules/custom", "¡Módulo de Lógica Core cargado con éxito!"))
return (Samp_SDK::Log("¡ERROR FATAL: Fallo al cargar el módulo 'core_logic'!"), false);
// Carga el módulo 'admin_system.dll' (o 'admin_system.so')
// que está ubicado directamente en la carpeta 'plugins/' del servidor.
if (!Plugin_Module("admin_system", "plugins", "Módulo de Administración activado."))
Samp_SDK::Log("AVISO: El módulo de Administración no pudo ser cargado.");Un módulo debe exportar las funciones Load, Unload y Supports, al igual que un plugin normal. El SDK administra el ciclo de vida del módulo de la siguiente manera:
-
Carga: Cuando se llama a
Plugin_Module, el SDK:- Construye la ruta completa del archivo (ej:
modules/custom/core_logic.dll). - Usa
Dynamic_Library(LoadLibrary/dlopen) para cargar el binario. - Obtiene los punteros a TODAS las funciones del ciclo de vida del módulo:
- Obligatorias:
Load,Unload,Supports. Si alguna falta, la carga del módulo falla. - Opcionales:
AmxLoad,AmxUnload,ProcessTick.
- Obligatorias:
- Llama a la función
Loaddel módulo, pasandoppDatadel plugin principal. - Si
Loaddevuelvetrue, el módulo se añade a la lista interna de módulos cargados.
- Construye la ruta completa del archivo (ej:
-
Reenvío de Eventos: El plugin anfitrión reenvía automáticamente los eventos a todos los módulos cargados.
Important
Para que los eventos se reenvíen correctamente, el plugin anfitrión (el que llama a Plugin_Module) debe estar configurado para recibir esos eventos.
- Para que
AmxLoadyAmxUnloadfuncionen en los módulos, el plugin anfitrión debe definir la macroSAMP_SDK_WANT_AMX_EVENTS. - Para que
ProcessTickfuncione en los módulos, el plugin anfitrión debe definir la macroSAMP_SDK_WANT_PROCESS_TICK.
- Descarga: Durante
OnUnloadde su plugin principal, el SDK descarga todos los módulos que fueron cargados a través dePlugin_Module. Esto se realiza en orden inverso a la carga (el último en cargarse es el primero en descargarse), lo cual es crucial para administrar dependencias y garantizar la liberación correcta de recursos.
- Organización del Código: Divida grandes plugins en componentes más pequeños y manejables, cada uno en su propio archivo de módulo.
- Reusabilidad: Cree módulos genéricos (ej: un módulo de base de datos, un módulo de sistema de registro avanzado) que puedan ser utilizados por diferentes plugins, promoviendo la reutilización de código.
- Componentes Independientes: Cree módulos que sean totalmente orientados a eventos e independientes. Un módulo puede tener sus propias
Plugin_Natives, interceptarPlugin_Publics y tener su propia lógicaOnProcessTick, operando como un plugin autónomo, pero cargado por un anfitrión. - Actualizaciones Dinámicas: En escenarios controlados, permite la actualización de partes de su sistema (reemplazando un
.dllo.sode módulo) sin la necesidad de recompilar y reiniciar el plugin principal o el servidor completo (aunque esto requiere una gestión de versiones y compatibilidad rigurosas).
Utilice Plugin_Call para invocar una Plugin_Native definida dentro de su propio plugin.
Plugin_Call(NombreDeLaNative, Param1, Param2, ...)- Ventaja: Evita la sobrecarga de buscar la nativa en el array de nativas de la AMX. El SDK mantiene un mapa directo de hashes de nombre a punteros de función para sus propias nativas, lo que hace de esta la forma más rápida de llamarlas internamente.
- Requiere:
SAMP_SDK_WANT_AMX_EVENTS.
// main.cpp
Plugin_Native(InternalCheckPlayerLevel, AMX* amx, cell* params) {
int playerid;
Register_Parameters(playerid);
// Lógica para verificar el nivel
return (playerid % 2 == 0) ? 1 : 0; // Ejemplo: nivel par para IDs pares
}
void Check_All_Players_Level() {
for (int i = 0; i < MAX_PLAYERS; ++i) {
if (Pawn_Native(IsPlayerConnected, i)) {
if (Plugin_Call(InternalCheckPlayerLevel, i)) // Llama a su propia nativa
Samp_SDK::Log("¡Jugador %d está en un nivel alto!", i);
}
}
}- Descripción: Imprime mensajes en la consola del servidor y en el archivo
server_log.txt. Un envoltorio seguro paralogprintf. - Uso: Para depuración, mensajes de estado y errores.
- Mecanismo: Internamente, el SDK obtiene el puntero a
logprintfa través deppData[PLUGIN_DATA_LOGPRINTF]. La función maneja el formato de la string de forma segura.
// En cualquier lugar de su plugin
Samp_SDK::Log("El plugin fue inicializado con un valor %d y una string '%s'.", 123, "prueba");- Descripción: Formatea una string de forma segura (similar a
sprintf) y devuelve unstd::string. Esta es la manera recomendada y más idiomática de formatear strings para usar dentro de su plugin. - Uso: Ideal para construir mensajes formateados antes de pasarlos a
Samp_SDK::Log,Pawn_Native(SendClientMessage, ...), o para cualquier otra necesidad de string dentro de su código C++. - Mecanismo: Internamente,
Plugin_Formates una macro que llama aSamp_SDK::Format. Utilizavsnprintfpara determinar el tamaño exacto de la string formateada y asigna unstd::stringcon capacidad suficiente, evitando desbordamientos de búfer.
int playerid = 0; // Ejemplo de ID
int health = 50;
Pawn_Native(SendClientMessage, playerid, 0xFFFFFFFF, Plugin_Format("Jugador %d, tu vida actual es %d.", playerid, health));
// También se puede usar para logs internos
Samp_SDK::Log(Plugin_Format("DEBUG: Procesando estado para el ID %d", playerid));- Descripción: La función de implementación subyacente para el formato de strings, ubicada dentro del namespace
Samp_SDK. - Uso: Generalmente no es llamada directamente por el usuario. La macro
Plugin_Formatse proporciona como una conveniencia para esta función, alineándose con la convención de nombres de otras macros del SDK (Plugin_Public,Plugin_Native). Solo la llamaría directamente si quisiera evitar la macroPlugin_Formatpor alguna razón específica.
// Ejemplo de cómo funciona Samp_SDK::Format, pero prefiera Plugin_Format
std::string raw_status = Samp_SDK::Format("Solo para uso interno: %d.", 42);- Descripción: Convierte una dirección de string de la AMX (
cell amx_addr) en unstd::stringde C++. - Uso: Principalmente dentro de
Plugin_NativeyPlugin_Native_Hookcuando necesita acceder a strings que no son convertidas automáticamente porRegister_ParametersoNative_Params(ej: si el parámetro de Pawn es unconststringy no fue declarado comostd::stringen suPlugin_NativeoPlugin_Publicpara el Marshalling automático).
Plugin_Native(PrintRawAmxString, AMX* amx, cell* params) {
Native_Params p(amx, params);
cell amx_string_addr = p.Get<cell>(0); // Obtiene la dirección de la string en la AMX
std::string cpp_string = Samp_SDK::Get_String(amx, amx_string_addr);
Samp_SDK::Log("String de la AMX: %s", cpp_string.c_str());
return 1;
}- Su plugin DEBE ser compilado para la arquitectura x86 (32-bit).
- Plataformas Soportadas: Windows (.dll) y Linux (.so).
- Cree un nuevo proyecto de "Dynamic-Link Library (DLL)".
- En las configuraciones del proyecto, defina la "Plataforma de Solución" a x86.
- Asegúrese de que el estándar del lenguaje C++ sea al menos C++17.
# Para un plugin llamado 'my_plugin.so' a partir de 'main.cpp'
g++ -m32 -shared -std=c++17 -O2 -fPIC -Wall -Wextra -Wl,--no-undefined main.cpp -o my_plugin.so-m32: Compila para 32-bit.-shared: Crea una biblioteca compartida (.so).-std=c++17: Define el estándar de C++ a C++17 (puede serc++20, pero C++17 es el mínimo).-O2: Nivel de optimización 2.-fPIC: Genera código independiente de la posición, necesario para bibliotecas compartidas.-Wall -Wextra: Activa advertencias adicionales para ayudar a detectar errores.-Wl,--no-undefined: Impide la creación de la biblioteca si hay símbolos indefinidos.
# Para un plugin llamado 'my_plugin.dll' a partir de 'main.cpp'
g++ -m32 -shared -std=c++17 -O2 -static-libstdc++ -static-libgcc -Wl,--no-undefined main.cpp -o my_plugin.dll-static-libstdc++: Enlaza la biblioteca estándar C++ estáticamente. Es esencial para evitar que su plugin dependa de DLLs de tiempo de ejecución específicas del compilador que podrían no estar presentes en el sistema del usuario.-static-libgcc: Enlaza la biblioteca GCC estáticamente.
- Nombre del Archivo: Su plugin debe tener la extensión
.dll(Windows) o.so(Linux). Ej:my_plugin.dll. - Ubicación: Coloque el archivo compilado en la carpeta
plugins/de su servidor SA-MP. - server.cfg: Agregue el nombre de su plugin (si es Windows, sin la extensión) a la línea
pluginsenserver.cfg.plugins my_plugin (si es Linux, my_plugin.so)
Copyright © AlderGrounds
Este software está licenciado bajo los términos de la Licencia MIT ("Licencia"); puede utilizar este software de acuerdo con las condiciones de la Licencia. Puede obtener una copia de la Licencia en: MIT License
La presente licencia otorga, gratuitamente, a cualquier persona que obtenga una copia de este software y archivos de documentación asociados, los siguientes derechos:
- Usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y/o vender copias del software sin restricciones
- Permitir que las personas a las que se les proporciona el software hagan lo mismo, sujeto a las siguientes condiciones
Todas las copias o partes sustanciales del software deben incluir:
- El aviso de derechos de autor anterior
- Este aviso de permiso
- El aviso de exención de responsabilidad a continuación
El software y toda la documentación asociada están protegidos por leyes de derechos de autor. La AlderGrounds mantiene la titularidad de los derechos de autor originales del software.
EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A LAS GARANTÍAS DE COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN.
EN NINGÚN CASO LOS AUTORES O TITULARES DE LOS DERECHOS DE AUTOR SERÁN RESPONSABLES DE CUALQUIER RECLAMACIÓN, DAÑOS U OTRA RESPONSABILIDAD, YA SEA EN UNA ACCIÓN DE CONTRATO, AGRAVIO O DE OTRO MODO, QUE SURJA DE, FUERA DE O EN CONEXIÓN CON EL SOFTWARE O EL USO U OTROS TRATOS EN EL SOFTWARE.
Para información detallada sobre la Licencia MIT, consulte: https://opensource.org/licenses/MIT