Requerimientos previos:
- Dynamic Natives
- Arrays
- Manejo de menús
- Forwards (No encontré un buen tuto de esto así que lo explico aquí)
Opcional:
- Tries
- Bitsums
El objetivo de este tuto es poder crear un plugin que realice algunas funciones y se complemente con otros plugins que llamaremos "subplugins", a este plugin se le llamará Plugin API.
Tenemos 2 formas de comunicar plugins a través del entorno virtual: Forwards y Natives.
Explicaré lo que es una forward, con un ejemplo:
Como vemos, esta es una función que se llama en todos los plugins que la tengan como "public" y la tengan registrada como una forward.
Si revisamos amxmodx.inc encontraremos esto:
Es aquí donde se indica que es una forward.
Una forward es una función específica que se llama en todos los plugins que la contengan como una función pública y registrada como forward.
Otro ejemplo: zp_extra_item_selected (forward que se llama cuando se compra un extra item en ZP).
Para explicar mejor cómo hacer una API con forwards y natives, haré un ejemplo con una tienda, cuyos artículos se registran a través de subplugins.
1. Creando la tienda con hardcode.
Es una tienda bastante simple, con los items hardcodeados.
2. Siguente paso: que se puedan editar los nombres de los ítems sin tocar el menú.
Pero me dirán... y esto para qué sirve? Si aún hay hardcode en el handler del menú. Pues sí, no sirve de mucho, es aquí donde entra la utilidad de un plugin API.
3. Ahora haré que cada ítem se registre con una native, y cuando el ítem sea elegido se comunique al subplugin que registró el ítem a través de una forward. De esta forma el subplugin manejará lo que sea que se haya comprado.
Listo! Ya está, una tienda API completamente funcional.
4. Ahora veremos los subplugins. Anteriormente expliqué que para que funcione una forward se debe indicar como tal (como se hace en amxmodx.inc con client_putinserver). Para esto, crearemos una librería (include) para nuestra API.
5. Crearemos un subplugin. Asumiendo que guardamos la librería con el nombre "tienda.inc". Este subplugin será de un ítem: +100 HP.
Y eso es todo! Consulten cualquier duda, estoy para responder.
- Dynamic Natives
- Arrays
- Manejo de menús
- Forwards (No encontré un buen tuto de esto así que lo explico aquí)
Opcional:
- Tries
- Bitsums
El objetivo de este tuto es poder crear un plugin que realice algunas funciones y se complemente con otros plugins que llamaremos "subplugins", a este plugin se le llamará Plugin API.
Tenemos 2 formas de comunicar plugins a través del entorno virtual: Forwards y Natives.
Explicaré lo que es una forward, con un ejemplo:
Código PHP:
public client_putinserver(id)
Si revisamos amxmodx.inc encontraremos esto:
Código PHP:
/* Called when client is entering to a game. */
forward client_putinserver(id);
Una forward es una función específica que se llama en todos los plugins que la contengan como una función pública y registrada como forward.
Otro ejemplo: zp_extra_item_selected (forward que se llama cuando se compra un extra item en ZP).
Para explicar mejor cómo hacer una API con forwards y natives, haré un ejemplo con una tienda, cuyos artículos se registran a través de subplugins.
1. Creando la tienda con hardcode.
Código PHP:
#include <amxmodx>
#include <cstrike>
#include <fun>
new g_callback;
public plugin_init()
{
register_plugin("Test Shop", "0.1", "Cajita De Leshe");
register_clcmd("say /shop", "clcmd_shop");
g_callback = menu_makecallback("menu_shop_cb");
}
public clcmd_shop(id)
{
new menu = menu_create("Tienda", "menu_shop");
menu_additem(menu, "Ak-47", .callback = g_callback);
menu_additem(menu, "M4a1", .callback = g_callback);
menu_additem(menu, "200 HP", .callback = g_callback);
menu_display(id, menu);
}
public menu_shop_cb(id, menu, item)
{
if (!is_user_alive(id))
return ITEM_DISABLED;
return ITEM_ENABLED;
}
public menu_shop(id, menu, item)
{
if (!is_user_connected(id) || !is_user_alive(id))
{
menu_destroy(menu);
return PLUGIN_HANDLED;
}
switch (item)
{
case 0: give_item(id, "weapon_ak47");
case 1: give_item(id, "weapon_m4a1");
case 2: set_user_health(id, 200);
}
menu_destroy(menu);
return PLUGIN_HANDLED;
}
2. Siguente paso: que se puedan editar los nombres de los ítems sin tocar el menú.
Código PHP:
#include <amxmodx>
#include <cstrike>
#include <fun>
new g_callback;
new const ITEMS[][] = { "AK47", "M4A1", "200 HP" }; // Ahora los items los tengo en una matriz constante.
public plugin_init()
{
register_plugin("Test Shop", "0.1", "Cajita De Leshe");
register_clcmd("say /shop", "clcmd_shop");
g_callback = menu_makecallback("menu_shop_cb");
}
public clcmd_shop(id)
{
new menu = menu_create("Tienda", "menu_shop");
for (new i = 0; i < 3; i++)
menu_additem(menu, ITEMS[i], .callback = g_callback);
menu_display(id, menu);
}
public menu_shop_cb(id, menu, item)
{
if (!is_user_alive(id))
return ITEM_DISABLED;
return ITEM_ENABLED;
}
public menu_shop(id, menu, item)
{
if (!is_user_connected(id) || !is_user_alive(id))
{
menu_destroy(menu);
return PLUGIN_HANDLED;
}
switch (item)
{
case 0: give_item(id, "weapon_ak47");
case 1: give_item(id, "weapon_m4a1");
case 2: set_user_health(id, 200);
}
menu_destroy(menu);
return PLUGIN_HANDLED;
}
3. Ahora haré que cada ítem se registre con una native, y cuando el ítem sea elegido se comunique al subplugin que registró el ítem a través de una forward. De esta forma el subplugin manejará lo que sea que se haya comprado.
Código PHP:
#include <amxmodx>
#include <cstrike>
new g_callback;
new g_items_count; // Una variable para contar los ítems registrados
new Array:g_item_name, Array:g_item_cost; // Arrays dinámicas para los nombres y costos de los ítems.
new forward_itemselected; // Una variable para almacenar el id de la forward.
public plugin_natives()
{
// Una native para registrar ítems en la tienda.
register_native("tienda_registrar", "native_register", 0);
}
public plugin_init()
{
register_plugin("Test Shop", "0.1", "Cajita De Leshe");
register_clcmd("say /shop", "clcmd_shop");
g_callback = menu_makecallback("menu_shop_cb");
// Esta será la forward que se llamará cuando se seleccione un ítem.
forward_itemselected = CreateMultiForward("tienda_item_seleccionado", ET_STOP, FP_CELL, FP_CELL);
// ET_STOP: Parámetro que indica detener la llamada si algún plugin retorna PLUGIN_HANDLED.
// FP_CELL: Significa que se enviará una celda de dato. Ej, el índice de un jugador es una celda.
// En este caso puse 2, 1 para el id del jugador, otra para el id del ítem seleccionado.
// Arrays dinámicas
g_item_name = ArrayCreate(32); // 32 celdas debe ser suficiente...
g_item_cost = ArrayCreate();
}
// Aclaro: uso el estilo 0 de las natives porque me parece más cómodo
// Usar el estilo 0 o 1 es a gusto del cliente.
public native_register(plugin, params)
{
// La native tendrá esta estructura:
// tienda_registrar(nombre[], precio)
new szNombre[32];
get_string(1, szNombre, charsmax(szNombre));
ArrayPushString(g_item_name, szNombre); // Guardamos el nombre
ArrayPushCell(g_item_cost, get_param(2)); // Guardamos el costo
g_items_count++; // Incrementamos en 1 el contador de ítems.
return g_items_count; // Retornamos el contador que servirá como un ID para el ítem.
// Un detalle: Al usar ArrayGetString/Cell, usaremos un número que es 1 menos que el ID del ítem.
// ¿Por qué? Porque no es apropiado enviar un ID 0, por eso el primer ID será 1 y le corresponde el número 0 en las arrays dinámicas.
}
public clcmd_shop(id)
{
new menu = menu_create("Tienda", "menu_shop");
new szItem[50], len, bool:canbuy, cost;
for (new i = 0; i < g_items_count; i++)
{
// Agregamos el costo
cost = ArrayGetCell(g_item_cost, i);
canbuy = (cs_get_user_money(id) >= cost) ? true : false;
len = formatex(szItem, charsmax(szItem), "\%c[$%d]\%c ", canbuy ? 'y' : 'r', cost, canbuy ? 'w' : 'd');
// Ahora el nombre del item
ArrayGetString(g_item_name, i, szItem[len], charsmax(szItem)-len);
menu_additem(menu, szItem, .callback = canbuy ? -1 : g_callback/*Si no puede comprar, llamamos al callback para desactivar el ítem*/);
}
// Esto es solo estético
menu_setprop(menu, MPROP_EXITNAME, "\ySalir");
menu_setprop(menu, MPROP_BACKNAME, "Anterior");
menu_setprop(menu, MPROP_NEXTNAME, "Siguiente");
menu_display(id, menu);
}
public menu_shop_cb(id, menu, item)
{
// Este callback sólo se llama cuando no alcanza el dinero...
return ITEM_DISABLED;
}
public menu_shop(id, menu, item)
{
if (!is_user_connected(id) || !(0 <= item < g_items_count))
{
menu_destroy(menu);
return PLUGIN_HANDLED;
}
// Primero comprobamos que el usuario tenga dinero suficiente.
new cost = ArrayGetCell(g_item_cost, item);
new money = cs_get_user_money(id);
if (money >= cost)
{
// Estamos listos, ahora avisaremos al subplugin que se quiere comprar el ítem
// Aquí entra el trabajo de las forwards.
new ret; // Una variable para almacenar el valor de retorno
ExecuteForward(forward_itemselected, ret, id, item+1);
// Como explique anteriormente, el ID del ítem es 1 unidad más que el número en los arrays y el menú.
// Supongamos que tengo un subplugin que da un arma, pero qué pasa si lo compro mientras estoy muerto (por imbécil o por accidente).
// El subplugin debe comprobar si el jugador está en condición de comprar el ítem.
// Para eso es el valor de retorno, si el subplugin comprueba que no puedo comprar, debe retornar PLUGIN_HANDLED.
if (ret == PLUGIN_HANDLED) // No puede comprar
client_print(id, print_chat, "No puedes comprarlo ahora.");
else // Se ha entregado el ítem, a cobrar!
{
new szItemName[32];
ArrayGetString(g_item_name, item, szItemName, charsmax(szItemName));
client_print(id, print_chat, "Has comprado: %s", szItemName);
cs_set_user_money(id, money-cost);
}
}
else
client_print(id, print_chat, "No tienes el dinero suficiente.");
menu_destroy(menu);
return PLUGIN_HANDLED;
}
4. Ahora veremos los subplugins. Anteriormente expliqué que para que funcione una forward se debe indicar como tal (como se hace en amxmodx.inc con client_putinserver). Para esto, crearemos una librería (include) para nuestra API.
Código PHP:
#if defined _shop_api_included // Si ya se definio _shop_api_included
#endinput // Aqui terminamos, lo que sigue en el .inc no se agregara al plugin.
#endif
#define _shop_api_included // Definimos _shop_api_included
// Lo de arriba sirve para evitar líos si pones #include <libreria> 2 veces o más.
// Bastante simple, registré una native y una forward
// Aquí indico su estructura a los plugins que tengan esta librería incluida
// La native para registrar un ítem.
native tienda_registrar(const nombre[], precio);
// La forward que se llama cuando un jugador elige un ítem.
forward tienda_item_seleccionado(id, item_id);
5. Crearemos un subplugin. Asumiendo que guardamos la librería con el nombre "tienda.inc". Este subplugin será de un ítem: +100 HP.
Código PHP:
#include <amxmodx>
#include <fun>
#include <tienda> // La librería de nuestra API
new g_item; // Variable para almacenar el id del ítem.
public plugin_init()
{
register_plugin("[Tienda] +100 HP", "0.1", "Cajita De Leshe");
// Recordemos la native: va el nombre y el costo, retorna el id del item.
g_item = tienda_registrar("+100 HP", 700);
}
// Esta es la forward que se va a llamar cuando un ítem es seleccionado.
public tienda_item_seleccionado(id, item_id)
{
// La forward nos pasa el ID del ítem seleccionado.
// Si el ID no es el ID de este ítem, entonces este subplugin no tiene nada que hacer.
if (item_id != g_item)
return PLUGIN_CONTINUE; // Plugin continue: continua la forward.
// Si llegamos aquí, entonces el ítem es el de este subplugin.
// Cada subplugin pone sus reglas
// En este caso, este ítem es sólo para vivos!
if (!is_user_alive(id))
return PLUGIN_HANDLED; // Como pusimos en el API: PLUGIN_HANDLED = No se compró.
// Si llegamos aquí, felicidades! Has comprado un ítem!
set_user_health(id, get_user_health(id)+100); // Aquí entregamos el ítem, en este caso +100 HP.
return PLUGIN_CONTINUE;
}
Y eso es todo! Consulten cualquier duda, estoy para responder.