AMX Mod X 1.70 Scripting Changes

From AlliedModders Wiki
Revision as of 01:49, 1 March 2006 by BAILOPAN (talk | contribs)
Jump to: navigation, search

This article goes over the new scripting changes available in AMX Mod X version 1.70.

New Systems

These are entirely new categories of functions added to AMX Mod X in 1.70.

CVAR Pointers

CVAR pointers are a new method of getting/retrieving CVAR values. It is a much faster method than using the get/set_cvar functions. The difference is instead of passing a "cvar name", you pass the "cvar pointer", which is a direct memory access rather than a string lookup. In order to easily accomodate this, register_cvar now returns a CVAR pointer from the CVAR you created. You can also use get_cvar_pointer. Usage is mapped as such:

  • get_cvar_flags -> get_pcvar_flags
  • set_cvar_flags -> set_pcvar_flags
  • get_cvar_string -> get_pcvar_string
  • set_cvar_string -> NONE (not implemented)
  • get_cvar_num -> get_pcvar_num
  • set_cvar_num -> set_pcvar_num
  • get_cvar_float -> get_pcvar_float

An example of old code might be:

#include <amxmodx>
 
public plugin_init()
{
   register_cvar("csdm_active", "1")
}
 
public pfn_touch()
{
   if (!get_cvar_num("csdm_active"))
      return PLUGIN_CONTINUE
}

To use CVAR pointers and optimize your code:

#include <amxmodx>
 
new g_csdm_active
 
public plugin_init()
{
   g_csdm_active = register_cvar("csdm_active", "1")
}
 
public pfn_touch()
{
   if (!get_pcvar_num(g_csdm_active))
      return PLUGIN_CONTINUE
}

Event Forwarding

One of AMX Mod X's first advanced API additions was "callfunc", which let plugins intercommunicate. This expanded with the "module forward API", then to "dynamic natives". Now, AMX Mod X has a "plugin forward API". This lets you call a public function in all plugins at once, which is very useful for global event/messaging creation. The new natives are:

  • CreateMultiForward - Creates a global forward to a public function in all plugins.
  • CreateOneForward - Creates a single, per-plugin forward to a public function.
  • PrepareArray - Prepares an array to be passed into a forward.
  • ExecuteForward - Executes a forward, calling all the public functions it contains.
  • DestroyForward - Removes a forward from memory.

An example of creating a forward is below. This plugin creates the forward, and fires it when the command is typed.

#include <amxmodx>
 
new g_fwd_what
 
public plugin_init()
{
    register_plugin("forward test", "1.0", "BAILOPAN")
    register_srvcmd("fwd_test", "Command_FwdTest")
 
    g_fwd_what = CreateMultiForward("Event_What", ET_STOP, FP_STRING, FP_CELL, FP_ARRAY)
}
 
public Event_What(str[], d, arr[])
{
    server_print("Event what called with (%s) num = %d|%d (should be 5|37)", str, d, arr[d])
 
    return PLUGIN_CONTINUE
}
 
public Command_FwdTest()
{
    new array[6], ret
    array[5] = 37
 
    new pArray = PrepareArray(array, 6)
    if (!ExecuteForward(g_fwd_what, ret, "gaben", 5, pArray))
    {
        server_print("FAILED!")
    }
}

Fast File Natives

One of the biggest flaws in the original AMX Mod core is the tendency to replace direct access handles with bad abstraction layers. Write_file and read_file are examples of this. To read or write line N to a file, they must open the file, seek to line N, then close it. For reading sequentially, this is an O(n^2) operation -- very slow!

These have been made obsolete with:

  • fopen - Opens and returns a file handle on success.
  • fclose - Closes a file handle.
  • feof - Checks for end of file.
  • fprintf - Writes to a file.
  • fgets - Reads text from a file.
  • fseek - Seek to a position in a file.
  • ftell - Get the current position of a file.
  • fread/fread_blocks/fread_raw - Read binary data from a file.
  • fwrite/fwrite_blocks/fwrite_raw - Write binary data to a file.

An example of reading a file with the new system:

#include <file>
 
stock CopyTextFile(const infile[], const outfile[])
{
   new infp = fopen(infile, "rt")    //read text
   new outfp = fopen(outfile, "wt")  //write text
 
   if (!infp || !outfp)
      return 0
 
   new buffer[2048]
   while (!feof(infp))
   {
      fgets(infp, buffer, 2047)
      fprintf(outfp, "%s", buffer)
   }
   fclose(infp)
   fclose(outfp)
}

Note that fgets includes a newline if one is reached. A quick way to strip newlines is:

new len = strlen(string)
if (string[len-1] == '^n')
   string[--len] = 0


Upgraded Systems

A few systems in AMX Mod X 1.70 were either slightly or completely overhauled.

New Menus

The major changes included in the "new newmenu" system:

  • menu_setprop - You can now set per-menu properties.
    • MPROP_PERPAGE - Sets the level of pagination.
    • MPROP_BACKNAME - Sets the text for the "Back" button.
    • MPROP_NEXTNAME - Sets the text for the "More" button.
    • MPROP_EXITNAME - Sets the text for the "Exit" button.
    • MPROP_TITLE - Sets the menu title.
    • MPROP_EXIT - Controls how "Exit" buttons are displayed.
    • MPROP_ORDER - Sets menu item order.
    • MPROP_NOCOLORS - Forces no colors.
    • MPROP_PADMENU - Sets how items/spaces are padded.
  • menu_destroy - Menus are now destroyable.
  • Menus calculate items correctly.
  • Menu callbacks no longer send MENU_BACK or MENU_MORE, it is handled internally.
  • Menu callbacks will received MENU_EXIT if the player disconnects or is sent another menu.
  • Menu callbacks are guaranteed to be called in any situation (so you can destroy them).
  • menu_addblank - Menus can have blank entries.
  • player_menu_info - Newmenu synchronization.

An example of using destroyable, per-player newmenus is below:

#include <amxmodx>
 
public plugin_init()
{
    register_plugin("forward test", "1.0", "BAILOPAN"); 
    register_clcmd("menu_test", "Command_MenuTest");  
}
 
public gaben_handler(id, menu, item)
{
    client_print(0, print_chat, "Player %d selected menu %d item %d", id, menu, item);
    if (item == MENU_EXIT)
    {
        //Note that you must remember to destroy the menu here, since 
        // you won't hit the one below.
        menu_destroy(menu);
        return PLUGIN_HANDLED;
    }
 
    new cmd[6], iName[64];
    new access, callback;
 
    menu_item_getinfo(menu, item, access, cmd,5, iName, 63, callback);
 
    client_print(0, print_chat, " ---> Evaluated to: %s[%s]", iName, cmd);
 
    menu_destroy(menu);
 
    return PLUGIN_HANDLED;
}
 
public Command_MenuTest(id)
{
    new menu = menu_create("Gaben?", "gaben_handler");
    menu_additem(menu, "1", "1", 0);
    menu_addblank(menu);
    menu_additem(menu, "", "2", 0);
    menu_additem(menu, "3", "3", 0);
    menu_additem(menu, "4", "4", 0);
    menu_additem(menu, "6", "6", 0);
    menu_additem(menu, "7", "7", 0);
    menu_additem(menu, "8", "8", 0);
    menu_additem(menu, "9", "9", 0);
    menu_setprop(menu, MPROP_PERPAGE, 3);
    menu_setprop(menu, MPROP_EXIT, MEXIT_ALL);
    menu_setprop(menu, MPROP_NEXTNAME, "Next");
 
    menu_display(id, menu, 0);
}

HUD Text Channels

AMX Mod X 1.70 features automated channeling of HUD messages and HUD synchronization. This was created to solve the problem of a big plugin, like StatsX, uses all four HUD channels in a hardcoded manner.

To use this API, you can pass "-1" as the channel to set_hudmessage. Or, if you need to cache the automatically chosen HUD channel for future use, you can use next_hudchannel(player), which will return a given player's next available HUD channel. The selection algorithm is "least recently used" (LRU).

However, another problem emerges with this. Imagine the situation where:

  • Plugin A uses the new auto-channeling system. It places a message in the top-left corner on channel X.
  • Plugin B uses hardcoded channels. It places a message on the bottom corner on channel X.
  • Plugin A wants to display a new message on the same coordinates as the last one. It must first clear channel X or risk having messages that overlap. Plugin A has two choices:
    • Ignore them problem and just let them potentially overlap.
    • Clear channel X, and risk overwriting a valid message that is no longer overlapping.

In order to solve this weird race condition, "HUD Sync Objects" were created. These will automatically use the auto-channeling system and take care of clearing potentially colliding areas for. The rule of thumb is to check how many overlapping areas of the screen you have. For example, if you have three messages which display in the top, and four which display in the bottom, you should use two HUD Sync Objects to make sure that when one message is displayed, you don't overwrite an invalid one or clear a valid one.

An example of this is below:

new g_MyMsgSync
 
public plugin_init()
{
    register_clcmd("/show", "showMsg")
    register_clcmd("/what", "whatMsg")
 
    new g_MyMsgSync = CreateHudSyncObj()
}
 
public showMsg(id)
{
    //last parameter is not needed
    set_hudmessage(0, 255, 255, -1.0, 0.35, 0, 6.0, 6.0, 0.5, 0.15)
    //use this instead of show_hudmessage
    ShowSyncHudMsg(id, g_MyMsgSync, "You have been shown a message!")
}
 
public whatMsg(id)
{
    set_hudmessage(0, 255, 255, -1.0, 0.30, 0, 6.0, 6.0, 0.5, 0.15)
    ShowSyncHudMsg(id, g_MyMsgSync, "what....")
}

The player can type these two commands and the messages, which would normally overlap without messy synchronization, are automatically cleared for you as necessary.

New Natives