Dynamic Items in Menu and Plugin API

For scripting examples for learning how to use AMXX.
User avatar
Archive-Poster
Posts: 1229
Gender:
United States of America

Dynamic Items in Menu and Plugin API

Post by Archive-Poster »

Introduction:

I've seen many users ask how to create dynamic classes or items with sub-plugins like in Zombie Plague.
This tutorial will teach you just how to do that.

This tutorial is not for new users to AMXX scripting!

In this tutorial, you will need knowledge of:
new menu style
cellarrays
data structures (enum)
callfunc_* natives (aren't used in this but could be for more advanced API)
creating custom forwards
creating dynamic natives

For this tutorial, I'm going to use a "shop" for an example.
It will show a menu on spawn which contains the items.

Integration File (.inc):

Let's call it shop.inc:

Code: Select all

#if defined _shop_included
    #endinput
#endif

#define _shop_included
That code will make sure if included more than once in a single plugin, it won't duplicate its contents.

First, you will need a custom library for this.
Let's call it "shop"

Code: Select all

#if AMXX_VERSION_NUM >= 175
    #pragma reqlib shop
    #if !defined AMXMODX_NOAUTOLOAD
        #pragma loadlib shop
    #endif
#else
    #pragma library shop
#endif
Next, you will need 2 functions that will integrate your plugins:
A function executed in the sub-plugin that sends data to the main plugin so the item is recognized.
A forward executed from the main plugin and is hooked in the sub-plugin to know when the item is selected.

If the sub-plugin is going to be an item, then you will need to pass all the information about the item into the function.
To keep it simple, we will only need:
item name
item cost

So, here is how our function would look.

Code: Select all

native shop_item_add( const szName[ ], const iCost );
The function should return a unique item index so it can be separate from other plugins.
In the forward when the player selects the item, it will need to pass the player's index and the unique item index.

So, here is how the forward would look.

Code: Select all

forward shop_item_selected( iPlayer, iItemIndex );
Here is our complete shop.inc file:

Code: Select all

#if defined _shop_included
    #endinput
#endif

#define _shop_included

#if AMXX_VERSION_NUM >= 175
    #pragma reqlib shop
    #if !defined AMXMODX_NOAUTOLOAD
        #pragma loadlib shop
    #endif
#else
    #pragma library shop
#endif

native shop_item_add( const szName[ ], const iCost );

forward shop_item_selected( iPlayer, iItemIndex );
Main Plugin:

I commented the plugin very well.
If you have knowledge that was listed above, you should be able to understand everything with just the comments.

Code: Select all

#include < amxmodx >
#include < cstrike >
#include < hamsandwich >

// create the structure for each item
// we need the item name and cost
enum _:ItemData
{
    ItemName[ 32 ],
    ItemCost
};

// create a dynamic array to hold all the items
new Array:g_aItems;

// this will tell how many are in the array instead of using ArraySize()
new g_iTotalItems;

// create a forward for when player selects an item
new g_hSelectItemForward;

public plugin_init( )
{
    // register player spawn to show the shop menu
    RegisterHam( Ham_Spawn, "player", "FwdPlayerSpawnPost", 1 );
    
    // create our array with the size of the item structure
    g_aItems = ArrayCreate( ItemData );
    
    // create our forward
    g_hSelectItemForward = CreateMultiForward( "shop_item_selected", ET_IGNORE, FP_CELL, FP_CELL );
}

public plugin_natives( )
{
    // register our custom library
    register_library( "shop" );
    
    // create a native to allow other plugins to add items
    register_native( "shop_item_add", "_item_add" );
}

public _item_add( iPlugin, iParams )
{
    // create an array to hold our item data
    new eItemData[ ItemData ];
    
    // get item name from function
    get_string( 1, eItemData[ ItemName ], charsmax( eItemData[ ItemName ] ) );
    
    // get item cost from function
    eItemData[ ItemCost ] = get_param( 2 );
    
    // add item to array and increase size
    ArrayPushArray( g_aItems, eItemData );
    g_iTotalItems++;
    
    // return the index of this item in the array
    // this creates the unique item index
    return ( g_iTotalItems - 1 );
}

public FwdPlayerSpawnPost( iPlayer )
{
    // check if player spawned properly
    if( is_user_alive( iPlayer ) )
    {
        // show the shop menu starting with the first page
        // don't understand this function call?
        // <a href="http://forums.alliedmods.net/showthread.php?t=90480" target="_blank" rel="nofollow noopener">http://forums.alliedmods.net/showthread.php?t=90480</a>
        ShowShopMenu( iPlayer, .iPage = 0 );
    }
}

ShowShopMenu( iPlayer, iPage )
{
    // check if there are no items
    if( !g_iTotalItems )
    {
        return;
    }
    
    // clamp page to valid range of pages
    iPage = clamp( iPage, 0, ( g_iTotalItems - 1 ) / 7 );
    
    // create menu
    new hMenu = menu_create( "Shop Menu", "MenuShop" );
    
    // used to display item in the menu
    new eItemData[ ItemData ];
    new szItem[ 64 ];
    
    // used for array index to menu
    new szNum[ 3 ];
    
    // loop through each item
    for( new i = 0; i < g_iTotalItems; i++ )
    {
        // get item data from array
        ArrayGetArray( g_aItems, i, eItemData );
        
        // format item for menu
        formatex( szItem, charsmax( szItem ), "%s\R\y%i", eItemData[ ItemName ], eItemData[ ItemCost ] );
        
        // pass array index to menu to find information about it later
        num_to_str( i, szNum, charsmax( szNum ) );
        
        // add item to menu
        menu_additem( hMenu, szItem, szNum );
    }
    
    // display menu to player
    menu_display( iPlayer, hMenu, iPage );
}

public MenuShop( iPlayer, hMenu, iItem )
{
    if( iItem == MENU_EXIT )
    {
        menu_destroy( hMenu );
        return;
    }
    
    new iAccess, szNum[ 3 ], hCallback;
    menu_item_getinfo( hMenu, iItem, iAccess, szNum, charsmax( szNum ), _, _, hCallback );
    menu_destroy( hMenu );
    
    // get item index from menu
    new iItemIndex = str_to_num( szNum );
    
    // get item data from array
    new eItemData[ ItemData ];
    ArrayGetArray( g_aItems, iItemIndex, eItemData );
    
    // get player money and subtract the cost
    new iMoney = cs_get_user_money( iPlayer ) - eItemData[ ItemCost ];
    
    // result money will be < 0 if not enough money
    if( iMoney < 0 )
    {
        // notify player
        client_print( iPlayer, print_chat, "* You need $%i more to buy this item!", ( iMoney * -1 ) );
    }
    else
    {
        // set player money
        cs_set_user_money( iPlayer, iMoney );
        
        // notify plugins that the player bought this item
        new iReturn;
        ExecuteForward( g_hSelectItemForward, iReturn, iPlayer, iItemIndex );
    }
    
    ShowShopMenu( iPlayer, .iPage = ( iItemIndex / 7 ) );
}
Sub-Plugin:

Code: Select all

#include < amxmodx >
#include < fun >
#include < shop >

// save item index
new g_iItemIndex;

public plugin_init( )
{
    // register item with main plugin and store item index
    g_iItemIndex = shop_item_add( "Low Gravity", 2000 );
}

// catch when items are bought
public shop_item_selected( iPlayer, iItemIndex )
{
    // check if bought item is the one from this plugin
    if( g_iItemIndex == iItemIndex )
    {
        // set gravity
        set_user_gravity( iPlayer, 0.25 );
    }
}

Who is online

Users browsing this forum: No registered users and 1 guest