1 hour ago
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

