/* Legal: Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 the "License"; you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the YSI framework. The Initial Developer of the Original Code is Alex "Y_Less" Cole. Portions created by the Initial Developer are Copyright C 2011 the Initial Developer. All Rights Reserved. Contributors: Y_Less koolk JoeBullet/Google63 g_aSlice/Slice Misiur samphunter tianmeta maddinat0r spacemud Crayder Dayvison Ahmad45123 Zeex irinel1996 Yiin- Chaprnks Konstantinos Masterchen09 Southclaws PatchwerkQWER m0k1 paulommu udan111 Thanks: JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL. ZeeX - Very productive conversations. koolk - IsPlayerinAreaEx code. TheAlpha - Danish translation. breadfish - German translation. Fireburn - Dutch translation. yom - French translation. 50p - Polish translation. Zamaroht - Spanish translation. Los - Portuguese translation. Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes for me to strive to better. Pixels^ - Running XScripters where the idea was born. Matite - Pestering me to release it and using it. Very special thanks to: Thiadmer - PAWN, whose limits continue to amaze me! Kye/Kalcor - SA:MP. SA:MP Team past, present and future - SA:MP. Optional plugins: Gamer_Z - GPS. Incognito - Streamer. Me - sscanf2, fixes2, Whirlpool. */ #define _GROUP_STOP_INCLUDES /*-------------------------------------------------------------------------*//** * Check if the function exists? * Variable holding the function. * Parameters. * * Calls the correct function to chain a function with the given number of * parameters, calls the function stored in a variable and passes parameters * BY REFERENCE - ALL of them! * *//*------------------------------------------------------------------------**/ /* New groups check: if (ALL A == ALL B) { if (ALL A == P) { // Add. } else { // Remove. } } else { if (ANY A == P && NO B == P) { // Add. } else { // Remove. } } */ enum e_GROUP_FLAGS (<<= 1) { e_GROUP_FLAGS_NONE = 0, e_GROUP_FLAGS_GANG = 1, // I can't remember why I had this! //e_GROUP_FLAGS_CHAT, e_GROUP_FLAGS_ACTIVE, // Only ever set for one group. e_GROUP_FLAGS_GLOBAL, // Has no name. e_GROUP_FLAGS_TEMP, e_GROUP_FLAGS_COLOR = 0xFFFFFF00 } enum E_GROUP_DATA { E_GROUP_DATA_NAME[MAX_GROUP_NAME char], //E_GROUP_DATA_COUNT, E_GROUP_DATA_HASH, e_GROUP_FLAGS:E_GROUP_DATA_FLAGS, } stock PlayerGroups(a, b) { return 0; } // Define the groups iterator in terms of the underlying bits, but without the // need for users to know about the "YSI_gGroupPlayers" array, just the player. #define Iter_Func@PlayerGroups(%1,%0) GROUP_MANGLE(Iter_Func@Bits(_:%1,YSI_gGroupPlayers[%0])) #define Iterator@PlayerGroups Iterator@Bits static // Function pointers for chaining. YSI_g_sNextInitFunc, YSI_g_sNextUpdFunc, YSI_g_sNextAddFunc, YSI_g_sGroupCount; static stock const YSI_g_scGlobalName[] = "__GLOBAL", BitArray:RG@<_MAX_GROUPS_G>; MASTER_DATA<_MAX_GROUPS_G> static stock __declspec(dist_master) YSI_gGroupData[_MAX_GROUPS_G][E_GROUP_DATA]; static stock __declspec(dist_tagged) Bit:YSI_g_sChildGroups[_MAX_GROUPS_G][bits<_MAX_GROUPS_G>]; static stock BitArray:YSI_g_sDefaultGroups<_MAX_GROUPS_G>, // Create group hierarchys. Iterator:GroupPlayers[_MAX_GROUPS_G]; //#define GROUP_BITS<%0> BitArray:%0<_MAX_GROUPS_G>, #define _Group_HasPlayer(%0,%1) \ Bit_Get(YSI_gGroupPlayers[(%1)],(%0)) //, _MAX_GROUPS_G) //PA_Get(YSI_gGroupPlayers[(%0)],(%1)) //Bit_Get(YSI_gGroupPlayers[(%0)], (%1), MAX_PLAYERS) #define _Group_GetColor(%0) \ (_:(YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] & e_GROUP_FLAGS_COLOR) | 0xAA) #define _Group_SetColor(%0,%1) \ (YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] = (YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] & ~e_GROUP_FLAGS_COLOR) | (e_GROUP_FLAGS:(%1) & e_GROUP_FLAGS_COLOR)) #define _Group_GetGang(%0) \ (bool:(YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] & e_GROUP_FLAGS_GANG)) #define _Group_LetGang(%0) \ (YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] |= e_GROUP_FLAGS_GANG) #define _Group_VetGang(%0) \ (YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] &= ~e_GROUP_FLAGS_GANG) #define _Group_IsActive(%0) \ (YSI_gGroupData[_:(%0)][E_GROUP_DATA_FLAGS] & e_GROUP_FLAGS_ACTIVE) //#define _Group_IsValid(%0) // (0 <= (%0) < _MAX_GROUPS && Group_IsActive(%0)) #define _Group_IsValid(%0) \ ((_:GROUP_MASK<=_:(%0)<=_:GROUP_GLOBAL)&&(_Group_IsActive(GROUP_TEMP_FIX(%0)))) //#define Group_IsActive(%0) // (YSI_gGroupData[_:GROUP_FIX(%0)][E_GROUP_DATA_FLAGS] & e_GROUP_FLAGS_ACTIVE) foreign void:_Group_ReInitPlayers(); global void:_Group_ReInitPlayers() { Iter_Init(GroupPlayers); for (new i = 0; i != _MAX_GROUPS_G; ++i) if (_Group_IsActive(i)) { foreach (new playerid : Player) { if (_Group_HasPlayer(i, playerid)) Iter_Add(GroupPlayers[i], playerid); } } } HANDOFF() { P:1("y_groups HANDOFF called"); DISTRIBUTE(YSI_gGroupData); DISTRIBUTE(YSI_g_sChildGroups); _Group_ReInitPlayers(); P:1("y_groups HANDOFF ended"); } /*-------------------------------------------------------------------------*//** * Group to add the following items to. * * This sets up a temporary environment, during which all items are ONLY added * to the specified group and no others, with no complex extra code required. * *//*------------------------------------------------------------------------**/ #define GROUP_ADD<%0> for(J@=_Group_AddInternal(1,(%0));J@;J@=_Group_AddInternal(0,(%0))) static remotefunc void:__Group_AddInternal(a,g) { GROUP_CHAIN?(-1, a, g); } // Can't use "stock remotefunc". #pragma unused __Group_AddInternal stock _Group_AddInternal(a, Group:g) { //printf("_Group_AddInternal %d %d", a, _:g); if (_Group_IsValid(g)) { broadcastfunc __Group_AddInternal(a, _:GROUP_TEMP_FIX(g)); return a; } else { return 0; } } /*-------------------------------------------------------------------------*//** * Player who connected. * The player's new groups. * Size of "g". * * Pretend the player is in all the groups they won't be in shortly so that * they have a complete blank slate when they connect - the system KNOWS they * are not in some groups and are in others. * * Uses an unusual size in "g" to reduce the string length. * *//*------------------------------------------------------------------------**/ static remotefunc void:_Group_FakePlayer(p,Bit:g[sizeof RG@],s) { for (new i = 0; i != sizeof (g); ++i) { // Set their current groups to the inverse of what they are now in. YSI_gGroupPlayers[p][i] = ~g[i]; } _Group_SetPlayer(p, g, s); } /*-------------------------------------------------------------------------*//** * Group to check. * * bool: - Is the group active and valid? * *//*------------------------------------------------------------------------**/ foreign bool:Group_IsValid(Group:g); global bool:Group_IsValid(Group:g) { //GROUP_FIX(g); return _Group_IsValid(g); } static stock Group_DefineStates_() { } static stock Group_DefineStates_() { } static stock Group_DefineStates_() { } /*-------------------------------------------------------------------------*//** * * Finds three functions by prefix: * * _yGI - An init function to set up a script using groups. * _yGA - An add function called when a new group is created. * _yGU - An update function called when a player's groups change. * *//*------------------------------------------------------------------------**/ hook OnScriptInit() { P:1("Group_OnScriptInit called"); state YSI_has_groups : y; // Set up the global groups. #if !(YSIM_HAS_MASTER && (_YSIM_IS_STUB || _YSIM_IS_CLIENT)) #if _YSIM_IS_CLOUD if (_YCM@y) #endif { P:5("Group_OnScriptInit: Master"); Iter_Init(GroupPlayers); strpack(YSI_gGroupData[_MAX_GROUPS][E_GROUP_DATA_NAME], YSI_g_scGlobalName, MAX_GROUP_NAME char); YSI_gGroupData[_MAX_GROUPS][E_GROUP_DATA_FLAGS] = e_GROUP_FLAGS_ACTIVE | e_GROUP_FLAGS_GLOBAL; YSI_gGroupData[_MAX_GROUPS][E_GROUP_DATA_HASH] = YHash(YSI_g_scGlobalName); // Store the default groups away from the actual global group. // The default groups include the global group, but it itself // doesn't. Bit_Let(YSI_g_sDefaultGroups, _:_MAX_GROUPS); } #endif // Call the other group init functions. new ni = AMX_GetPublicPointerPrefix(0, YSI_g_sNextInitFunc, _A<_yGI>), na = AMX_GetPublicPointerPrefix(0, YSI_g_sNextAddFunc, _A<_yGA>), nu = AMX_GetPublicPointerPrefix(0, YSI_g_sNextUpdFunc, _A<_yGU>); //printf("Group_OnScriptInit: %d, %d, %d", ni, na, nu); P:4("Group_OnScriptInit: %d, %d, %d", ni, na, nu); P:4("Group_OnScriptInit: %d, %d, %d", AMX_HEADER_COD - AMX_BASE_ADDRESS, YSI_g_sNextInitFunc, AMX_HEADER_DAT - AMX_BASE_ADDRESS); GROUP_CHAIN?(ni, na, nu); //CallLocalFunction("_Group_SpecialInit", ""); heapspace(); // I'm sure there's a reason this is here. But I forgot it! #if defined _GROUP_INCLUDE_ALL_PREVIOUS if (FALSE) { // Include, but never call, this function. _GROUP_UNIQUE_FUNCTION(); } #endif P:1("Group_OnScriptInit end"); return 1; } /*-------------------------------------------------------------------------*//** * * Destroy all this script's groups. This is an instance of "_YSI_SpecialExit" * which can't be y_hooked and is called after (almost) every other callback, * at least after every one controlled via y_scriptinit. * *//*------------------------------------------------------------------------**/ public OnScriptExit() { #if defined _Group_SpecialExit _Group_SpecialExit(); #endif _Group_TryRemove(); return 1; } #undef OnScriptExit #define OnScriptExit _Group_SpecialExit #if defined _Group_SpecialExit forward _Group_SpecialExit(); #endif /*-------------------------------------------------------------------------*//** * Group to destroy from the system. *//*------------------------------------------------------------------------**/ static remotefunc void:_Group_Destroy(g, Iterator@gp[MAX_PLAYERS + 1], s) { #pragma unused s new ps = Bit_Slot(g), Bit:pm = ~Bit_Mask(g); if (YSI_g_sNextUpdFunc) { // Update all players who were in this group as they may have lost // some permissions. This is the simplest update because we don't // need to worry about the effect of removing this group on the // players' memberships in other groups. Maybe they were added to // that other group as a direct result of being added to this group, // but maybe they weren't - there's no way to know! // Check it! Local parameter iterator! foreach (new p : gp) { YSI_gTempGroups = YSI_gGroupPlayers[p]; // If these parameters were the other way round I could do this: //GROUP_CHAIN(p, (YSI_gGroupPlayers[p], YSI_gGroupPlayers[p][ps] &= pm), YSI_gGroupPlayers[p]); // But they're currently not so I can't. GROUP_CHAIN(p, YSI_gTempGroups, YSI_gGroupPlayers[p]); YSI_gGroupPlayers[p][ps] &= pm; } } else { foreach (new p : gp) { // Remove all players from this group. YSI_gGroupPlayers[p][ps] &= pm; } } } // Added for the fun of it. foreign void:Group_Destroy(Group:group); global void:Group_Destroy(Group:group) { P:2("Group_Destroy called: %i", _:group); P:2("Group_Destroy called in %d", _@); // You can't destroy the global group. if (_Group_IsValid(group) && group != GROUP_GLOBAL) { GROUP_FIX(group); broadcastfunc _Group_Destroy(_:group, Iter_TrueArray(GroupPlayers[_:group]), sizeof (Iter_TrueArray(GroupPlayers[]))); YSI_gGroupData[_:group][E_GROUP_DATA_FLAGS] = e_GROUP_FLAGS:0; YSI_gGroupData[_:group][E_GROUP_DATA_HASH] = 0; Iter_Clear(GroupPlayers[_:group]); MASTER_RESET<_:group> // This group no longer exists, so can't be the child of another group. new ps = Bit_Slot(_:group), Bit:pm = ~Bit_Mask(_:group); for (new i = 0; i != _MAX_GROUPS; ++i) { YSI_g_sChildGroups[i][ps] &= pm; } // Nor can it have groups (saves some later checks). YSI_g_sChildGroups[_: group] = YSI_g_cEmptyGroups; } } /*-------------------------------------------------------------------------*//** * * Removes all groups purely owned by the calling script. * *//*------------------------------------------------------------------------**/ @foreign void:_Group_TryRemove(); @global void:_Group_TryRemove() { for (new i = 0; i != _MAX_GROUPS; ++i) if (_Group_IsActive(i)) { MASTER_REMOVE MASTER_EMPTY { //printf("removing i"); broadcastfunc _Group_Destroy(i, Iter_TrueArray(GroupPlayers[i]), sizeof (Iter_TrueArray(GroupPlayers[]))); YSI_gGroupData[i][E_GROUP_DATA_FLAGS] = e_GROUP_FLAGS:0; YSI_gGroupData[i][E_GROUP_DATA_HASH] = 0; Iter_Clear(GroupPlayers[i]); } } //return 1; } /*-------------------------------------------------------------------------*//** * The player that left. * Why they left (unused). * * Removes this player from all groups. Unfortunately there's no way to * combine multiple iterator removals to improve their efficiency - currently * they have to loop through all previous ones to find the one to modify to * skip over that player. I did debate updating foreach to doubly-linked * lists for this reason - that would make reverse traversal and removal faster * by doubling memory consumption but only very slightly affecting adds. * *//*------------------------------------------------------------------------**/ hook OnPlayerDisconnect(playerid, reason) { #pragma unused reason P:2("hook OnPlayerDisconnect called: %d, %d", playerid, reason); // Can't - can't what? Don't stop typing mid comment! //foreach (new Group:g : PlayerGroups(playerid)) #if !(YSIM_HAS_MASTER && (_YSIM_IS_STUB || _YSIM_IS_CLIENT)) #if _YSIM_IS_CLOUD if (_YCM@y) #endif { foreach (new g : Bits(YSI_gGroupPlayers[playerid])) { Iter_Remove(GroupPlayers[g], playerid); Bit_Vet(YSI_gGroupPlayers[playerid], g); } } #endif // Remove them from everything ever. //static const // BitArray:scEmpty<_MAX_GROUPS_G>; //GROUP_CHAIN?(playerid, YSI_gGroupPlayers[p], scEmpty); YSI_gGroupPlayers[playerid] = YSI_g_cEmptyGroups; return 1; } /*-------------------------------------------------------------------------*//** * The player that joined. * * The player may not have ACTUALLY joined the server, they may have just been * added to this newly loaded script. In that case we need to initialise the * locally stored group data to the new data. Of course, if this script is the * group master, then we need to do significantly more! This is more complex * than other scripts with master as they don't have some things to do in non- * master scripts as well, whereas this one does. * *//*------------------------------------------------------------------------**/ hook OnPlayerConnect@9(playerid) { P:1("Groups_OnPlayerConnect called: %d, %d", playerid, !(YSIM_HAS_MASTER && (_YSIM_IS_STUB || _YSIM_IS_CLIENT))); #if !(YSIM_HAS_MASTER && (_YSIM_IS_STUB || _YSIM_IS_CLIENT)) #if _YSIM_IS_CLOUD if (_YCM@y) #endif { //printf("0"); P:6("Group_OnPlayerConnect: Master %d", playerid); // Is master. ALWAYS reset all players - if they start screwing // around with loading orders then that's their own fault! broadcastfunc _Group_FakePlayer(playerid, YSI_g_sDefaultGroups, bits<_MAX_GROUPS_G>); //printf("1"); } #if _YSIM_IS_CLOUD else #endif #endif #if YSIM_HAS_MASTER { P:6("Group_OnPlayerConnect: Slave %d", playerid); static const BitArray:scF<_MAX_GROUPS_G> = {Bit:-1, ...}; _GROUPS_CHECK_ANY(scF, YSI_gGroupPlayers[playerid]) { // Already been set up - return. return 1; } //_Group_ _Group_InitPlayer(playerid, _@); } #endif return 1; } /*-------------------------------------------------------------------------*//** * The player that joined. * The script that just started. * * Request all a player's groups from the master system. * *//*------------------------------------------------------------------------**/ foreign void:_Group_InitPlayer(p, master); global void:_Group_InitPlayer(p, master) { targetfunc _Group_FakePlayer(p, YSI_gGroupPlayers[p], bits<_MAX_GROUPS_G>); } /*-------------------------------------------------------------------------*//** * Player to add to multiple groups. * Parent group to add to. * * A new array of groups. * * * Takes a group and adds a player to that group and every child group of * which they are not already a member. * *//*------------------------------------------------------------------------**/ // If you overflow this stack there is something VERY wrong! The manual stack // is intended to help us combat very long inheritance chains. This code also // neatly ignores recursion by only doing any one group if the player doesn't // already have it. static YSI_g_sRecursionStack[_MAX_GROUPS_G][2]; static stock Bit:_Group_SetSome(playerid, group) { new g = -1, s = 0; YSI_gTempGroups = YSI_gGroupPlayers[playerid]; Bit_Let(YSI_gTempGroups, group); for ( ; ; ) { g = Iter_Func@Bits(g, YSI_g_sChildGroups[group], bits<_MAX_GROUPS_G>); if (g == -1) { // Finished this child group, pop off the stack. if (s--) { g = YSI_g_sRecursionStack[s][0]; group = YSI_g_sRecursionStack[s][1]; } else break; } else if (!Bit_Get(YSI_gTempGroups, g)) { // This player isn't in this group, add them. Bit_Let(YSI_gTempGroups, g); YSI_g_sRecursionStack[s][0] = g; YSI_g_sRecursionStack[s][1] = group; ++s; group = g; g = -1; } } return YSI_gTempGroups; } /*-------------------------------------------------------------------------*//** * Group to modify. * Player to modify the group for. * Add the player (true) or remove them (false). * * Will in some cases update all settings, unless they are being added through * a recursive call due to being added to child groups. * * There is an internal version that ONLY adds them to the group and DOES NOT * update any of their other settings. As a result it has less checks in it. * *//*------------------------------------------------------------------------**/ foreign void:Group_SetPlayer(Group:g,p,bool:s); global void:Group_SetPlayer(Group:g,p,bool:s) { if (_Group_IsValid(g) && VALID_PLAYERID(p)) { GROUP_FIX(g); if (s) { if (!_Group_HasPlayer(g, p)) { // Added to a group they don't already have. Make a new list of // groups they're in, taking in to account the new groups // hierarchy and pass that new array about. broadcastfunc _Group_SetPlayer(p, _Group_SetSome(p, _:g), sizeof (YSI_gTempGroups)); return; //1; } } else { if (_Group_HasPlayer(g, p)) { YSI_gTempGroups = YSI_gGroupPlayers[p]; Bit_Vet(YSI_gTempGroups, _:g); broadcastfunc _Group_SetPlayer(p, YSI_gTempGroups, sizeof (YSI_gTempGroups)); return; //1; } } } //return 0; } /*-------------------------------------------------------------------------*//** * Player to set. * The player's new groups. * Size of "g". * * Pass a list of groups that the player is now in or not in. This is a * complete replacement for their existing list of groups, and so multiple * updates can be done at once. * *//*------------------------------------------------------------------------**/ remotefunc void:_Group_SetPlayer(p, Bit:g[sizeof RG@], s) { #pragma unused s #if !(YSIM_HAS_MASTER && (_YSIM_IS_STUB || _YSIM_IS_CLIENT)) #if _YSIM_IS_CLOUD if (_YCM@y) #endif { P:2("_Group_SetPlayer called: %i, %s, %s, %i", p, Bit_Display(YSI_gGroupPlayers[p]), Bit_Display(g), s); // new Bit:cur, Bit:exor, Bit:bit, group; for (new i = 0; i != sizeof (g); ++i) { // Get the difference between the old and new groups. cur = YSI_gGroupPlayers[p][i]; exor = g[i] ^ cur; P:6("_Group_SetPlayer: Loop %d 0x%04x%04x 0x%04x%04x", i, _:cur >>> 16, _:cur & 0xFFFF, _:exor >>> 16, _:exor & 0xFFFF); // Low-level bit twiddling. while ((bit = Bit:Cell_GetLowestComponent(exor))) { // group = i * cellbits + (scDeBruijn[(_:bit * 0x077CB531) >>> 27]); group = i * cellbits + Cell_GetLowestBit(exor); P:7("_Group_SetPlayer: %d %s %d", p, _:(cur & bit) ? ("remove from") : ("add to"), group); if (cur & bit) { // Used to have this group, now don't. Iter_Remove(GroupPlayers[group], p); } else { // Didn't have this group, now do. Iter_Add(GroupPlayers[group], p); } exor ^= bit; } } } #endif #pragma tabsize 4 GROUP_CHAIN?(p, YSI_gGroupPlayers[p], g); // NOW save the new version, after everything else has had a chance to // update according to the changes. YSI_gGroupPlayers[p] = g; } /*-------------------------------------------------------------------------*//** * Player to add to the smallest group. * An array of possible groups. * The number of USED items in the array. * * The group they have been added to. * * * Chains the call with "Group_SetPlayer" to use its hierarchy code. * *//*------------------------------------------------------------------------**/ foreign Group:_Group_SetBalancedInternal(p,Group:gs[],c); global Group:_Group_SetBalancedInternal(p,Group:gs[],c) { // Find which of the listed groups has the least players in. new count = cellmax, Group:id = INVALID_GROUP; for (new i = 0; i != c; ++i) { // Find the group with the least players. new gi = _:gs[i]; if (_Group_IsValid(Group:gi)) { new g2 = _:GROUP_TEMP_FIX(Group:gi), cc = Iter_Count(GroupPlayers[g2]); if (_Group_HasPlayer(g2, p)) { // The player is already in this group - just return. return Group:gi; } else if (cc < count) { count = cc, id = Group:gi; } } } if (id == INVALID_GROUP) { P:W("Group_SetBalanced(Array) requires at least 1 valid group."); } else { Group_SetPlayer(id, p, true); } return id; } /*-------------------------------------------------------------------------*//** * Player to add to the smallest group. * An array of possible groups. * The number of USED items in the array. * * The group they have been added to. * * * Puts a player in whichever of the given groups currently has the least * players. * * Now ONLY takes a list - arrays should use "Group_SetBalancedArray". As a * result, has more error-checking. * *//*------------------------------------------------------------------------**/ stock Group:Group_SetBalancedArray(p, Group:gs[], c = sizeof (gs)) { if (!(0 <= p < MAX_PLAYERS)) return INVALID_GROUP; return _Group_SetBalancedInternal(p, gs, c); } /*-------------------------------------------------------------------------*//** * Player to put in one of a number of groups * A list of groups. * * Puts a player in whichever of the given groups currently has the least * players. * * Now ONLY takes a list - arrays should use "Group_SetBalancedArray". As a * result, has more error-checking. * *//*------------------------------------------------------------------------**/ stock Group:Group_SetBalanced(playerid, Group:g0, Group:g1, Group:...) { new Group:possible[_MAX_GROUPS_G], count = min(_MAX_GROUPS_G + 1, numargs()), i = 2, j = 3; possible[0] = g0, possible[1] = g1; while (j != count) { possible[i++] = Group:getarg(j++); } return Group_SetBalancedArray(playerid, possible, i); } /*-------------------------------------------------------------------------*//** * Group to check. * Player to check. * * Is this player in this group? * *//*------------------------------------------------------------------------**/ foreign bool:Group_GetPlayer(Group:g,p); global bool:Group_GetPlayer(Group:g,p) { P:2("bool:Group_GetPlayer called: %i, %i", _:g, p); if (_Group_IsValid(g) && VALID_PLAYERID(p)) { GROUP_FIX(g); return Bit_Get(YSI_gGroupPlayers[p], _:g); //Bit_Set(YSI_gGroupPlayers[g], p, s, bits); } return false; } /*-------------------------------------------------------------------------*//** * * Group to set the name of. * The new name of the group. * * Sets the name of a group. * *//*------------------------------------------------------------------------**/ foreign void:Group_SetName(Group:g,string:n[]); global void:Group_SetName(Group:g,string:n[]) { P:2("Group_SetName called: %i, \"%s\"", _:g, n); if (_Group_IsValid(g)) { GROUP_FIX(g); if (isnull(n)) { YSI_gGroupData[_:g][E_GROUP_DATA_NAME][0] = 0; YSI_gGroupData[_:g][E_GROUP_DATA_HASH] = 0; } else { strpack(YSI_gGroupData[_:g][E_GROUP_DATA_NAME], n, MAX_GROUP_NAME char); YSI_gGroupData[_:g][E_GROUP_DATA_HASH] = YHash(n); } } //return 1; } /*-------------------------------------------------------------------------*//** * Group to get the name of. * * string: * * * Gets the name of a group. * *//*------------------------------------------------------------------------**/ foreign string:Group_GetName(Group:g); global string:Group_GetName(Group:g) { //printf("Group_GetName called: %04x%04x", _:g >>> 16, _:g & 0xFFFF); P:2("Group_GetName called: %04x%04x", _:g >>> 16, _:g & 0xFFFF); static ret[YSI_MAX_STRING]; if (_Group_IsValid(g)) { //printf("Is Valid"); //return Bit_Get(YSI_gGroupPlayers[g], p); //Bit_Set(YSI_gGroupPlayers[g], p, s, bits); strunpack(ret, YSI_gGroupData[_:GROUP_TEMP_FIX(g)][E_GROUP_DATA_NAME], YSI_MAX_STRING); //printf("Is Valid: %s", ret); } return ret; } /*-------------------------------------------------------------------------*//** * Name of a group to get. * * Group: - The ID of the group with this name. * *//*------------------------------------------------------------------------**/ foreign Group:Group_GetID(string:name[]); global Group:Group_GetID(string:name[]) { P:2("Group_GetID called: %s", name); new i, hash = YHash(name); while (i != _MAX_GROUPS) { if (_Group_IsActive(i) && YSI_gGroupData[i][E_GROUP_DATA_HASH] == hash) { break; } ++i; } if (i == _MAX_GROUPS) return INVALID_GROUP; return GROUP_MANGLE(i); } /*-------------------------------------------------------------------------*//** * Group to set for. * Set or not? * * I actually can't remember what the "Gang" setting on a group does - I don't * think it actually does anything in the latest code version! * *//*------------------------------------------------------------------------**/ foreign void:Group_SetGang(Group:g,bool:n); global void:Group_SetGang(Group:g,bool:n) { P:2("Group_SetGang called: %i, %i", _:g, n); if (_Group_IsValid(g)) { if (n) { _Group_LetGang(GROUP_TEMP_FIX(g)); } else { _Group_VetGang(GROUP_TEMP_FIX(g)); } } //return 1; } /*-------------------------------------------------------------------------*//** * Group to get the gang status of. * * bool: * * * I still don't remember what this once did! * *//*------------------------------------------------------------------------**/ foreign bool:Group_GetGang(Group:g); global bool:Group_GetGang(Group:g) { P:2("bool:Group_GetGang called: %i", _:g); if (_Group_IsValid(g)) { return _Group_GetGang(GROUP_TEMP_FIX(g)); } return false; } /*-------------------------------------------------------------------------*//** * * Group_SetColor * Group_SetColour * * The group to set the colour of. * An RGBA colour. * * This colour is not actually currently used anywhere. * *//*------------------------------------------------------------------------**/ foreign void:Group_SetColour(Group:g,c); global void:Group_SetColour(Group:g,c) { P:2("Group_SetColour called: %i, %i", _:g, c); if (_Group_IsValid(g)) { GROUP_FIX(g); _Group_SetColor(g, c); } //return 1; } /*-------------------------------------------------------------------------*//** * * Group_GetColor * Group_GetColour * * The group to get the colour of. * * An RGBA colour. * * * This colour is not actually currently used anywhere. * *//*------------------------------------------------------------------------**/ foreign Group_GetColour(Group:g); global Group_GetColour(Group:g) { P:2("Group_GetColour called: %i", _:g); if (_Group_IsValid(g)) { return _Group_GetColor(GROUP_TEMP_FIX(g)); //Bit_Set(YSI_gGroupPlayers[g], p, s, bits); } return 0; } #define Group_SetColor Group_SetColour #define Group_GetColor Group_GetColour /*-------------------------------------------------------------------------*//** * The group to get the membership for. * * The number of players in the group. * *//*------------------------------------------------------------------------**/ foreign Group_GetCount(Group:g); global Group_GetCount(Group:g) { P:2("Group_GetCount called: %i", _:g); if (_Group_IsValid(g)) { GROUP_FIX(g); return Iter_Count(GroupPlayers[_:g]); } return 0; } /*-------------------------------------------------------------------------*//** * The optional name of the new group. * * The group ID with a tag of "Group:", or "INVALID_GROUP". * * * Group_Create - Local function to detect and "NULL"ify empty strings. * _Group_Create - Global function that does most of the work. * _Group_CreateChain - Remote function that updates all master scripts with * the new group's existence. * *//*------------------------------------------------------------------------**/ static remotefunc void:_Group_CreateChain(Group:g) { GROUP_CHAIN?(g); } @foreign Group:_Group_Create(string:name[]); @global Group:_Group_Create(string:name[]) { P:2("Group:Group_Create called: \"%s\"", name); P:2("Group_Create called in %d", _@); new i; if (!isnull(name)) { // Get a group of the same name if it already exists. i = _:Group_GetID(name); if (i != _:INVALID_GROUP) { MASTER_ADD<_:GROUP_TEMP_FIX(Group:i)> return Group:i; } } i = 0; while (i != _MAX_GROUPS && _Group_IsActive(i)) { ++i; } if (i == _MAX_GROUPS) return INVALID_GROUP; if (isnull(name)) { YSI_gGroupData[i][E_GROUP_DATA_NAME][0] = 0; YSI_gGroupData[i][E_GROUP_DATA_FLAGS] = e_GROUP_FLAGS_TEMP | e_GROUP_FLAGS_ACTIVE; YSI_gGroupData[i][E_GROUP_DATA_HASH] = 0; } else { strpack(YSI_gGroupData[i][E_GROUP_DATA_NAME], name, MAX_GROUP_NAME char); YSI_gGroupData[i][E_GROUP_DATA_FLAGS] = e_GROUP_FLAGS_ACTIVE; if (!(YSI_gGroupData[i][E_GROUP_DATA_HASH] = YHash(name))) { P:E("Group %d has hash 0.", name); } } MASTER_SET ++YSI_g_sGroupCount; // Set the child groups to only this group. //Bit_Let(YSI_g_sChildGroups[i], i); // Add this group to other groups. broadcastfunc _Group_CreateChain(Group:i); return GROUP_MANGLE(i); } stock Group:Group_Create(string:name[] = "") { if (name[0]) { return _Group_Create(name); } else { return _Group_Create(NULL); } } /*-------------------------------------------------------------------------*//** * Group to loop over. * Last value. * * The next player. * * * Internal implementation of the "Group()" iterator for "foreach". Returns * all the players in a group one at a time. Now just wraps the "GroupPlayers" * iterator that is only stored in the GROUP master script. * *//*------------------------------------------------------------------------**/ #pragma deprecated use "GroupMember" stock Iter_Func@Group(start, Group:group) { static sOnce = true; if (sOnce) { P:I("The \"Group\" iterator is deprecated, please use \"GroupMember\" instead."); sOnce = false; } return Iter_Func@GroupMember(start, Group:group); } // Else. VERY similar to the `iterstart` macro, but without the first `J@`, // which is used both for `sizeof` (we already gave it `I@`) and for detecting // state functions, which this is not one of (so doesn't need the detection - // and even if it was a state function, we would know that here and could // adapt). #define Iterator@GroupElse_(%1)%9$%9))-1);_:(%9=Iterator@%0$[_:%2]%9; ))-1+(%1));_:(%1)!=_:F@k:F@l:(%2=Iter_Func@%0$(_:%2)); foreign Iter_Func@GroupMember(start, Group:group); global Iter_Func@GroupMember(start, Group:group) { GROUP_FIX(group); if (start == -1) { //start = Iter_Begin(GroupPlayers[]); start = Iter_First(GroupPlayers[_:GROUP_TEMP_FIX(group)]); } else { start = Iter_Next(GroupPlayers[_:GROUP_TEMP_FIX(group)], start); } if (start == Iter_End(GroupPlayers[])) { return -1; } return start; } #define Iterator@GroupMember iterstart(-1) /*-------------------------------------------------------------------------*//** * Last value. * * The next group. * * * Internal implementation of the "CreatedGroup()" iterator for "foreach". * Returns all the groups that exist. * *//*------------------------------------------------------------------------**/ // Bypass y_iterate restrictions. foreign Group:Iter_Func@CreatedGroup(group); global Group:Iter_Func@CreatedGroup(group) { // In most iterators the inital value is implicitly handled by the first // "++", but it isn't here because we need to correct for the "Group" flag. if (Group:group != INVALID_GROUP) GROUP_FIX(Group:group); while (++group < (_MAX_GROUPS_G - 1)) { // DO NOT include the global group in this loop! if (_Group_IsActive(Group:group)) return GROUP_MANGLE(Group:group); } return INVALID_GROUP; } #define Iterator@CreatedGroup iterstart(-1) /*-------------------------------------------------------------------------*//** * Group to get the children of. * Last value. * * The next group. * * * Internal implementation of the "ChildGroup()" iterator for "foreach". * Returns all the groups that are a child of the provided group. * *//*------------------------------------------------------------------------**/ foreign Group:Iter_Func@GroupChild(start, Group:parent); global Group:Iter_Func@GroupChild(start, Group:parent) { if (!_Group_IsValid(parent)) return INVALID_GROUP; // In most iterators the inital value is implicitly handled by the first // "++", but it isn't here because we need to correct for the "Group" flag. if (Group:start != INVALID_GROUP) GROUP_FIX(Group:start); // Use the existing "Bits" iterator with fixed identifiers. return GROUP_MANGLE(Iter_Func@Bits(start, YSI_g_sChildGroups[_:GROUP_TEMP_FIX(parent)], bits<_MAX_GROUPS_G>)); } #define Iterator@GroupChild Iterator@Bits /*-------------------------------------------------------------------------*//** * * _Y_G@C_0 * _Y_G@C_1 * _Y_G@C_2 * _Y_G@C_3 * * Function to call. * 0, 1, 2, or 3 parameters. * * 0 - ALWAYS zero to make "_gchain" work properly. * * * Basically function indirection, call a function through a pointer and use * the compiler to figure out the assembly to generate instead of having a * run-time loop inside a single instance of this function. Using just one * macro ("GROUP_CHAIN") one of these four functions are selected and called. * Adding more is fairly trivial too, the parameters are just: * * #emit PUSH.S <16 + n * 4> * #emit PUSH.S ... * #emit PUSH.S 20 * #emit PUSH.S 16 * #emit PUSH.C * * *//*------------------------------------------------------------------------**/ stock _Y_G@C_0(func) { #emit PUSH.C 0 #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri #emit LOAD.S.pri func #emit SCTRL 6 // ALWAYS return 0; return 0; } stock _Y_G@C_1(func, GLOBAL_TAG_TYPES:...) { #emit PUSH.S 16 #emit PUSH.C 4 #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri #emit LOAD.S.pri func #emit SCTRL 6 //#emit RETN return 0; } stock _Y_G@C_2(func, GLOBAL_TAG_TYPES:...) { #emit PUSH.S 20 #emit PUSH.S 16 #emit PUSH.C 8 #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri #emit LOAD.S.pri func #emit SCTRL 6 //#emit RETN return 0; } stock _Y_G@C_3(func, GLOBAL_TAG_TYPES:...) { #emit PUSH.S 24 #emit PUSH.S 20 #emit PUSH.S 16 #emit PUSH.C 12 #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri #emit LOAD.S.pri func #emit SCTRL 6 //#emit RETN return 0; } /*-------------------------------------------------------------------------*//** * Group to set for. * Element to set. * Set or unset? * * IMPORTANT NOTE: Groups are SLIGHTLY different to other systems - if you * REMOVE a group from another group then players WILL NOT be removed from * that second group, or any child groups. * *//*------------------------------------------------------------------------**/ foreign void:Group_SetGroup(Group:g,Group:el,bool:s); global void:Group_SetGroup(Group:g,Group:el,bool:s) { P:2(#Group_SetGroup " called: %i, %i, %i", _:g, _:el, s); // Set wether a group can use this item. if (g != el && _Group_IsValid(g) && _Group_IsValid(el)) { GROUP_FIX(g); GROUP_FIX(el); if (s) { if (Bit_Get(YSI_g_sChildGroups[_:g], _:el)) { // Child is already part of parent. return; } Bit_Let(YSI_g_sChildGroups[_:g], _:el); if (g == Group:_MAX_GROUPS) Bit_Let(YSI_g_sDefaultGroups, _:el); foreach (new p : GroupPlayers[_:g]) { if (!_Group_HasPlayer(el, p)) { broadcastfunc _Group_SetPlayer(p, _Group_SetSome(p, _:el), bits<_MAX_GROUPS_G>); } } } else { if (Bit_Get(YSI_g_sChildGroups[_:g], _:el)) { // Child is a part of parent (don't remove players though). Bit_Vet(YSI_g_sChildGroups[_:g], _:el); if (g == Group:_MAX_GROUPS) Bit_Vet(YSI_g_sDefaultGroups, _:el); } } } } /*-------------------------------------------------------------------------*//** * Element to set. * Set or unset? * * If "s" is true, then one element is added to the global group. False it is * removed. * *//*------------------------------------------------------------------------**/ foreign void:Group_SetGlobalGroup(Group:el,bool:s); global void:Group_SetGlobalGroup(Group:el,bool:s) { P:2(#Group_SetGlobalGroup " called: %i, %i", _:el, s); Group_SetGroup(GROUP_GLOBAL, el, s); } /*-------------------------------------------------------------------------*//** * Group_Get... * Group to get from. * Element to get. * * bool: Does the group have the element? * * * This has no "active" checks on the groups as if they aren't active but are * in range, then "YSI_g_sChildGroups" will return false anyway. Extra checks * are therefore just a waste of time. * *//*------------------------------------------------------------------------**/ foreign bool:Group_GetGroup(Group:g,Group:el); global bool:Group_GetGroup(Group:g,Group:el) { P:2(#Group_GetGroup " called: %i, %i", _:g, _:el); return (GROUP_MASK <= el <= GROUP_GLOBAL && GROUP_MASK <= g <= GROUP_GLOBAL && Bit_Get(YSI_g_sChildGroups[_:GROUP_TEMP_FIX(g)], _:GROUP_TEMP_FIX(el))); } /*-------------------------------------------------------------------------*//** * Group_GetGlobal... * Element to get. * * bool: Does the global group have the element? * *//*------------------------------------------------------------------------**/ foreign Group_GetGlobalGroup(Group:el); global Group_GetGlobalGroup(Group:el) { P:2(#Group_GetGlobalGroup " called: %i", _:el); return Group_GetGroup(GROUP_GLOBAL, el); } #define Group_AddChild(%0,%1) Group_SetGroup((%0), (%1), true) #define Group_RemoveChild(%0,%1) Group_SetGroup((%0), (%1), false) #define Group_IsChild(%0,%1) Group_GetGroup((%0), (%1)) /*-------------------------------------------------------------------------*//** * Ancestor group to check. * Other group to check. * * bool: Is the second group related to the first? * * * Now uses implicit, not explicit, recursion. * *//*------------------------------------------------------------------------**/ static stock BitArray:YSI_g_sGroupDone<_MAX_GROUPS_G>; foreign bool:Group_IsDescendant(Group:p, Group:c); global bool:Group_IsDescendant(Group:p, Group:c) { // Groups that don't exist CAN'T be related, even if they're the same group. if (_Group_IsValid(p) && _Group_IsValid(c)) { if (p == c) return true; // Any group is related to itself. GROUP_FIX(p); GROUP_FIX(c); // "g" = current child. "s" = current stack position. "c" = target // child group. "p" = current parent. Bit_SetAll(YSI_g_sGroupDone, false); Bit_Let(YSI_g_sGroupDone, _:p); new g = -1, s = 0; for ( ; ; ) { g = Iter_Func@Bits(g, YSI_g_sChildGroups[_:p], bits<_MAX_GROUPS_G>); if (g == -1) { // Finished this child group, pop off the stack. if (s--) { g = YSI_g_sRecursionStack[s][0]; p = Group:YSI_g_sRecursionStack[s][1]; } else return false; } else if (!Bit_Get(YSI_g_sGroupDone, g)) { if (Group:g == c) return true; // We haven't tested this group yet. Bit_Let(YSI_g_sGroupDone, g); YSI_g_sRecursionStack[s][0] = g; YSI_g_sRecursionStack[s][1] = _:p; ++s; p = Group:g; g = -1; } } } return false; }