16.04.2015, 18:02
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);
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");
}
Код:
Area_AddPoly(/* First Point */ -100.0, -100.0, /* Second Point */ 100.0, -100.0, /* Third Point */ 0.0, 100.0);
Код:
Area_AddPoly(/* First Point */ -100.0, -100.0, /* Third Point */ 0.0, 100.0, /* Second Point */ 100.0, -100.0);
Код:
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
);
Код:
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
);
Код:
#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");
}
- "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 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);
- Код:
// Add a vertically infinite box area. forward Area_AddBox(Float:x0, Float:y0, Float:x1, Float:y1);
- "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 a circle, with optional height. forward Area_AddCircle(Float:x, Float:y, Float:r, Float:height = FLOAT_INFINITY);
- 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.Код:
// Add an oval (an irregular circle). forward Area_AddOval(Float:x, Float:y, Float:rx, Float:ry, Float:h, Float:d);
- Obviously defines a sphere in which to detect players.Код:
forward Area_AddSphere(Float:x, Float:y, Float:z, Float:r);
- 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_AddOvoid(Float:x, Float:y, Float:z, Float:rx, Float:ry, Float:rz);
- See above.Код:
forward Area_AddPoly(Float:x1, Float:y1, Float:x2, Float:y2, Float:x3, Float:y3, Float:...);
As well as functions to add areas, there are several helper functions:
-
A player can be in multiple areas at once as they can overlap. This function returns them all like so:Код:
forward Area_GetPlayerAreas(playerid, idx);
Код:for (new idx, areaid; (areaid = Area_GetPlayerAreas(playerid, idx)) != NO_AREA; ++idx) { printf("Areaid: %d", areaid); } - Areas can be disabled per-player. This function also enables "y_groups" integration with functions such as:Код:
forward Area_SetPlayer(area, playerid, bool:set);
Код:Group_SetArea(Group:g, areaid, bool:set);
- Check if a player can be in an area, not if they are in it.Код:
forward Area_GetPlayer(area, playerid);
- 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_SetWorld(area, world, bool:set);
- Check if the given area is valid in the given virtual world.Код:
forward Area_GetWorld(area, world);
- Set all the player's permissions for a given area.Код:
forward Area_SetAllPlayers(area, bool:set);
- Set all the virtual world permissions for a given area.Код:
forward Area_SetAllWorlds(area, bool:set);
- Is the ID a valid area?Код:
forward Area_IsValid(area);
- Are there no people in this world?Код:
forward Area_IsEmpty(area);
- Remove an area from the system.Код:
forward Area_Delete(area);
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))

