[Tutorial] y_areas
#1

y_areas


Introduction

This is one of the higher-level YSI libraries that has been in existence for a while, but which I never got around to documenting. It provides two callbacks:

Код:
forward OnPlayerEnterArea(playerid, areaid);

forward OnPlayerLeaveArea(playerid, areaid);
These are called whenever a player enters or leaves one of the pre-defiend regions of space defined in your mode through a variety of functions.

Use

This library provides a number of functions for declaring areas. For example, to detect when a player is in Fort Carson do:

Код:
#include <YSI\y_areas>

new
    gAreaFortCarson

public OnScriptInit()
{
    gAreaFortCarson = Area_AddCuboid(-376.20, 826.30, -3.00, 123.70, 1220.40, 200.00);
}

public OnPlayerEnterArea(playerid, areaid)
{
    if (areaid == gAreaFortCarson) SendClientMessage(playerid, X11_GREEN, "Welcome to Fort Carson");
}
You can also have more complex shapes. A "Poly" is any "polygon", and is defined by a series of co-ordinates, each one representing a corner of the shape. A simple triangle would look something like:

Код:
Area_AddPoly(/* First Point */ -100.0, -100.0, /* Second Point */ 100.0, -100.0, /* Third Point */ 0.0, 100.0);
Note that this shape is equivalent:

Код:
Area_AddPoly(/* First Point */ -100.0, -100.0, /* Third Point */ 0.0, 100.0, /* Second Point */ 100.0, -100.0);
The points must be in an order such that drawing lines between them will trace the outline of the shape. This is a valid square:

Код:
Area_AddPoly(
    -100.0, -100.0, // Bottom left
     100.0, -100.0, // Bottom right
     100.0,  100.0, // Top right
    -100.0,  100.0  // Top left
);
This is NOT a square, even though it has the same points. Instead, the order draws out an egg-timer shape (the last and first points are implicitly connected):

Код:
Area_AddPoly(
    -100.0, -100.0, // Bottom left
     100.0,  100.0, // Top right
    -100.0,  100.0, // Top left
     100.0, -100.0 // Bottom right
);
The (x, Y) pairs imply an even number of parameters; however, with an odd number the last one (335.90 below) is the HEIGHT - any players above this point are not in the area. Currently you can't have a lower-bound on polygons, but can on most other shapes. To detect when a player enters Downtown Los Santos (which is not a simple square) do:

Код:
#include <YSI\y_areas>

new
    gAreaDowntownLosSantos;

public OnScriptInit()
{
    gAreaDowntownLosSantos= Area_AddPoly(
        1812.60, -1430.80,
        1812.60, -1150.80,
        1463.90, -1150.80,
        1463.90, - 926.90,
        1391.00, - 926.90,
        1391.00, -1026.30,
        1378.30, -1026.30,
        1378.30, -1130.80,
        1370.80, -1130.80,
        1370.80, -1384.90,
        1463.90, -1384.90,
        1463.90, -1430.80, 335.90);
}

public OnPlayerEnterArea(playerid, areaid)
{
    if (areaid == gAreaDowntownLosSantos) SendClientMessage(playerid, X11_GREEN, "Welcome to Downtown Los Santos");
}
Area Functions
  • Код:
    // Add a box area with upper and lower limits.
    forward Area_AddCuboid(Float:x0, Float:y0, Float:z0, Float:x1, Float:y1, Float:z1);
    forward Area_AddCube(Float:x0, Float:y0, Float:z0, Float:x1, Float:y1, Float:z1);
    "x0" and "x1" do not have to be lower and upper bounds in that order - they can be either way around. Same for y and z.
  • Код:
    // Add a vertically infinite box area.
    forward Area_AddBox(Float:x0, Float:y0, Float:x1, Float:y1);
  • Код:
    // Add a circle, with optional height.
    forward Area_AddCircle(Float:x, Float:y, Float:r, Float:height = FLOAT_INFINITY);
    "x" and "y" are the centre of the circle. "r" is the radius. This uses "Area_AddOval" internally. This basically defines a cylinder in which to detect a player.
  • Код:
    // Add an oval (an irregular circle).
    forward Area_AddOval(Float:x, Float:y, Float:rx, Float:ry, Float:h, Float:d);
    In this function, "h" is the upper limit and "d" is the lower limit (can be set to FLOAT_INFINITY and FLOAT_NEGATIVE_INFINITY). "rx" and "ry" are the width of the oval in each dimension.
  • Код:
    forward Area_AddSphere(Float:x, Float:y, Float:z, Float:r);
    Obviously defines a sphere in which to detect players.
  • Код:
    forward Area_AddOvoid(Float:x, Float:y, Float:z, Float:rx, Float:ry, Float:rz);
    This is similar to "Area_AddOval", but in three dimensions. You can define a shape similar to an egg (but you can't actually define an egg as they are smaller at one end than the other - this is regular).
  • Код:
    forward Area_AddPoly(Float:x1, Float:y1, Float:x2, Float:y2, Float:x3, Float:y3, Float:...);
    See above.
Other Functions

As well as functions to add areas, there are several helper functions:
  • Код:
    forward Area_GetPlayerAreas(playerid, idx);
    A player can be in multiple areas at once as they can overlap. This function returns them all like so:

    Код:
    for (new idx, areaid; (areaid = Area_GetPlayerAreas(playerid, idx)) != NO_AREA; ++idx)
    {
        printf("Areaid: %d", areaid);
    }
  • Код:
    forward Area_SetPlayer(area, playerid, bool:set);
    Areas can be disabled per-player. This function also enables "y_groups" integration with functions such as:

    Код:
    Group_SetArea(Group:g, areaid, bool:set);
  • Код:
    forward Area_GetPlayer(area, playerid);
    Check if a player can be in an area, not if they are in it.
  • Код:
    forward Area_SetWorld(area, world, bool:set);
    Set which virtual world an area is valid in. Can be called multiple times to set multiple worlds. Note that by default (without changing "AREA_WORLDS") you can only specify 256 worlds.
  • Код:
    forward Area_GetWorld(area, world);
    Check if the given area is valid in the given virtual world.
  • Код:
    forward Area_SetAllPlayers(area, bool:set);
    Set all the player's permissions for a given area.
  • Код:
    forward Area_SetAllWorlds(area, bool:set);
    Set all the virtual world permissions for a given area.
  • Код:
    forward Area_IsValid(area);
    Is the ID a valid area?
  • Код:
    forward Area_IsEmpty(area);
    Are there no people in this world?
  • Код:
    forward Area_Delete(area);
    Remove an area from the system.
Download

This library, like most of my libraries, is included in YSI. This can be downloaded here:

https://sampforum.blast.hk/showthread.php?tid=321092

Advanced

This system uses a (IMHO) quite complex zoning system to stream areas - only the ones known to be near the player are checked. The world is split in to a grid and areas added to the grid square in which they fit entirely. If an area does not fit in to a single square there are numerous pregressively larger zones in which they can be placed - quarter of the map, half the map, the whole map, several areas outside the map, and the whole world (i.e. everywhere). This means that a player is in several zones at once and only these ones are checked. You can see the (quite ugly and hand-unrolled) code for this zone determination in "Area_DetermineZone".

In addition to this zoning, the more complex zones (i.e. polygons) have extra optimisations in the form of bounding circles. A circle is drawn around the whole area - if the player is not in this circle (which is easy to check) then they can't possibly be in the polygon (which is hard to check). Only if they are in the circle is the polygon checked too.

As already mentioned, a player can be in multiple areas at once. Several of these areas are stored in an array for fast access - if they are in a great number of areas at once (which is unlikely) then the extras are stored in slower but more dynamic PVars. The limit at which this happens is defined by "AREAS_MAX_OVERLAPS" and defaults to 3.

Finally, this code contains the most complex "enum" that I've ever written! An area has certain pieces of data such as players and worlds that all areas need, but some areas need more storage for co-ordinates than others. To accommodate this, multiple areas are used to store a single one, but in those cases the space used to store things like player permissions is not needed and is instead used for extra co-ordinate space. This is done through this enum:

Код:
// =============================================================================
// =============================================================================
// IMPORTANT NOTE: The order of elements in this enum is INCREDIBLY important.
// Because this is three enums in one, they share some pieces of data that must
// not get overridden.
// =============================================================================
// =============================================================================
// This might just be the most complex enum I've ever made!
enum E_AREA
{
    //union
    //{
    //    struct
    //    {
            #if YSIM_HAS_MASTER
                E_AREA_MASTER,
            #endif
            // This has increased from 4 to 6, so we can store cubes and ovoids
            // in a single slot.
            Float:E_AREA_POS[6],
            PlayerArray:E_AREA_PLAYERS<MAX_PLAYERS>,
            // This MUST go between "E_AREA_PLAYERS" and "E_AREA_FLAGS" to
            // remain constant in subsequent unions.
            //#if defined AREA_VERY_FAST
            //    Float:E_AREA_BOUNDING[4],
            //#endif
            // As must this.
            #if AREA_WORLDS > 0
                BitArray:E_AREA_WORLDS<AREA_WORLDS>,
            #endif
            // ALWAYS last (actually used by EVERY type).
            e_AREA_FLAGS:E_AREA_FLAGS,
    //    }
    //    // Start of poly data.
    //    struct
    //    {
            // Reset the enum counter to 0 (on the next item).
            _E_AREA_RESET_@1 = -1,
            // Now restart.
            #if YSIM_HAS_MASTER
                // Skip the master flag if it exists.
                _E_AREA_MASTER_@1,
            #endif
            // This is where polys differ from all others.
            E_AREA_POLY_COUNT,
            E_AREA_POLY_NEXT,
            //Float:E_AREA_POLY_POS[4],
            // This slot holds ONLY the polygon bounding data, it doesn't hold
            // any co-ordinates AT ALL.
            //Float:E_AREA_BOUNDING[4],
            Float:E_AREA_POLY_BOUND_X,
            Float:E_AREA_POLY_BOUND_Y,
            Float:E_AREA_POLY_BOUND_R,
            Float:E_AREA_POLY_BOUND_H,
            // Skip all the array and flag data, they stay the same.
            _E_AREA_ARRAYS_@1[E_AREA_FLAGS - E_AREA_PLAYERS],
            // Skip the flags.
            E_AREA_UNUSED_NEXT,
    //    }
    //    // Start of poly child data.
    //    struct
    //    {
            // Reset the enum counter to 0 (on the next item).
            _E_AREA_RESET_@2 = -1,
            // Now restart.
            #if YSIM_HAS_MASTER
                // Skip the master flag if it exists.
                _E_AREA_MASTER_@2,
            #endif
            // Store a link to the parent of this poly child.
            E_AREA_CHILD_PARENT,
            // And the next one in VERY rare cases (I hope).
            // THIS SHARES A SLOT WITH "E_AREA_POLY_NEXT", so we can always just
            // use that slot when iterating.
            #if _:(E_AREA_FLAGS - E_AREA_CHILD_PARENT) & 1
                // There are an ODD number of available slots.  We can store an
                // extra pair iff we don't need to use "NEXT".
                //_E_AREA_SKIP_@2,
                E_AREA_CHILD_OPT_Y,
            #endif
            E_AREA_CHILD_OPT_X,
            // Just store a vast number of X/Y pairs in this child.
            Float:E_AREA_CHILD_ELEMS[_:E_AREA_FLAGS - _:E_AREA_CHILD_OPT_X - 1],
            // Skip the flags.
            _E_AREA_CHILD_ELEMS_END
    //    }
}

#define CHILD_AREA_SLOTS ((_:_E_AREA_CHILD_ELEMS_END - _:E_AREA_CHILD_ELEMS))
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)