AMX Mod X 1.70 Scripting Changes

From AlliedModders Wiki
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
  • set_cvar_float -> set_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:

#include <amxmodx>
 
new g_MyMsgSync
 
public plugin_init()
{
    register_clcmd("/show", "showMsg")
    register_clcmd("/what", "whatMsg")
 
    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 / Native Changes

format/formatex

Format() and formatex() are now completely rewritten for speed. The important difference between the two functions lies within how things are copied for efficiency. The first parameter to "format" is the "output buffer". All other parameters are "source inputs".

  • If format() detects that any single source input lies within the same memory as the destination output, it will revert to a slower, "copy-back" version.
  • If format() detects that no source inputs collide with the output buffer, it will call formatex() instead, which is faster.
  • formatex() eliminates the check of format() for a tiny bit of extra speed. It is recommended to use this if you know your copying is safe.

For example:

new buffer[255]
new output[255]
new num = 7
 
//Fast format
format(buffer, 254, "This is a fast format %%d %d", 7)
//Slower format - buffer is the same as buffer
format(buffer, 254, buffer, 7)
//Slower format - buffer[4] lies within buffer
format(buffer, 254, "%s", buffer[4])
 
//Fastest format
formatex(buffer, 254, "This is a valid format: %d", 7)
//Invalid format - garbage string, since boundary collision is unchecked
formatex(buffer, 254, "%s", buffer)

Other Core Natives

  • set_fail_state - "Unload" your plugin forcibly and irreversibly.

Fun

  • get_user_footsteps - Complements set_user_footsteps.

Natural Selection

  • ns_get_hive_ability - Gets hive abilities.

MySQL/DBI

  • dbi_query2 - Returns the number of rows affected, if any.

Fakemeta

  • unregister_forward - Unregisters a forward so plugins can unhook hot/expensive events.
  • pev_valid - Complements is_valid_ent() from Engine.

Sockets

  • socket_send2 - Sends binary data, rather than stopping at a NULL byte.

New Stocks

Core

  • get_time_length - Returns an intelligent, multi-lingual, stringized difference of two times. (Brad)
  • replace_all - Replaces all occurences of one string inside another. (jtp10181)
  • split - Like strbreak except for matching multiple tokens. (Suicid3)
  • remove_filepath - Strips a file of its hard path.


Module API

Player Properties

Because modules often change things that Core doesn't know about, it became necessary to abstract Core's internal variable states for players. MF_GetPlayerPropAddr() takes in a player index and a property, and returns an address to that property. It should be noted that these addresses are raw, and when modified, must correspond exactly to the same data type that Core expects.

  • Player_Name (String, CString.h) - Player's name.
  • Player_Ip (String, CString.h) - Player's IP address.
  • Player_Team (String, Cstring.h) - Player's Team Name.
  • Player_Ingame (bool) - Whether a player is ingame.
  • Player_Authorized (bool) - Whether a player is authorized.
  • Player_Vgui (bool) - Whether a player has a VGUI menu.
  • Player_Time (float) - Connected time.
  • Player_Playtime (float) - In-game time.
  • Player_MenuExpire (float) - Time when menu expires.
  • Player_Weapons (struct{int,int}[32]) - Weapon id -> ammo,clip mappings.
  • Player_CurrentWeapon (int) - Current weapon id.
  • Player_TeamID (int) - Team ID.
  • Player_Deaths (int) - Death count.
  • Player_Aiming (int) - Aim target.
  • Player_Menu (int) - Old menu index.
  • Player_Keys (int) - Pressed menu keys.
  • Player_Flags (int[32]) - Array of flags.
  • Player_Newmenu (int) - New menu index.
  • Player_NewmenuPage (int) - New menu page number.

A short example:

int *deaths = static_cast<int *>(MF_GetPlayerPropAddr(id, Player_Deaths));
*deaths = 0;

Authorization Callbacks

You can also hook "Client_Authorized", a custom and often redundantly coded event provided by the AMX Mod X core. This was not available to modules before 1.70. Example:

void OnPlayerAuthorized(int player, const char *authid)
{
   ///CODE HERE
}
 
void OnAmxxAttach()
{
   MF_RegAuthFunc(OnPlayerAuthorized);
}
 
void OnAmxxDetach()
{
   MF_UnregAuthFunc(OnPlayerAuthorized);
}

The unregister version is provided for plugins which are not Metamod enabled, and thus can be removed at mapchange. The callbacks will otherwise be called whenever a player or bot is deemed to have a valid SteamID.


Compiler Changes

  • You can no longer pass the wrong number of arguments to a function and have it pass off as a warning.
  • Scripters can now use the __DATE__ macro to insert a string of the date at compile time. this didn't make it into the final correctly -- BAILOPAN 19:26, 4 March 2006 (EST)