• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Plugin] PawnMap - a plugin for creating maps in Pawn
#1
PawnMap - a plugin for creating maps in Pawn

Main goal: to make a map for Pawn that is as clear and performant as possible.
So it could be used in regular systems, without significantly falling behind raw Pawn arrays, and where some data operation is needed (search, sorting), it would even be faster.

All detailed documentation with examples is in my repository >> https://github.com/i-Saibot/PawnMap/wiki
Here I'll just briefly show the list of functions and a couple of examples:

Functions
Code:
Map_Create - Creates a new map instance.
Map_Destroy - Deletes a map instance.
Map_IsValid - Checks whether the map exists in memory.
Map_Clear - Completely clears the map.
Map_Clone - Creates a full copy of an existing map.
Map_Merge - Merges two maps.
Map_Set - Sets a set of values for the specified key.
Map_Get - Retrieves all data stored under the specified key.
Map_RemoveKey - Removes the specified key.
Map_SafeRemoveKey - Safely removes the specified key inside a loop.
Map_RenameKey - Changes the identifier of an existing key to a new one.
Map_Swap - Swaps the data of two specified keys.
Map_ContainsKey - Checks whether the specified key exists in the map.
Map_GetKeyByIndex - Gets the key value by its index.
Map_GetKeyCount - Gets the number of keys in the map.
Map_FindKeyByField - Searches for a key by its value.
Map_SortByKey - Sorts all keys in the map according to the selected order.
Map_SortByField - Sorts entries in the map based on the values of an enum field.
Map_SetString - Helper function for safely writing a string into an array.
mapfor - A loop for iterating over keys in a map.
Map_StringKeyToIntor - Converts a string key to an int for the map.
Map_GetIdByStringKey - Finds and returns the integer (ID) of an existing string key in the map.
Map_ContainsStringKey - Checks whether an entry with the specified string key exists in the map.
Map_GetStringById - Gets the string name of a key by its numeric identifier (ID).


Dm Zone
Let's say we need to make a DM arena where we'll track the number of kills, deaths, and damage.
At the end of the round we need to sort the list, print the data, and clear the map for the next round.
In this map we use playerid as the key.

Code:
enum e_dm_zone
{
    Kills,
    Death,
    Float:Damage
}
new PawnMap:MapDmZone;

public OnGameModeInit()
{
    // Create the map on mode start
    MapDmZone = Map_Create();
    return 1;
}

public OnGameModeExit()
{
    // Destroy the map
    Map_Destroy(MapDmZone);
    return 1;
}

public OnPlayerTakeDamage(playerid, issuerid, Float:amount, weaponid, bodypart)
{
    // Check whether the player is in the DM zone

    static data[e_dm_zone];

    // Get the player's current data to update it
    Map_Get(MapDmZone, playerid, data);

    data[Damage] += amount;

    // Update only the damage
    Map_Set(MapDmZone, playerid, data);
    return 1;
}

public OnPlayerDeath(playerid, killerid, reason)
{
    // Check whether the player is in the DM zone

    static data[e_dm_zone];

    // Update deaths (Death)
    Map_Get(MapDmZone, playerid, data);
    data[Death] += 1;
    Map_Set(MapDmZone, playerid, data);

    // Update kills (Kill)
    if(killerid != INVALID_PLAYER_ID)
    {
        Map_Get(MapDmZone, killerid, data);
        data[Kills] += 1;
        Map_Set(MapDmZone, killerid, data);
    }
    return 1;
}

stock DmZoneRoundFinish()
{
    // Sort in descending order (players with more kills first)
    Map_SortByField(MapDmZone, e_dm_zone:Kills, MAP_SORT_DESC);

    static data[e_dm_zone];
    new string[144];

    mapfor(MapDmZone, i)
    {
        Map_Get(MapDmZone, i, data);

        format(string, sizeof(string),
            "[DM ZONE] playerid %d | kills %d | death %d | damage %.2f",
            i,
            data[Kills],
            data[Death],
            data[Damage]
        );
        SendClientMessageToAll(-1, string);
    }
   
    // Clear the data for the next round
    Map_Clear(MapDmZone);
    return 1;
}

Inventory
Let's say we need to implement a simple inventory with a basic set of functions.
In this architecture we use the slot ID as the unique key (Key), and the item structure as the value (Value).
This allows efficient management of slots, moving items, and quickly checking whether the bag is full.
In this system we use the slot ID as the key (Key), and the item structure as the value (Value).

Code:
const INVENTORY_MAX_SLOTS = 15;

enum e_inventory
{
    Item,
    Amount,
    Name[24]
}
new PawnMap:MapInventory[MAX_PLAYERS] = {INVALID_MAP_ID, ...};

public OnPlayerConnect(playerid)
{
    // Create a map for each player on connect
    MapInventory[playerid] = Map_Create();
    return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
    new PawnMap:mapid = MapInventory[playerid];

    // Check validity before deleting
    if (Map_IsValid(mapid))
    {
        // Fully destroy the map and free the memory in the plugin
        Map_Destroy(mapid);
       
        // Reset the variable to its initial state
        MapInventory[playerid] = INVALID_MAP_ID;
    }
    return 1;
}

stock GiveInventoryItems(playerid, itemid, amount, const name[])
{
    new PawnMap:mapid = MapInventory[playerid];
    // Check whether this item already exists in the inventory (by the Item field)
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);

    static data[e_inventory];

    if (slotid != INVALID_MAP_KEY_ID)
    {
        // If the item is found - increase the amount
        Map_Get(mapid, slotid, data);
        data[Amount] += amount;
        Map_Set(mapid, slotid, data);
    }
    else
    {
        // If the item is new - check the slot limit
        if (Map_GetKeyCount(mapid) >= INVENTORY_MAX_SLOTS)
        {
            SendClientMessage(playerid, -1, "No free slot for the item.");
            return 0;
        }

        new free_slotid = Map_GetFreeKey(mapid);
        if (free_slotid == INVALID_MAP_KEY_ID)
        {
            SendClientMessage(playerid, -1, "Error finding a free slot.");
            return 0;
        }

        data[Item] = itemid;
        data[Amount] = amount;
        Map_SetString(data[Name], name);

        Map_Set(mapid, free_slotid, data);
    }
    SendClientMessage(playerid, -1, "Inventory updated.");
    return 1;
}

stock RemoveInventoryItems(playerid, itemid)
{
    new PawnMap:mapid = MapInventory[playerid];
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);

    if (slotid == INVALID_MAP_KEY_ID)
    {
        SendClientMessage(playerid, -1, "Error: item not found.");
        return 0;
    }
    Map_RemoveKey(mapid, slotid);
    SendClientMessage(playerid, -1, "Item removed.");
    return 1;
}

stock UseInventoryItems(playerid, itemid, amount)
{
    new PawnMap:mapid = MapInventory[playerid];
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);

    if (slotid == INVALID_MAP_KEY_ID)
    {
        SendClientMessage(playerid, -1, "Error: item not found.");
        return 0;
    }

    static data[e_inventory];
    Map_Get(mapid, slotid, data);

    data[Amount] -= amount;

    if (data[Amount] <= 0)
    {
        Map_RemoveKey(mapid, slotid);
    }
    else
    {
        Map_Set(mapid, slotid, data);
    }
    return 1;
}

stock SwapInventoryItems(playerid, slot_1, slot_2)
{
    new PawnMap:mapid = MapInventory[playerid];

    if (Map_Swap(mapid, slot_1, slot_2))
    {
        SendClientMessage(playerid, -1, "Items successfully moved.");
    }
    else
    {
        SendClientMessage(playerid, -1, "Move error.");
    }
    return 1;
}

stock ShowInventory(playerid)
{
    new PawnMap:mapid = MapInventory[playerid];
    new string[1024];
   
    mapfor(mapid, slotid)
    {
        static data[e_inventory];
        Map_Get(mapid, slotid, data);

        format(string, sizeof(string),
            "%s№%d\t%s\tAmount: %d\n",
            string,
            slotid,
            data[Name],
            data[Amount]
        );
    }
    ShowPlayerDialog(playerid, 1000, DIALOG_STYLE_LIST, "Inventory", string, "Select", "Close");
    return 1;
}

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
    if (dialogid == 1000)
    {
        if (!response) return 0;

        new slotid = listitem;
        new PawnMap:mapid = MapInventory[playerid];

        static data[e_inventory];
        if (Map_Get(mapid, slotid, data))
        {
            new string[144];
            format(string, sizeof(string),
                "You selected - Name: %s | ID: %d | Amount: %d",
                data[Name], data[Item], data[Amount]
            );
            SendClientMessage(playerid, -1, string);
        }
    }
    return 1;
}

Speed test 

OPERATION PawnMap Raw Array DIFF (RA/PM)
CREATE & DEL 2 ms 0 ms x0.0
SET 2 ms 1 ms x0.5
GET 1 ms 2 ms x2.0
ADD 1 ms 0 ms x0.0
FIND 8 ms 417 ms x52.1
CONTAINS 0 ms 1052 ms x1052.0
REMOVE 44 ms 1901 ms x43.2
SWAP 0 ms 4 ms x4.0
SORT 4 ms 18 ms x4.5
SET STR 1 ms 1 ms x1.0
CONS STR 0 ms 1394 ms x1394.0
TOTAL TIME 63 ms 4790 ms x76.0

Quote:At this point, only an integer (int) can be a key. In four years of working on projects, I only needed a string identifier for a system once;I don't recall other cases where it would have been necessary. To avoid cluttering the API with duplicate functions with the _Str postfix (which would need to be added to practically every function) and to avoid reducing map performance, I decided not to implement string key support. Instead, I implemented the ability to convert a string into an integer key.

Example
Code:
enum inventory_struct
{
    Itemid,
    Amount
}

public OnGameModeInit()
{
    new PawnMap:mapid = Map_Create();

    // 1. Convert the string "Deagle" into a numeric ID and write the data
    new keyid = Map_StringKeyToInt(mapid, "Deagle");

    if (keyid != INVALID_MAP_KEY_ID)
    {
        new data[inventory_struct];

        data[Itemid] = 24;
        data[Amount] = 100;

        // Save the data array under this ID
        Map_Set(mapid, keyid, data);

        printf("String 'Deagle' successfully converted to ID: %d", keyid);
    }

    // 2. Get the ID by string (using the already created variable, without 'new')
    keyid = Map_GetIdByStringKey(mapid, "Deagle");

    if (keyid != INVALID_MAP_KEY_ID)
    {
        printf("ID for key 'Deagle' found: %d", keyid);
    }
    else
    {
        printf("This string key does not exist in the map.");
    }

    // 3. Check for the key directly
    if (Map_ContainsStringKey(mapid, "Deagle"))
    {
        printf("Key 'Deagle' exists in this map.");
    }
    else
    {
        printf("Key 'Deagle' not found.");
    }

    // 4. Convert the numeric ID back to a string
    new buffer[32];

    if (Map_GetStringById(mapid, keyid, buffer))
    {
        printf("Name of key under ID %d - '%s'", keyid, buffer);
    }

    return 1;
}

Download: >> https://github.com/i-Saibot/PawnMap/releases
  Reply


Messages In This Thread
PawnMap - a plugin for creating maps in Pawn - by email.d.value - 1 hour ago

Forum Jump: