[Tutorial] Creating An Effective Stranger System
#1

CREATING AN EFFECTIVE STRANGER SYSTEM

Section 1.0 - Introduction
It (a Stranger System) nowadays seems to be a must-have feature with servers. But most of them are poorly executed and just show a stranger, not a stranger ID to report and everything else, which gets very confusing for administrators, players, etcetera (or even worse, and just hides their name and that's about it)! So, it is important that you have a stranger system that has both dazzle and functionality. I will cover it all in this tutorial! There are multiple things you should consider:
  • How important is dazzle to me? (How will I spice it up a bit? I.E: Creating a transparent -but darker- textdraw over the entire screen when a player puts on sunglasses)
  • How important is functionality to me? (How much work should admins have to go through to find who a stranger is?)
  • What will the system's purpose be? (Will it be used for ultra-secret factions or such, or for anybody?)
These are very important points, as they will ultimately decide how elaborate you will go with your system. I will cover a standard RP server's needs, not going into a huge amount of detail, but enough for you to get a feel for the system and work with it to meet your server's needs.

Section 1.1 - What We Will Use
We will be using multiple things today, such as:
  • Standard Variables
  • Loops
  • 3DTextLabels
  • ProxDetector
  • Scripting Basics
If you do not understand any of these, I advise you first look up tutorials on these; however you may be able to get by, as I will be in a fair amount of depth with my explanations. We will use a few functions that are not default in SA:MP, so we must make them (or use them from the SA:MP forums).
pawn Code:
stock randomEx(min, max)
{
    //Credits to y_less
    new rand = random(max-min)+min;
    return rand;
}
pawn Code:
forward ProxDetectorS(Float:radi, playerid, targetid);
stock ProxDetector(Float:radi, playerid, string[], col1, col2, col3, col4, col5)
{
    if(IsPlayerConnected(playerid))
    {
        new Float:posx, Float:posy, Float:posz;
        new Float:oldposx, Float:oldposy, Float:oldposz;
        new Float:tempposx, Float:tempposy, Float:tempposz;
        new invehicle[MAX_PLAYERS];
        new virtualworld = GetPlayerVirtualWorld(playerid);
        new interior = GetPlayerInterior(playerid);
        new vehicleid = GetPlayerVehicleID(playerid);
        new ivehicleid;
        if(vehicleid)
        {
            GetVehiclePos(vehicleid,oldposx,oldposy,oldposz);
        }
        else
        {
            GetPlayerPos(playerid, oldposx, oldposy, oldposz);
            vehicleid = GetPlayerVehicleID(playerid);
        }
        while (strfind(string, PlayerName(playerid), false) != -1 && PlayerInfo[playerid][pMask])
        {
            new pos = strfind(string, PlayerName(playerid), false);
            strdel(string, pos, pos + strlen(PlayerName(playerid)));
            new string2[26];
            format(string2, sizeof(string2), "Stranger %d", StrangerID[playerid]);
            strins(string, string2, pos, 512);
        }
        for(new i = 0; i < MAX_PLAYERS; i++)
        {
            if(IsPlayerConnected(i))
            {
                if(!BigEar[i])
                {
                    if(GetPlayerVirtualWorld(i) == virtualworld)
                    {
                        if((GetPlayerInterior(i) == interior))
                        {
                            if(vehicleid)
                            {
                                if(IsPlayerInVehicle(i,vehicleid)) invehicle[i] = 1;
                            }
                            if(!invehicle[i])
                            {
                                if(IsPlayerInAnyVehicle(i))
                                {
                                    ivehicleid = GetPlayerVehicleID(i);
                                    GetVehiclePos(ivehicleid,posx,posy,posz);
                                }
                                else
                                {
                                    GetPlayerPos(i,posx,posy,posz);
                                }
                                tempposx = (oldposx -posx);
                                tempposy = (oldposy -posy);
                                tempposz = (oldposz -posz);
                                if (((tempposx < radi/16) && (tempposx > -radi/16)) && ((tempposy < radi/16) && (tempposy > -radi/16)) && ((tempposz < radi/16) && (tempposz > -radi/16))) SendClientMessage(i, col1, string);
                                else if (((tempposx < radi/8) && (tempposx > -radi/8)) && ((tempposy < radi/8) && (tempposy > -radi/8)) && ((tempposz < radi/8) && (tempposz > -radi/8))) SendClientMessage(i, col2, string);
                                else if (((tempposx < radi/4) && (tempposx > -radi/4)) && ((tempposy < radi/4) && (tempposy > -radi/4)) && ((tempposz < radi/4) && (tempposz > -radi/4))) SendClientMessage(i, col3, string);
                                else if (((tempposx < radi/2) && (tempposx > -radi/2)) && ((tempposy < radi/2) && (tempposy > -radi/2)) && ((tempposz < radi/2) && (tempposz > -radi/2))) SendClientMessage(i, col4, string);
                                else if (((tempposx < radi) && (tempposx > -radi)) && ((tempposy < radi) && (tempposy > -radi)) && ((tempposz < radi) && (tempposz > -radi))) SendClientMessage(i, col5, string);
                            }
                            else SendClientMessage(i, col1, string);
                        }
                    }
                }
                else SendClientMessage(i, col1, string);
            }
        }
    }
    return 1;
}
Section 2.0 - The Variables
pawn Code:
#define DIALOG_MASK 1
#define PURPLE 0xC2A2DAAA

new StrangerID[MAX_PLAYERS]; //Gets/Sets the player's stranger ID when the mask.
new Text3D:StrangerTag[MAX_PLAYERS]; //This will be the 3DTextLabel that shows their stranger ID
new Text3D:StrangerHealth[MAX_PLAYERS]; //This will show the stranger's health & armor (if they have armor)
All three of these variables are vital for the functionality of the stranger system throughout the rest of the tutorial, if you are a beginner to scripting I advise you use variables as I have them here. If you are more advanced you should place these in an enum. If you are inserting this into your own gamemode, you will probably have it where players must buy their mask, you'll have to set that up yourself - it will not be covered in this tutorial.

Section 2.1 - Creating the Command
pawn Code:
CMD:mask(playerid, params[])
{
     if(StrangerID[playerid]) == 0) //Checking to see if the player is already masked...
     {
          ShowPlayerDialog(playerid, DIALOG_MASK, DIALOG_STYLE_LIST, "Which mask?", "Sunglasses\nHandkerchief\nMask (default)", "Select", "Cancel"); //Asking them what style of mask they would like to wear...
     }
     else if(StrangerID[playerid] > 0) //But if the player is already masked...
     {
         SendClientMessage(playerid, -1, "ERROR: You already have a mask on. Use /unmask to take it off!"); //Send this message...
     }
     return 1;
}
Now, let's delve into the code. The first if statement, if(StrangerID[playerid]) == 0), is checking to see if the player is already masked, seeing as StrangerID's default value is zero and will be set to zero when a player unmasks. Then if a player is not masked it will send them a dialog, asking them what style of mask they would like to wear (this is merely for roleplay purposes, you can put your mask function - which we will learn later - here if you do not want to give your players a choice on the type of mask). And if the player is already masked then it will inform them of the command to take off their mask (I personally prefer them to be two separate commands; however you can make it to where it unmasks your player there if you wish).

Section 2.2 - Masking Up
pawn Code:
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
     switch(dialogid)
     {
          case DIALOG_MASK: //If the mask dialog is chosen...
          {
               if(!response) return 0; //If the cancel button is hit then do nothing.
               new choice[64], string[128], bool:found = false; //The bool here is important as it states whether the system has found an available stranger ID for the player.
               for(new i = 0; i < MAX_PLAYERS; i++) //Looping through all the players.
               {
                    new randz = randomEx(1000, 5000);
                    if (StrangerID[i] == randz) //The Stranger ID is currently in use by another player, so we will continue the server for an ID...
                    {
                         while (!found) //When the system has still yet to find an available stranger ID...
                         {
                              randz = randomEx(1000, 5000); //Call another random to try to find an available Stranger ID
                              if (StrangerID[i] == randz) //If the attempted new Stranger ID is already taken...
                              {
                                   found = false; //Let the search continue...
                                   continue; //Continue the search...
                              }
                              else //If the attempted new Stranger ID is available...
                              {
                                   found = true; //The search is over, we have found our Stranger ID!
                                   break; //Get out of this section and continue on in the script.
                              }
                         }
                     }
                     StrangerID[playerid] = randz; //Set the stranger ID to the found random integer
                     ShowPlayerNameTagForPlayer(i, playerid, 0); //Hide the player's name tag for all players.
               }
               switch(listitem) //Switching between the three mask options (no difference between them other than the tag, which is intended for roleplay purposes.
               {
                    case 0: //If they chose sunglasses...
                    {
                         format(string, sizeof(string), "* Stranger %d has put on a pair of sunglasses", StrangerID[playerid]); //Just formatting their RP message for ProxDetector.
                         format(choice, sizeof(choice), "Stranger %d (sunglasses)", StrangerID[playerid]); //Setting their Stranger Tag
                    }
                    case 1: //If they chose a handkerchief...
                    {
                         format(string, sizeof(string), "* Stranger %d has put on a handkerchief", StrangerID[playerid]); //Just formatting their RP message for ProxDetector.
                         format(choice, sizeof(choice), "Stranger %d (handkerchief)", StrangerID[playerid]);//Setting their Stranger Tag
                    }
                    case 2: //If they chose a regular mask...
                    {
                         format(string, sizeof(string), "* Stranger %d has put on a mask", StrangerID[playerid]); //Just formatting their RP message for ProxDetector.
                         format(choice, sizeof(choice), "Stranger %d (mask)", StrangerID[playerid]);//Setting their Stranger Tag
                    }
               }
               StrangerTag[playerid] = Create3DTextLabel(choice, 0xFFFFFFFF, 0, 0, 0, 15.0, 0, 1); //Let's define the player's StrangerTag and set it to their formatted tag from above.
               Attach3DTextLabelToPlayer(StrangerTag[playerid], playerid, 0.0, 0.0, 0.3); //Attach their 3DTextLabel from the line above.
               new Float:arm, Float:hps, healthstring[64]; //Float variables to detect a player's health and the string to format the information to.
               GetPlayerHealth(playerid, hps); //Get the player's health and set it to Float:hps.
               GetPlayerArmour(playerid, arm); //Get the player's armor and set it to Float:arm.
               new hpe = floatround(hps); //Create a new variable (integer) and round the float to a whole number to display the health.
               new arme = floatround(arm); //Create a new variable (integer) and round the float to a whole number to display the armor.
               if(arm != 0) //If the player's armor is greater than zero...
               {
                   format(healthstring, sizeof(healthstring), "HP: %d | Armour: %d", hpe, arme); //Format their health string to include both Health and Armor information.
               }
               else //If the player has no armor.
               {
                   format(healthstring, sizeof(healthstring), "Health: %d", hpe); //Format their health string to only include their health.
               }
               StrangerHealth[playerid] = Create3DTextLabel(healthstring, 0xFFFFFFFF, 0, 0, 0, 15.0, 0, 1); //Create their 3DTextLabel for their health information and set it to the healthstring formatted in the if statement above.
               Attach3DTextLabelToPlayer(StrangerHealth[playerid], playerid, 0.0, 0.0, 0.225); //Attach their health information.
               ProxDetector(15.0, playerid, string, PURPLE, PURPLE, PURPLE, PURPLE, PURPLE); //Send the RP message that the person has put on a mask
          }
          return 1;
     }
     return 0;
}
Now... that may take a while to sink into your head. Basically, what this is doing (check the comments within the code for a line-by-line information guide) is finding a stranger ID that is not already taken between 1,000 and 5,000, which will help conceal the identity of a player and also not cause confusion that the stranger ID is the same as the player's and such. There are 4,000 possibilities, so you will never, ever, run out of stranger ID's (unless SA:MP servers eventually permit 4,001 people on a single server). After it has found an available stranger ID it will set a string based on what you selected for your mask and it will create a 3DTextLabel and attach it to you with the method you are masked (how you are concealing your identity RPly) and your stranger ID (in the case said stranger needs to be reported) and then a string to say you are putting on a mask and what type of mask you are putting on. The bottom portion is creating a seperate 3DTextLabel (StrangerHealth[playerid]) that gathers your health and armor information and connects them to you as well (just below your stranger ID and mask method). This removes the ability for strangers to fail to RP because no one can see their health/armor.

Section 2.3 - Removing The Mask
pawn Code:
stock RemoveMask(playerid)
{
     if(StrangerID[playerid] == 0) return 0; //If the player doesn't have a mask to remove in the first place...
     StrangerID[playerid] = 0; //Setting the stranger ID to zero, as the player no longer needs a Stranger ID.
     Delete3DTextLabel(StrangerTag[playerid]); //Deleting the StrangerTag, as they are no longer a stranger.
     Delete3DTextLabel(StrangerHealth[playerid]); //Deleting the StrangerHealth, as they are no longer a stranger.
     for(new i=0;i<MAX_PLAYERS;i++) //Looping through all the players...
     {
          ShowPlayerNameTagForPlayer(i, playerid, 1); //Show the player's name again to other players.
     }
     return 1;
}
This is a basic function to remove the mask. You can use it as RemoveMask(playerid) at any point you do not want them to be a stranger anymore. I will use this in multiple places, so it's important you understand what it is doing so you know when to use it in your script.
pawn Code:
public OnPlayerDeath(playerid, killerid, reason) //We are using this here because SA:MP (by default) show's player nametags again to everyone when the player dies. You can make a workaround for this; however for the sake of time I will not go into that (merely hide the player's name again and you should be good.)
{
     if(StrangerID[playerid] != 0) RemoveMask(playerid); //Call the RemoveMask function for the player when they die if the player is masked.
     return 1;
}

public OnPlayerDisconnect(playerid, reason) //We are removing it here in order to clean up the 3DTextLabels and permit the StrangerID to be available for new people who /mask.
{
     if(StrangerID[playerid] != 0) RemoveMask(playerid); //Call the RemoveMask function for the player when they disconnect if the player is masked so it cleans the old 3DTextLabels and can open up a StrangerID for the random search.
     return 1;
}

CMD:unmask(playerid, params[]) //And finally, the command to unmask..
{
     if(StrangerID[playerid] == 0) return SendClientMessage(playerid, -1, "You are not wearing a mask, use /mask to put one on!");
     RemoveMask(playerid);
     new string[128], Name[64];
     GetPlayerName(playerid, Name, MAX_PLAYER_NAME);
     format(string, sizeof(string), "* %s removes the clothing from their face, revealing their identity. *", Name); //formatting a RP message to show people nearly as the person removes their mask.
     ProxDetector(15.0, playerid, string, PURPLE, PURPLE, PURPLE, PURPLE, PURPLE); //Send the RP message that the person has removed their mask.
     return 1;
}
And throughout all of these in the pawn box above we have instances where RemoveMask is used and we continue on. RemoveMask merely resets all the mask variables and removes the 3DTextLabels, it doesn't send any RP messages, you have to do that by yourself.

Section 2.4 - Updating the Health String
pawn Code:
public OnGameModeInit()
{
     SetTimer("StrangerHealthUpdate", 1000, true); //You can put everything that is called in this timer in any other 1-second timer to conserve on computer RAM and CPU, I just made a separate one because we only use one timer throughout the script.
     return 1;
}

forward StrangerHealthUpdate();
public StrangerHealthUpdate()
{
     for(new i=0;i<MAX_PLAYERS;i++) //Looping through all the players...
     {
          if(StrangerID[i] != 0) //If the player's Stranger ID does not equal zero...
          {
               new Float:hps, Float:arm, healthstring[64]; //All the stuff from when we got the health before.
               GetPlayerHealth(playerid, hps); //Get their health in Float:hps
               GetPlayerArmour(playerid, arm); //Get their armor in Float:arm
               new armour = floatround(arm); //Round their armor to an integer
               new hltz = floatround(hps); //Round their health to an integer
               if(arm != 0) //If they have greater than zero armor...
               {
                    format(healthstring, sizeof(healthstring), "HP: %d | Armour: %d", hltz, armour); //Format it as such...
               }
               else //If they have zero armor...
               {
                  format(healthstring, sizeof(healthstring), "Health: %d", hltz); //Format it without armor
               }
               Update3DTextLabelText(StrangerHealth[playerid], 0xFFFFFFFF, healthstring); //And now we update the string.
          }
     }
     return 1;
}
So, here we have a timer loop that reoccurs every second that checks if a player is masked and it gets their information and sets it accordingly, this is very similiar to that of when we were originally masking, this merely updates it to make it (almost) constantly accurate. I do not advise using this at any higher of a frequency than what it is now. You can also put this under OnPlayerTakeDamage; however I had issues with the values being slightly inaccurate when placed there. (It may have just been my gamemode as well, seeing as it has edited damages for weapons.)

Section 3.0 - The Rest is up to You
Well, I covered the basic information that you need to know to creating an effective stranger system. Now what you should be able to easily do is create a /reportstranger command that get's the player's name and sends it as a regular report to administrators. And also a simple /viewstrangers command. You should easily be able to make it to where you detect if someone is wearing a mask and to create chat messages with their stranger ID rather than their name or just "stranger". Some suggestions that I will give you to make your stranger system more intricate are:
  • Create items to attach to the player's face based on what kind of mask they put on
  • Create TextDraw effects for things like the sunglasses to actually resemble the effects of sunglasses
  • Create more possibilities for masks
Section 3.1 - Conclusion
I have covered just about everything to show you how I make my stranger systems and to interpret how to make your own or build off of this one. I hope that I have taught you a thing or two within this tutorial; if I taught one person something, then the two hours I wrote writing this was well worth it! This is my first tutorial, so if I made any mistakes within my code please inform me and if I left out some explaining inform me as well. Good luck!
Reply
#2

gg bro looks good
Reply
#3

Thanks, I appreciate it.
Reply
#4

Good work, Nate.
Reply
#5

Wow, this is pretty nice, Keep up the good work
Reply
#6

Now this is a great tutorial. Good to see a tutorial with such explanations

I am going to test it soon, and will let you know if I find any bug.
Reply
#7

Quote:
Originally Posted by LivingLikeYouDo
View Post
Now this is a great tutorial. Good to see a tutorial with such explanations

I am going to test it soon, and will let you know if I find any bug.
I've actually found an annoyance, rather than a bug. I found out it is possible to do it with one textdraw, but I'm working on fixing that now. Thanks for your feedback!
Reply
#8

Quote:
Originally Posted by nmader
View Post
I've actually found an annoyance, rather than a bug. I found out it is possible to do it with one textdraw, but I'm working on fixing that now. Thanks for your feedback!
I will be waiting for it!

By the way, I don't think sunglasses are masks .
Reply
#9

Quote:
Originally Posted by LivingLikeYouDo
View Post
I will be waiting for it!

By the way, I don't think sunglasses are masks .
Well sunglasses, depending on the style you get, can cover your identity quite well as the eyes (and the general area covered by sunglasses) and huge contributing factors to determine what a person looks like.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)