[Tutorial] Admin levels with y_groups
#1

Introduction

y_groups is an integral part of many YSI libraries (y_classes, y_commands etc) which provides a single implementation for any grouping of players you may like. Examples of "groups" of players include admin levels, factions, gangs and teams. A collection of people with the same options is a "group", and people can be in multiple "groups" - an admin on a role-play server may still be in a faction for role-playing in-character.

For this article we will develop a very simple script which can add and remove admin commands in one line (so you can make ANY command admin or not).

Librariers and Commands

y_groups can interact with any library with the right interface (making it completely optional). This interface is an article for another time, but y_commands provides this interface (basically it has per-player permissions which can be set and unset by y_groups). First lets create a basic mode with all required libraries and three commands:

Code:
#include <YSI\y_commands>
#include <YSI\y_groups>
#include <sscanf2>

YCMD:kick(playerid, params[], help)
{
	if (help)
	{
		return SendClientMessage(playerid, 0xFF0000AA, "Kicks another player.");
	}
	else
	{
		new
			pid,
			reason[32];
		if (sscanf(params, "uS[32]", pid, reason))
		{
			return SendClientMessage(playerid, 0xFF0000AA, "Parameters: <playerid/name> [reason]");
		}
		if (reason[0])
		{
			SendClientMessage(pid, 0xFF0000AA, "You have been kicked.  Reason:");
			SendClientMessage(pid, 0xFF0000AA, reason);
		}
		else
		{
			SendClientMessage(pid, 0xFF0000AA, "You have been kicked.");
		}
		Kick(pid);
	}
	return 1;
}

YCMD:ban(playerid, params[], help)
{
	if (help)
	{
		return SendClientMessage(playerid, 0xFF0000AA, "Bans another player.");
	}
	else
	{
		new
			pid,
			reason[32];
		if (sscanf(params, "uS[32]", pid, reason))
		{
			return SendClientMessage(playerid, 0xFF0000AA, "Parameters: <playerid/name> [reason]");
		}
		if (reason[0])
		{
			SendClientMessage(pid, 0xFF0000AA, "You have been banned.  Reason:");
			SendClientMessage(pid, 0xFF0000AA, reason);
		}
		else
		{
			SendClientMessage(pid, 0xFF0000AA, "You have been banned.");
		}
		Ban(pid);
	}
	return 1;
}

YCMD:pm(playerid, params[], help)
{
	if (help)
	{
		return SendClientMessage(playerid, 0xFF0000AA, "Sends a private message to another player.");
	}
	else
	{
		new
			pid,
			message[32];
		if (sscanf(params, "us[32]", pid, message))
		{
			return SendClientMessage(playerid, 0xFF0000AA, "Parameters: <playerid/name> <message>");
		}
		SendClientMessage(pid, 0xFF0000AA, "Incoming message:");
		SendClientMessage(pid, 0xFF0000AA, message);
		SendClientMessage(playerid, 0xFF0000AA, "Sent!");
	}
	return 1;
}

main()
{
}
Now those commands are VERY basic, but they're just for show. Currently any player can use those commands, even the "/ban" command - this is not a good idea.

y_groups
y_groups has a very flexible API - it is actually defined in terms of other libraries. In this can the functions available include:
  • Code:
    Group:Group_Create(name[]);
    Creates a new group in to which to place people. The name is currently not optional but will be.
  • Code:
    Group_SetCommand(Group:group, command, bool:set);
    Set whether or not a group can use a command.
  • Code:
    Group_SetGlobalCommand(command, bool:set);
    Set whether or not people can use this command by default.
Those are the main functions. You can replace the word "Command" with any library using y_groups, e.g. "Class" for y_classes.

Permissions

Admin levels using y_groups can be done in two ways. The first is to have every level as a separate group and set all permissions for every level. The second is instead of having groups: "level 1", "level 2" etc, you have groups: "level 1+", "level 2+" etc. With "levels", people in level 2 can use level 1 commands, so they are level 1 admins AND level 2 admins. Lets generically create a number of admin levels:

Code:
// Up to 99.
#define MAX_ADMIN_LEVELS (3)

new
	Group:gAdmins[MAX_ADMIN_LEVELS];

public OnGameModeInit()
{
	new
		name[16];
	for (new i = 0; i != MAX_ADMIN_LEVELS; ++i)
	{
		format(name, sizeof (name), "Admin Level %d+", i + 1);
		gAdmins[i] = Group_Create(name);
	}
}
So we now have three admin levels, we need to set commands to be used only by admins. This is an implementation of the first method of making admin levels (by far the simplest):
Code:
SetAdminCommand(command[], level)
{
	// Get the ID of the command (required):
	new
		id = Command_GetID(command);
	if (level)
	{
		// To be used only by admins.
		// First let no normal people use it.
		Group_SetGlobalCommand(id, false);
		// Then loop through admin levels and set it true on the ones it can be
		// used by and false on the others.
		new
			cl = 0;
		while (cl != MAX_ADMIN_LEVELS)
		{
			new
				Group:group = gAdmins[cl];
			++cl;
			if (cl == level)
			{
				// Set this level as using the command.
				Group_SetCommand(group, id, true);
			}
			else
			{
				// Set this level as not using this command.
				Group_SetCommand(group, id, false);
			}
		}
	}
	else
	{
		// To be used by everyone, so let everyone use it.
		Group_SetGlobalCommand(id, true);
	}
}
You can now load and change admin levels in one place. Updating commands does not require modifying the command in any way - you could even set command permissions in a file or database for dynamic permissions.
Code:
public OnGameModeInit()
{
	// As before.
	new
		name[16];
	for (new i = 0; i != MAX_ADMIN_LEVELS; ++i)
	{
		format(name, sizeof (name), "Admin Level %d+", i + 1);
		gAdmins[i] = Group_Create(name);
	}
	// New.
	// "/kick" is a level 1 admin command.
	SetAdminCommand("kick", 1);
	// "/ban" is a level 2 admin command.
	SetAdminCommand("ban", 2);
	// "/pm" is not an admin command (not required as it's the default).
	SetAdminCommand("pm", 0);
}
In addition, you may have a command which can be used by admins of level 2 AND people in the "police" faction (let's call this command "arrest"):
Code:
new
	Group:gPolice;

public OnGameModeInit()
{
	// As before.
	new
		name[16];
	for (new i = 0; i != MAX_ADMIN_LEVELS; ++i)
	{
		format(name, sizeof (name), "Admin Level %d+", i + 1);
		gAdmins[i] = Group_Create(name);
	}
	SetAdminCommand("kick", 1);
	SetAdminCommand("ban", 2);
	SetAdminCommand("pm", 0);
	// New.
	gPolice = Group_Create("Police faction");
	SetAdminCommand("arrest", 2);
	Group_SetCommand(gPolice, Command_GetID("arrest"), true);
}
Note that "SetAdminCommand" contains a call to "Group_SetGlobalCommand" which prevents people using the command by default. If this was not an admin command but a faction command that call would still be needed.

Players

The one thing missing from the code above is a way to actually add people to a group, fortunately this is easily done with "Group_SetPlayer":
Code:
public OnPlayerConnect(playerid)
{
	if (IsPlayerAdmin(playerid))
	{
		// Add the player to the second admin level.  Default is not a member.
		Group_SetPlayer(gAdmins[2], playerid, true);
		// "false" is used to remove a player.
	}
}
Interestingly, the function "Group_SetPlayer" actually meets part of the requirements of a library to support groups, so you could in theory have groups which are part of other groups - this would be a third way of doing admin commands by simply adding the level 1 admin group to the level 2 admin group.

Other Functions

There are a few other functions available for use. Remember that "Command" can be replaced with "Class" or any other identifier for a library using y_groups:
  • Code:
    Group:Group_Destroy(Group:group);
    Destroys a group. People will loose the permissions afforded to them by this group.
  • Code:
    Group_SetCommandDefault(Group:group, bool:set);
    Normally a new group can not use anything on its own. This function essentially sets the permissions for this group for all commands at once. Note that previously set permissions will be lost.
  • Code:
    Group_SetGlobalCommandDefault(bool:set);
    The global group is everybody on the server. This group has permission to use everything by default, but this default can be changed. If "false" is used it makes jails very easy to create because you don't need to worry about removing the permissions on every command for people in jail - just take them out of all groups.
  • Code:
    bool:Group_GetPlayer(Group:group, playerid);
    Returns whether or not this player is a member of this group.
  • Code:
    Group_SetName(Group:group, name[]);
    Code:
    Group_GetName(Group:group);
    Sets and gets the name of a group.

Iterators
y_groups is foreach compatible:
Code:
foreach (new playerid : Group(gPolice))
{
}
That will loop through all the players in the "gPolice" group.
Code:
foreach (new Group:group : PlayerGroups(playerid))
{
}
That will loop through all the groups that a player is in. Note that foreach was updated a few weeks/months ago to support exactly this because this uses a "Group:" tag.

GROUP_ADD
If you are making a large number of objects (for example), then manually setting the group for every one of them can be tedious. To cover the common case where you want a set of elements in just ONE group (not the global group), there is now a "GROUP_ADD" macro:

PHP Code:
new
    
Group:Group_Create("Admin 1");
GROUP_ADD<g>
{
    
CreateDynamicObject(13370.00.04.00.00.00.0);
    
CreateDynamicObject(13371.00.04.00.00.00.0);
    
CreateDynamicObject(13372.00.04.00.00.00.0);
    
CreateDynamicObject(13373.00.04.00.00.00.0);
    
CreateDynamicObject(13374.00.04.00.00.00.0);
    
CreateDynamicObject(13375.00.04.00.00.00.0);

That will create a line of bins near the centre of the world that ONLY people in group "Admin 1" can see. Note that any objects (or anything else for that matter) created AFTER the "GROUP_ADD" block will have whatever you previously set the default permissions. For reference, the "default default" permissions are that everything is in the "global" group and no other group.

Note that this example used functions from the streamer plugin, which are now supported by y_groups.

Now obviously you can't create commands in this way, because commands already exist in the mode at compile time and are set up automatically by the system. However, you can still "touch" the commands inside the "GROUP_ADD" block to entirely reset their permissions to just the specified group:

PHP Code:
GROUP_ADD(myAdminGroup)
{
    @
YCMD:help;
    @
YCMD:commands;

Using that code, only people in the "myAdminGroup" group will be able to use the commands "help" and "commands". I've frequently tried to think of some way of doing this:

PHP Code:
YCMD:help<gg>(playeridparams[], help)
{

Such that that command only exists for a group called "gg", but as of yet I've not managed it. However, that has given me another idea.

Credits
This post was originally written by Y_Less. I'm merely reposting it for future scripters to develop their knowledge.
Reply
#2

I am aware I bump a 3-year old thread but there are not many threads that document y_groups.

I've decided to use y_commands + y_groups for the administrator team. I'll be using the "levels" method as it is mentioned in this thread but I have got 2 questions.

In SetAdminCommand function, shouldn't it be as the one below?
pawn Code:
if (cl >= level)
If "level" is 1, it should allow any admin with level 1+ to use the command. If it only checks for being equal, it basically ignores the higher-level administrators.

The second question is about setting the administrator to the group. For the case given, there are max 3 admin levels. If someone is set as being level 3, then shouldn't be in all groups (level 1+ group, level 2+ group, level 3 group)?
pawn Code:
for (new i = level_to_be_set - 1; i > -1; i--)
{
    Group_SetPlayer(gAdmins[i], playerid, true);
}
Reply
#3

for example i am making roleplay server, why would i want to use y_groups when i could just use simple variable ?
PHP Code:
enum{
GROUP_POLICE
};
if(
User[playerid][Group]!=GROUP_POLICE)return scm(playerid,-1,"You aren't a cop!"); 
Reply
#4

like your tutorial good job
Reply
#5

nice tutorial corne love'd it

So far achieved
- y_commands (%44.52)
- y_ini (%32.11)
- y_iterate (%12.5)
Reply
#6

Quote:
Originally Posted by Mobtiesgangsa
View Post
nice tutorial corne love'd it

So far achieved
- y_commands (%44.52)
- y_ini (%32.11)
- y_iterate (%12.5)
How do you calculate those numbers? Where is it coming from?
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)