SA-MP Map Zones

This library does not bring anything gamechanging to the table, it?s created to stop a decade long era of bad practices regarding map zones. An array of ~350 zones dumped (or manually converted?) from the game has been around for such a long time, but in that time I?ve never seen a satisfactory API for them. Let?s look at an implementation from Emmet_?s South Central Roleplay.
stock GetLocation(Float:fX, Float:fY, Float:fZ)
{
? ? enum e_ZoneData
? ? {
? ? ? ? e_ZoneName,
? ? ? ? Float:e_ZoneArea
? ? };
? ? new const g_arrZoneData[] =
? ? {
? ? ? ? // ...
? ? };
? ? new
? ? ? ? name = "San Andreas";
? ? for (new i = 0; i != sizeof(g_arrZoneData); i )
? ? {
? ? ? ? if (
? ? ? ? ? ? (fX >= g_arrZoneData && fX <= g_arrZoneData) &&
? ? ? ? ? ? (fY >= g_arrZoneData && fY <= g_arrZoneData) &&
? ? ? ? ? ? (fZ >= g_arrZoneData && fZ <= g_arrZoneData))
? ? ? ? {
? ? ? ? ? ? strunpack(name, g_arrZoneData);
? ? ? ? ? ? break;
? ? ? ? }
? ? }
? ? return name;
}
stock GetPlayerLocation(playerid)
{
? ? new
? ? ? ? Float:fX,
? ? ? ? Float:fY,
? ? ? ? Float:fZ,
? ? ? ? string,
? ? ? ? id = -1;
? ? if ((id = House_Inside(playerid)) != -1)
? ? {
? ? ? ? fX = HouseData;
? ? ? ? fY = HouseData;
? ? ? ? fZ = HouseData;
? ? }
? ? // ...
? ? else GetPlayerPos(playerid, fX, fY, fZ);
? ? format(string, 32, GetLocation(fX, fY, fZ));
? ? return string;
}

If you didn?t get the reference, you should probably check out this repository. GetPlayerLocation most likely uses format to prevent this bug from occurring, but the risk is still there and arrays should never be returned in PAWN. Let?s take a look at another implementation that even I used a long time ago.
stock GetPointZone(Float:x, Float:y, Float:z, zone[] = "San Andreas", len = sizeof(zone))
{
? ? for (new i, j = sizeof(Zones); i < j; i)
? ? {
? ? ? ? if (x >= Zones && x <= Zones && y >= Zones && y <= Zones && z >= Zones && z <= Zones)
? ? ? ? {
? ? ? ? ? ? strunpack(zone, Zones, len);
? ? ? ? ? ? return 1;
? ? ? ? }
? ? }
? ? return 1;
}
stock GetPlayerZone(playerid, zone[], len = sizeof(zone))
{
? ? new Float:pos;
? ? GetPlayerPos(playerid, pos, pos, pos);
? ? for (new i, j = sizeof(Zones); i < j; i)
? ? {
? ? ? ? if (x >= Zones && x <= Zones && y >= Zones && y <= Zones && z >= Zones && z <= Zones)
? ? ? ? {
? ? ? ? ? ? strunpack(zone, Zones, len);
? ? ? ? ? ? return 1;
? ? ? ? }
? ? }
? ? return 1;
}
First of all, what do we see? A lot of code repetition. That?s easy to fix in this case, but what if we also needed either the min/max position of the zone? We?d have to loop through the zones again or take a different approach. Which approach does this library take? Functions like GetMapZoneAtPoint and GetPlayerMapZone do not return the name of the zone, they return an identificator of it. The name or positions of the zone must be fetched using another function. In addition to that, I rebuilt the array of zones myself since the one used basically everywhere seems to be faulty according to this post.
Installation
Simply install to your project:
sampctl package install kristoisberg/samp-map-zones
Include in your code and begin using the library:
#include <map-zones>
Usage
Constants
Functions
MapZone:GetMapZoneAtPoint(Float:x, Float:y, Float:z)
MapZone:GetPlayerMapZone(playerid)
MapZone:GetVehicleMapZone(vehicleid)
MapZone:GetMapZoneAtPoint2D(Float:x, Float:y)
MapZone:GetPlayerMapZone2D(playerid)
MapZone:GetVehicleMapZone2D(vehicleid)
bool:IsValidMapZone(MapZone:id)
bool:GetMapZoneName(MapZone:id, name[], size = sizeof(name))
bool:GetMapZoneSoundID(MapZone:id, &soundid)
bool:GetMapZoneAreaCount(MapZone:id, &count)
GetMapZoneAreaPos(MapZone:id, &Float:minX = 0.0, &Float:minY = 0.0, &Float:minZ = 0.0, &Float:maxX = 0.0, &Float:maxY = 0.0, &Float:maxZ = 0.0, start = 0)
GetMapZoneCount()
Examples
Retrieving the location of a player
CMD:whereami(playerid) {
? ? new MapZone:zone = GetPlayerMapZone(playerid);
? ? if (zone == INVALID_MAP_ZONE_ID) {
? ? ? ? return SendClientMessage(playerid, 0xFFFFFFFF, "probably in the ocean, mate");
? ? }
? ? new name, soundid;
? ? GetMapZoneName(zone, name);
? ? GetMapZoneSoundID(zone, soundid);
? ? new string;
? ? format(string, sizeof(string), "you are in %s", name);
? ? SendClientMessage(playerid, 0xFFFFFFFF, string);
? ? PlayerPlaySound(playerid, soundid, 0.0, 0.0, 0.0);
? ? return 1;
}
Iterating through areas associated with a map zone
new zone = ZONE_RICHMAN, index = -1, Float:minX, Float:minY, Float:minZ, Float:maxX, Float:maxY, Float:maxZ;
while ((index = GetMapZoneAreaPos(zone, minX, minY, minZ, maxX, maxY, maxZ, index 1) != -1) {
? ? printf("%f %f %f %f %f %f", minX, minY, minZ, maxX, maxY, maxZ);
}
Extending
stock MapZone:GetPlayerOutsideMapZone(playerid) {
? ? new House:houseid = GetPlayerHouseID(playerid), Float:x, Float:y, Float:z;
? ? if (houseid != INVALID_HOUSE_ID) { // if the player is inside a house, get the exterior location of the house
? ? ? ? GetHouseExteriorPos(houseid, x, y, z);
? ? } else if (!GetPlayerPos(playerid, x, y, z)) { // the player isn't connected, presuming that GetPlayerHouseID returns INVALID_HOUSE_ID in that case?
? ? ? ? return INVALID_MAP_ZONE_ID;
? ? }
? ? return GetMapZoneAtPoint(x, y, z);
}
Testing
To test, simply run the package:
sampctl package run