PDA

View Full Version : [Tutorial] [New] How to make capture zone system (no more code repeating)



SergiuOfficial
10-06-17, 21:02
Capture Zone Tutorial
Here you will learn how to make an advance, easy and one line capture zone code.

So how's this different from others? - Here is a list of difference or features:
No more code repeatation (your whole capture zone system is of about 380 lines)
Gives you the ability to add as many capture zones as you want
Progress bar v2 supported
Capture zone textdraw, counts the timeleft
3DText label on the zone checkpoint (zone name and owner team's name display)
Intractive gangzones, so you will notice a border with gangzones
Multi teammates support, more fast capture rate when more players
Messages on attempts to capture, fails, and success
Faster than other scripts
Very easy to edit and less code lines
This is how it looks like: (note the progress bar, textdraw and gangzone):


Required libraries
Interactive Gangzones include
Makes easy to use gangzone functions, and apply borders to zones.
Progress v2 include
Creating a per player based progress bar for showing players capture status.
Streamer plugin
Required to create dynamic Checkpoints, 3D Text labels and Mapicons.

After that, add these libraries in your library inclusion part of code.
Code:
#include <gangzonesc>
#include <progress2>
#include <streamer>

Useful macros
We shall first add some useful macros which will be frequently used in the code.

First is the time in which a capture zone is captured. This is in seconds:
Code:
#define CAPTURE_TIME 30 // time in which a capture zone is captured by a single player
NOTE: this value will be reduced when more player will be in the checkpoint.

Secondly, we are going to use opaque colors for our teams but we don't want them to be transparent in gangzones. So here is a simple function to adjust transparency of any color.
Code:
#define SET_ALPHA(%1,%2) \
((%1 & ~0xFF) | (clamp(%2, 0x00, 0xFF)))

Declaring arrays
Teams array:
We need to create an array for storing team information in it. This will be a constant array which means you cannot change the information once the mode is started.

We need to store 3 things, so for that an enumerator shall be made.
Code:
enum e_TEAM_DATA
{
team_Name[35], // the team's name
team_Color // team's color
};

Once the enum is declared, now we shall create our constant array with all the team's information:
Code:
new const gTeamData[][e_TEAM_DATA] =
{
// Here you shall add all your teams data just like in the below example code
{"India", 0x00FF00FF},
{"Russia", 0xFF0000FF}
};
You can add as many teams as you want because we haven't set any limit for the array. The empty bracket gTeamData[][e_TEAM_DATA] explains it.

NOTE:The team id's must be in such a order that when someone tries to capture, the id must match the index. For example you use teamid "1" for team "Russia", so "Russia" shall be placed in the second line of the array.
Capture zones array:
Now after creating the teams array, we shall add an enumerator for our capture zones. Its quiet easy to understand enums than numbers in an array.

Here is our capture zone enum:
Code:
enum e_CAPTURE_ZONE
{
zone_Name[35], // the capture zone's name
Float:zone_Pos[4], // position of the gangzone (minx, miny, maxx, maxy)
Float:zone_CP[3], // position of the checkpoint
zone_Owner, // the initial owner of the zone (must be a valid team id which is entered in the "gTeamData" array
zone_Attacker, // the attacker's if will be stored in this
zone_Tick[2], // zone's progress will be stored in this (this is split in two parts, the first will go positive and the second will go negative)
zone_Id, // the gangzone id will be stored in this
zone_CPId, // the checkpoint id will be stored in this
zone_Timer, // The timer id will be stored in this
zone_Players // The number of players in the checkpoint will be stored in this
};

Now we create our array. This time as well we are making it constant because we don't require a dynamic capture zone system plus its faster when its constant.
Code:
new const gCaptureZone[][e_CAPTURE_ZONE] =
{
// Here you shall add all your capture zones just like the below examples
{"Big Ear", {-437.5,1513.671875, -244.140625,1636.71875}, {-311.0136,1542.9733,75.5625}, 0},
{"Area 51", {-46.875,1697.265625, 423.828125,2115.234375}, {254.4592,1802.8997,7.4285}, 1}
};
You may notice we don't insert all the entries according to the enum because enumerators allow us to skip the rest and use default values, most likely 0.
Players arrays:
Here is the array/variable in which we will store player capture zone textdraw id:
Code:
new PlayerText:ptxtCapture[MAX_PLAYERS] = {PlayerText:INVALID_TEXT_DRAW, ...};

And here is the variable to store player capture zone progress bar id:
Code:
new PlayerBar:pbarCapture[MAX_PLAYERS] = {INVALID_PLAYER_BAR_ID, ...};

Initializing
Once the server starts, the array data shall be initialized under your gamemode/filterscript initializing process. Here we will create gangzones, checkpoints, 3dtextlabels and mapicons through looping to all capture zone lines.

Here i am doing this under OnGameModeInit:
Code:
public OnGameModeInit()
{
for (new i, j = sizeof(gCaptureZone); i < j; i++) // loop through all capture zones
{
// Create the ganzone
gCaptureZone[i][zone_Id] = GangZoneCreate(gCaptureZone[i][zone_Pos][0], gCaptureZone[i][zone_Pos][1], gCaptureZone[i][zone_Pos][2], gCaptureZone[i][zone_Pos][3], 2.0);

// Create the checkpoint
gCaptureZone[i][zone_CPId] = CreateDynamicCP(gCaptureZone[i][zone_CP][0], gCaptureZone[i][zone_CP][1], gCaptureZone[i][zone_CP][2], 5.0, 0, .streamdistance = 250.0);

// Create the mapicon, we don't need to store it because we aren't going to do anything with it later
CreateDynamicMapIcon(gCaptureZone[i][zone_CP][0], gCaptureZone[i][zone_CP][1], gCaptureZone[i][zone_CP][2], 19, 0, 0, .streamdistance = 700.0);

// Create the 3DTextlabel, we don't need to store it because we aren't going to do anything with it later
// we will set the label string in this syntax <"zone's id"\n"zone's name">
new label[65];
format(label, sizeof(label), "ZONE %i\n%s", i, gCaptureZone[i][zone_Name]);
CreateDynamic3DTextLabel(label, 0xFFFFFFFF, gCaptureZone[i][zone_CP][0], gCaptureZone[i][zone_CP][1], gCaptureZone[i][zone_CP][2], 50.0);

// Set the attacker id to INVALID_PLAYER_ID since no one is connected when server starts
gCaptureZone[i][zone_Attacker] = INVALID_PLAYER_ID;
}
}

Creating player content
We will be creating the player capture HUDS (player textdraw and player progress bar) when player joins.
Gangzones shall be shown only once and i'll do that when player connects.
Code:
public OnPlayerConnect(playerid)
{
// Create capture HUDS
pbarCapture[playerid] = CreatePlayerProgressBar(playerid, 44.000000, 318.000000, 89.500000, 3.700000, -1429936641, CAPTURE_TIME, 0);

ptxtCapture[playerid] = CreatePlayerTextDraw(playerid, 87.000000, 308.000000, "Capturing !...");
PlayerTextDrawBackgroundColor(playerid, ptxtCapture[playerid], 255);
PlayerTextDrawFont(playerid, ptxtCapture[playerid], 1);
PlayerTextDrawLetterSize(playerid, ptxtCapture[playerid], 0.290000, 1.099999);
PlayerTextDrawColor(playerid, ptxtCapture[playerid], -1);
PlayerTextDrawAlignment(playerid, ptxtCapture[playerid], 2);
PlayerTextDrawSetOutline(playerid, ptxtCapture[playerid], 1);

// show all capture zones
for(new i, j = sizeof(gCaptureZone); i < j; i++) // loop through all capture zones and show their gangzones
{
GangZoneShowForPlayer(playerid, gCaptureZone[i][zone_Id], SET_ALPHA(gTeamData[gCaptureZone[i][zone_Owner]][team_Color], 100));
}
}

Starting capture
Previously we used timers to detect if the player entered a checkpoint and then loop through all players to check the count. The fact is that it was foolishness and could be very easily done with OnPlayerEnterDynamicCP, which makes the system very fast, efficient and timer free.

This callback is called when some one enters a dynamic checkpoint, so we will check the player's team and start capture zone if its an anti party.
Code:
public OnPlayerEnterDynamicCP(playerid, checkpointid)
{
for (new i, j = sizeof(gCaptureZone); i < j; i++) // loop through all capture zones
{
if (gCaptureZone[i][zone_CPId] == checkpointid) // if the checkpoint id matches with the zone's CP
{
new buf[150];
if (gCaptureZone[i][zone_Attacker] != INVALID_PLAYER_ID) // if the zone is already under attack
{
if (GetPlayerTeam(playerid) == GetPlayerTeam(gCaptureZone[i][zone_Attacker])) // if the player's team is same to that of player attacking the zone
{
if (IsPlayerInAnyVehicle(playerid)) // Prevent the player from capturing from inside a vehicle
{
return SendClientMessage(playerid, 0xFF0000FF, "ERROR: You cannot capture the zone in a vehicle.");
}

format(buf, sizeof(buf), "Capturing in %i...", gCaptureZone[i][zone_Tick][1]); // format the "buf" with giving timeleft for the capture
PlayerTextDrawSetString(playerid, ptxtCapture[playerid], buf);
PlayerTextDrawShow(playerid, ptxtCapture[playerid]);

SetPlayerProgressBarValue(playerid, pbarCapture[playerid], gCaptureZone[i][zone_Tick][0]); // set the progress bar value to the zone's progress done by the attacker initailly
ShowPlayerProgressBar(playerid, pbarCapture[playerid]);

gCaptureZone[i][zone_Players]++; // increase the count of player capturing so we can increase the rate of capture accordingly

SendClientMessage(playerid, 0x00FF00FF, "Stay in the checkpoint to assist your teammate in capturing the zone.");
}
}
else // if the zone is not attacked by anyone currently
{
if (GetPlayerTeam(playerid) != gCaptureZone[i][zone_Owner]) // the player is an enemy
{
if (IsPlayerInAnyVehicle(playerid)) // Prevent the player from capturing from inside a vehicle
{
return SendClientMessage(playerid, 0xFF0000FF, "ERROR: You cannot capture the zone in a vehicle.");
}

buf[0] = EOS;
strcat(buf, "The zone is controlled by team ");
strcat(buf, gTeamData[gCaptureZone[i][zone_Owner]][team_Name]);
strcat(buf, ".");
SendClientMessage(playerid, 0x00FF00FF, buf);
SendClientMessage(playerid, 0x00FF00FF, "Stay in the checkpoint for "#CAPTURE_TIME" seconds to capture the zone.");

GangZoneFlashForAll(gCaptureZone[i][zone_Id], SET_ALPHA(gTeamData[GetPlayerTeam(playerid)][team_Color], 100)); // flash the gangzone for all player with the enemy's team color

// store attacker id and set the tick rate to inital values
gCaptureZone[i][zone_Attacker] = playerid;
gCaptureZone[i][zone_Players] = 1;
gCaptureZone[i][zone_Tick][0] = 0;
gCaptureZone[i][zone_Tick][1]= CAPTURE_TIME;

// run a timer where we will update the progress of the zone
KillTimer(gCaptureZone[i][zone_Timer]);
gCaptureZone[i][zone_Timer] = SetTimerEx("OnZoneUpdate", 1000, true, "i", i);

// send message to all players that the zone is being attacked
buf[0] = EOS;
strcat(buf, "ZONE: Team ");
strcat(buf, gTeamData[GetPlayerTeam(playerid)][team_Name]);
strcat(buf, " is trying to capture zone ");
strcat(buf, gCaptureZone[i][zone_Name]);
strcat(buf, " against team ");
strcat(buf, gTeamData[gCaptureZone[i][zone_Owner]][team_Name]);
strcat(buf, ".");
SendClientMessageToAll(0xFFFFFFFF, buf);

PlayerTextDrawSetString(playerid, ptxtCapture[playerid], "Capturing in 30..."); // Show player capture zone textdraw
PlayerTextDrawShow(playerid, ptxtCapture[playerid]);

SetPlayerProgressBarValue(playerid, pbarCapture[playerid], gCaptureZone[i][zone_Tick][0]); // set the progress bar value to the zone's progress done by the attacker initailly
ShowPlayerProgressBar(playerid, pbarCapture[playerid]);
}
}

break; // break the loop because there is no other possible zone checkpoint at the same position
}
}
}

Since we use a timer for updating the progress of the capture zone. So we shall declare it first in order to use it.
Code:
forward OnZoneUpdate(zoneid);

You can change the callback name if you wish from both where you set the timer and while using it.

Code:
public OnZoneUpdate(zoneid)
{
// Calculate the rate of capture from the number of players in the zone
switch(gCaptureZone[zoneid][zone_Players])
{
case 1: // if there is only one attacker
{
gCaptureZone[zoneid][zone_Tick][0] += 1;
gCaptureZone[zoneid][zone_Tick][1] -= 1;
}
case 2: // two attacker
{
gCaptureZone[zoneid][zone_Tick][0] += 2;
gCaptureZone[zoneid][zone_Tick][1] += 2;
}
default: //or if 3 or more than 3 attacker in the checkpoint
{
gCaptureZone[zoneid][zone_Tick][0] += 3;
gCaptureZone[zoneid][zone_Tick][1] += 2;
}
}

// updating the progress bar for all the attacker inside the checkpoint
for (new i, j = GetPlayerPoolSize(); i <= j; i++)
{
if (IsPlayerInDynamicCP(i, gCaptureZone[zoneid][zone_CPId]) && ! IsPlayerInAnyVehicle(i) && GetPlayerTeam(i) == GetPlayerTeam(gCaptureZone[zoneid][zone_Attacker])) // if the player is in CP, outside any vehicle and of same team that of attacker
{
SetPlayerProgressBarValue(i, pbarCapture[i], gCaptureZone[zoneid][zone_Tick][0]);
}
}

// If the zone capture time has reached max capture zone time (means zone is captured)
if (gCaptureZone[zoneid][zone_Tick][0] > CAPTURE_TIME)
{
/*
Here you shall give rewards to the player who captured from the beginning
*/
SendClientMessage(gCaptureZone[zoneid][zone_Attacker], 0x00FF00FF, "You have successfully captured the zone, +3 score and +$3000.");
SetPlayerScore(gCaptureZone[zoneid][zone_Attacker], GetPlayerScore(gCaptureZone[zoneid][zone_Attacker]) + 3);
GivePlayerMoney(gCaptureZone[zoneid][zone_Attacker], 3000);

for (new p, l = GetPlayerPoolSize(); p <= l; p++)
{
if (IsPlayerInDynamicCP(p, gCaptureZone[zoneid][zone_CPId]))
{
// hide capture HUDS when the capture is complete
PlayerTextDrawHide(p, ptxtCapture[p]);
HidePlayerProgressBar(p, pbarCapture[p]);

// Check if the player assisted the attacker (give rewards to him/her as well
if (p != gCaptureZone[zoneid][zone_Attacker] && GetPlayerTeam(p) == GetPlayerTeam(gCaptureZone[zoneid][zone_Attacker]) && ! IsPlayerInAnyVehicle(p))
{
/*
Here you shall give rewards to the player who assisted in capturing
*/
SendClientMessage(p, 0x00FF00FF, "You have assisted your teammate to capture the zone, +1 score and +$1500.");
SetPlayerScore(p, GetPlayerScore(p) + 1);
GivePlayerMoney(p, 1500);
}
}
}

// stop the gangzone from flashing
GangZoneStopFlashForAll(gCaptureZone[zoneid][zone_Id]);
// set the gangzone color to the new team's color
GangZoneShowForAll(gCaptureZone[zoneid][zone_Id], SET_ALPHA(gTeamData[GetPlayerTeam(gCaptureZone[zoneid][zone_Attacker])][team_Color], 100));

// Kill the timer since we no longer need it
KillTimer(gCaptureZone[zoneid][zone_Timer]);

new text[150];
strcat(text, "ZONE: Team ");
strcat(text, gTeamData[GetPlayerTeam(gCaptureZone[zoneid][zone_Attacker])][team_Name]);
strcat(text, " has successfully captured the zone ");
strcat(text, gCaptureZone[zoneid][zone_Name]);
strcat(text, " against team ");
strcat(text, gTeamData[gCaptureZone[zoneid][zone_Owner]][team_Name]);
strcat(text, ".");
SendClientMessageToAll(0xFFFFFFFF, text);

gCaptureZone[zoneid][zone_Owner] = GetPlayerTeam(gCaptureZone[zoneid][zone_Attacker]);
gCaptureZone[zoneid][zone_Attacker] = INVALID_PLAYER_ID;
}
}

Stoping capture
This callback is called when some one exits a dynamic checkpoint, so we will check the player's team and stop capture zone if there are no player left in the CP.
Also we will adjust the rate of capture from this callback. Since the player leaves, we can decrease the tick and the rate of capture is self adjusted.

Code:
public OnPlayerLeaveDynamicCP(playerid, checkpointid)
{
for (new i, j = sizeof(gCaptureZone); i < j; i++) // loop through all capture zones
{
if (gCaptureZone[i][zone_CPId] == checkpointid) // if the checkpoint id matches with the zone's CP
{
if (gCaptureZone[i][zone_Attacker] != INVALID_PLAYER_ID) // if the capture zone is being attacked
{
if (GetPlayerTeam(playerid) == GetPlayerTeam(gCaptureZone[i][zone_Attacker])) // if the teams are the same
{
gCaptureZone[i][zone_Players]--; // decrease the number of players count in the CP

if (! gCaptureZone[i][zone_Players]) // if the number of players is 0 now, that means no player in CP
{
SendClientMessage(playerid, 0xFF0000FF, "You failed to capture the zone, there were no teammates left in your checkpoint.");

GangZoneStopFlashForAll(gCaptureZone[i][zone_Id]); // stop the zone to flash for all players

new buf[150];
strcat(buf, "ZONE: Team ");
strcat(buf, gTeamData[GetPlayerTeam(playerid)][team_Name]);
strcat(buf, " failed to capture zone ");
strcat(buf, gCaptureZone[i][zone_Name]);
strcat(buf, " against team ");
strcat(buf, gTeamData[gCaptureZone[i][zone_Owner]][team_Name]);
strcat(buf, ".");
SendClientMessageToAll(0xFFFFFFFF, buf);

gCaptureZone[i][zone_Attacker] = INVALID_PLAYER_ID;
KillTimer(gCaptureZone[i][zone_Timer]);
}
else if (gCaptureZone[i][zone_Attacker] == playerid) // if the count wasn't 0 but the attacker left
{
for (new p, l = GetPlayerPoolSize(); p <= l; p++) // loop through all players
{
if (GetPlayerTeam(p) == GetPlayerTeam(playerid)) // if the player is of same team that of attacker
{
if (IsPlayerInDynamicCP(p, checkpointid)) // if the player is in the checkpoint assisting
{
gCaptureZone[i][zone_Attacker] = p; // set him/her as the beneficial attacker :) (the one who'll get +3 score on capture)
break;
}
}
}
}
}

// hide capture HUDS

PlayerTextDrawHide(playerid, ptxtCapture[playerid]);
HidePlayerProgressBar(playerid, pbarCapture[playerid]);

break;
}
}
}
}

Sample
http://pastebin.com/QaZcdjsh

FAQ.
How to add teams?
Its very simple to add teams into the array. Lets say you have the following team:
Code:
Name - German
Color - 0xFFFF90FF
And say you are using id 0 for this team while setting player's team using SetPlayerTeam.

In order to make the script use this team, add it in the first place since it holds the id 0.
Code:
new const gTeamData[][e_TEAM_DATA] =
{
{"Germany", 0xFFFF90FF}
};
How to add capture zones?
Adding new capture zones is very easy and supports one line one capture zone. Since we using a constant array, we will add the data similar way we add teams.

There are 4 things we need to fill into the array for a new capture zone. These 4 are:
Zone name
Gangzone coordinate (minx, miny, maxx, maxy)
Checkpoint coordinate (x, y, z)
The teamid which will be the initial owner of the zone

NOTE: The teamid shall be matching the index of array "gTeamData". For example you make team 0 the owner of zone A (say), then team 0 should exist in the array "gTeamData". Currently it is team Germany.

Here is an example how you will add a capture zone:
Code:
new const gCaptureZone[][e_CAPTURE_ZONE] =
{
{"Big Ear", {-437.5,1513.671875, -244.140625,1636.71875}, {-311.0136,1542.9733,75.5625}, 0}
};
Here the zone name is "Big Ear" and the owner is 0 (i.e. Germany).
If i put 1 as the owner, but don't create a team in index 1, the script will bug up. So keep care of that.