[TulT Programação PAWN Iniciante/Intermediário
Autor: Marlon Lorran | Publicado: 2011-01-14T18:17:00-08:00

Tutorial de Programação Iniciante/Intermediário

Índice:
Iniciando
Strings e Variáveis
Strtok
Multi-Array Strings
Stock Functions
User Databases (dudb)
Y_Objects
Races

1. Iniciando
Neste tutorial vou explicar algumas funções do pawno que algumas pessoas vem me perguntando bastante. Lembrando que este é umtutorial de nível Iniciante/Intermediário. Para aprender o seguinte, você deve saber no mínimo as funções básicas do pawno, e a função de cada callback (você deve no mínimo dos mínimos saber o que é um callback). Logo estarei postando outros tutoriais, de nível Intermediário/Avançado e de Nível Iniciante.


2. Strings e Variáveis
Variáveis são as funções que vão definir o que vai acontecer se esta variável tiver um certo valor. Ex: Se o jogador fulano tiver a profissão número 12, ele vai explodir, etc... E strings são funções que geralmente são criadas para mandar mensagens que tenham variáveis, Ex.: Você está jogando e vai no banco para ver seu saldo. Então aparece a mensagem: "Seu saldo bancário: 504031904913". Sendo que 504031904913 é uma variável que define a quantia que você tem no banco.
Então, vamos começar criando uma variável. Ponha isto no topo do seu gamemode:

CÓDIGO
new kills[MAX_PLAYERS];
new deaths[MAX_PLAYERS];
Estas variáveis vao ser usadas para mostrar o numero de kills e mortes de um player.
Agora vamos botar a seguinte função no OnPlayerDeath:

CÓDIGO
kills[killerid] ++;
deaths[playerid] ++;
E agora vamos fazer um comando para mostrar os status do player. Ponha isso no OnPlayerCommandText:

CÓDIGO
if(strcmp(cmdtext, "/stats", true) == 0)
{
new stringkills[256];
new stringmortes[256];
format(stringkills,sizeof(stringkills), "Kills: %d",kills[playerid]);
format(stringmortes,sizeof(stringmortes), "Mortes: %d",mortes[playerid]);
SendClientMessage(playerid, 0xFFFF00AA, "Seus stats:");
SendClientMessage(playerid, 0xFFFF00AA, stringkills);
SendClientMessage(playerid, 0xFFFF00AA, stringmortes);
return 1;
}
O que acabamos de fazer:
Quando botamos new kills[MAX_PLAYERS]; nós criamos uma variável, que seria executada particularmente para cada player. Após isso, colocamos no OnPlayerDeath kills[killerid] ++; &amp; deaths[playerid] ++;, isso fará com que o assassino ganhe + 1 na variável kills, e o morto + 1 na variavel deaths. Após isso, fizemos um comando que mostraria o numero de kills e mortes do player quando ele digitasse /stats, usando strings. Como você pode perceber, usamos a função format(stringkills,sizeof(stringkills), "Kills: %d",kills[playerid]);, que significa que formatou a string para aparecer o número de kills, e usamos SendClientMessage(playerid, 0xFFFF00AA, stringkills); para mandar uma mensagem com a string para o player.
OBS: Nunca se pode usar uma variável em SendClientMessage sem ter usado uma string!
OBS²: Você não precisa usar strings em todas as mensagens, só precisa usar nas que tem variáveis!


3. Strtok
A função strtok é usada para usar comandos com variáveis, por exemplo /vida id vida. Se nós não usarmos strtok para fazer comandos como /vida id vida, teríamos que fazer um comando para cada id, por exemplo /vida 1 100 depois /vida 2 100 depois /vida 3 100, e isso gastaria muito do tempo e totalmente inútil. Então vamos começar fazendo um comando com strtok.
Adicione esta função (strtok) no final do seu Gamemode/Filterscript:
CÓDIGO
strtok(const string[], &amp;index)
{
new length = strlen(string);
while ((index &lt; offset =" index;"&gt; ' ') &amp;&amp; ((index - offset) &lt; (sizeof(result) - 1))) { result[index - offset] = string[index]; index++; } result[index - offset] = EOS; return result; }

Agora vamos colocar isso em cima de tudo no OnPlayerCommandText:
CÓDIGO
new cmd[256];
new idx;
cmd = strtok(cmdtext, idx);
E agora vamos fazer o comando, que neste caso será o comando /vida id vida.
CÓDIGO
if(strcmp(cmd,"/vida", true)==0 &amp;&amp; IsPlayerAdmin(playerid))
{
new string [256];
new tmp[256];
new plid, vida;
new nomeentregador[MAX_PLAYER_NAME];
new nomerecebedor[MAX_PLAYER_NAME];
tmp = strtok(cmdtext, idx);
if(!strlen(tmp)){
SendClientMessage(playerid, cinza, "[ERRO DE SINTAXE] /vida [id] [vida]");
return 1;
}
plid = strval(tmp);
tmp = strtok(cmdtext, idx);
if(!strlen(tmp)) {
SendClientMessage(playerid, cinza, "[ERRO DE SINTAXE] /vida [id] [vida]");
return 1;
}
vida = strval(tmp);
if(IsPlayerConnected(plid)){
GetPlayerName(plid, nomerecebedor, sizeof(nomerecebedor));
GetPlayerName(playerid, nomeentregador, sizeof(nomeentregador));
format(string, sizeof(string), "O Admnistrador %s setou a vida de %s para %d.", nomeentregador,nomerecebedor,vida);
SendClientMessageToAll(branco, string);
SetPlayerHealth(plid,vida);
return 1;
}
else
{
SendClientMessage(playerid, cinza, "Jogador não conectado.");
return 1;
}
}
Traduzindo:
Fizemos um comando, que se o player for admin, vai setar a vida do jogador escolhido para a vida escolhida. Criamos a variável tmp, que representaria o strtok. Você pode notar que botamos a função if(!strlen(tmp)){, que significa que se o jogador não digitar o id e a vida desejada, vai aparecer a mensagem "[ERRO DE SINTAXE] /vida id vida". Então criamos uma variável chamada vida e uma chamada plid. A variável plid será o id do player escolhido, e a variável vida seria a vida que seria dada ao player. Então usamos uma string para mandar a mensagem "O Admnistrador %s setou a vida de %s para %d." e depois como final do code, usamos o SetPlayerHealth(plid, vida);, que setaria a vida do player desejado.


4. Multi-Array Strings
Multi array strings são strings que carregam várias mensagens ao mesmo tempo, ou vários números, coordenadas, etc... Um exemplo muito conhecido de multi-array string é a multi-array do nome dos carros.

CÓDIGO
new VehicleName[212][0] = {
"Landstalker",//--------&gt; Numero 0
"Bravura",// --------&gt; Numero 1
"Buffalo",// -------&gt; Numero 2
"Linerunner",// -------&gt; E assim vai...
"Pereniel",
"Sentinel",
"Dumper",
"Firetruck",
"Trashmaster",
"Stretch",
"Manana",
"Infernus",
"Voodoo",
"Pony",
"Mule",
"Cheetah",
"Ambulance",
"Leviathan",
"Moonbeam",
"Esperanto",
"Taxi",
"Washington",
"Bobcat",
"Mr Whoopee",
"BF Injection",
"Hunter",
"Premier",
"Enforcer",
"Securicar",
"Banshee",
"Predator",
"Bus",
"Rhino",
"Barracks",
"Hotknife",
"Trailer",
"Previon",
"Coach",
"Cabbie",
"Stallion",
"Rumpo",
"RC Bandit",
"Romero",
"Packer",
"Monster Truck",
"Admiral",
"Squalo",
"Seasparrow",
"Pizzaboy",
"Tram",
"Trailer",
"Turismo",
"Speeder",
"Reefer",
"Tropic",
"Flatbed",
"Yankee",
"Caddy",
"Solair",
"Berkley's RC Van",
"Skimmer",
"PCJ-600",
"Faggio",
"Freeway",
"RC Baron",
"RC Raider",
"Glendale",
"Oceanic",
"Sanchez",
"Sparrow",
"Patriot",
"Quad",
"Coastguard",
"Dinghy",
"Hermes",
"Sabre",
"Rustler",
"ZR-350",
"Walton",
"Regina",
"Comet",
"BMX",
"Burrito",
"Camper",
"Marquis",
"Baggage",
"Dozer",
"Maverick",
"News Chopper",
"Rancher",
"FBI Rancher",
"Virgo",
"Greenwood",
"Jetmax",
"Hotring",
"Sandking",
"Blista Compact",
"Police Maverick",
"Boxville",
"Benson",
"Mesa",
"RC Goblin",
"Hotring Racer",
"Hotring Racer",
"Bloodring Banger",
"Rancher",
"Super GT",
"Elegant",
"Journey",
"Bike",
"Mountain Bike",
"Beagle",
"Cropdust",
"Stunt",
"Tanker",
"RoadTrain",
"Nebula",
"Majestic",
"Buccaneer",
"Shamal",
"Hydra",
"FCR-900",
"NRG-500",
"HPV1000",
"Cement Truck",
"Tow Truck",
"Fortune",
"Cadrona",
"FBI Truck",
"Willard",
"Forklift",
"Tractor",
"Combine",
"Feltzer",
"Remington",
"Slamvan",
"Blade",
"Freight",
"Streak",
"Vortex",
"Vincent",
"Bullet",
"Clover",
"Sadler",
"Firetruck",
"Hustler",
"Intruder",
"Primo",
"Cargobob",
"Tampa",
"Sunrise",
"Merit",
"Utility",
"Nevada",
"Yosemite",
"Windsor",
"Monster Truck",
"Monster Truck",
"Uranus",
"Jester",
"Sultan",
"Stratum",
"Elegy",
"Raindance",
"RC Tiger",
"Flash",
"Tahoma",
"Savanna",
"Bandito",
"Freight",
"Trailer",
"Kart",
"Mower",
"Duneride",
"Sweeper",
"Broadway",
"Tornado",
"AT-400",
"DFT-30",
"Huntley",
"Stafford",
"BF-400",
"Newsvan",
"Tug",
"Trailer",
"Emperor",
"Wayfarer",
"Euros",
"Hotdog",
"Club",
"Trailer",
"Trailer",
"Andromada",
"Dodo",
"RC Cam",
"Launch",
"Polícia (LSPD)",
"Polícia (SFPD)",
"Polícia (LVPD)",
"Police Ranger",
"Picador",
"S.W.A.T. Van",
"Alpha",
"Phoenix",
"Glendale",
"Sadler",
"Trailer de Bagagens",
"Trailer de Bagagens",
"Stair Trailer",
"Boxville",
"Farm Plow",
"Trailer de Utilidades"
};
Como você pode ter visto, esta string tem o nome de todos os carros. Agora vamos usá-la. Vamos fazer um comando /nomeveiculo [id], que vai mostrar o nome do veículo do id escolhido.
CÓDIGO
if (strcmp(cmd, "/nomeveiculo", true)==0)
{
new string [256];
new tmp[256];
new idveiculo;
tmp = strtok(cmdtext, idx);
if(!strlen(tmp)){
SendClientMessage(playerid, cinza, "[ERRO DE SINTAXE] /nomeveiculo [id]");
return 1;
}
idveiculo = strval(tmp);
format(string, sizeof(string), "Nome do veículo: %s.", VehicleName[idveiculo - 400]);
SendClientMessageToAll(branco, string);
return 1;
}
Agora fizemos um comando que vai mostrar o nome do veículo, e usamos VehicleName[idveiculo - 400] para poder mostrar o nome do veículo certo. Ou seja, quando ele digita /nomeveiculo 400, vai mostrar o veículo Nº 0, já que mostra a variável idveiculo - 400.


5. Stock Functions
Stocks e são funções criadas pelo player, para simplificar funções e adicionar novas funções ao sa-mp. Aqui um exemplo bem conhecido de função stock:
CÓDIGO
stock GetXYInFrontOfPlayer(playerid, &amp;Float:x, &amp;Float:y, Float:distance)
{ // Created by Y_Less

new Float:a;

GetPlayerPos(playerid, x, y, a);
GetPlayerFacingAngle(playerid, a);

if (GetPlayerVehicleID(playerid)) {
GetVehicleZAngle(GetPlayerVehicleID(playerid), a);
}

x += (distance * floatsin(-a, degrees));
y += (distance * floatcos(-a, degrees));
}

Esse stock adiciona uma função ao sa-mp, de pegar as coordenadas na frente do jogador. Agora vamos criar nossa própria função no sa-mp, uma função de SETAR o dinheiro das pessoas. (Não tem função de setar, so de dar dinheiro)
CÓDIGO
stock SetarGrana(playerid, grana)
{
ResetPlayerMoney(playerid);
GivePlayerMoney(playerid, grana);
}
Ok, nosso stock function está pronto, ou seja, vai resetar a grana do player e depois vai dar o dinheiro, sendo assim a mesma coisa que setar. Agora nós podemos usar a função que nós criamos em qualquer parte do nosso script. Por Exemplo, tente combinar o que aprendemos antes com os strtok com o SetarGrana, vamos criar um comando /setargrana id grana com a função que acabamos de fazer.
CÓDIGO
if (strcmp(cmd, "/setargrana", true)==0 &amp;&amp; IsPlayerAdmin(playerid))
{
new tmp[256];
new string[256];
new plid, grana;
new nomeentregador[MAX_PLAYER_NAME];
new nomerecebedor[MAX_PLAYER_NAME];
tmp = strtok(cmdtext, idx);
if(!strlen(tmp)){
SendClientMessage(playerid, cinza, "[ERRO DE SINTAXE] /setargrana [id] [grana]");
return 1;
}
plid = strval(tmp);
tmp = strtok(cmdtext, idx);
if(!strlen(tmp)) {
SendClientMessage(playerid, cinza, "[ERRO DE SINTAXE] /setargrana [id] [grana]");
return 1;
}
grana = strval(tmp);
if(IsPlayerConnected(plid)){
GetPlayerName(plid, nomerecebedor, sizeof(nomerecebedor));
GetPlayerName(playerid, nomeentregador, sizeof(nomeentregador));
format(string, sizeof(string), "O Admnistrador %s setou o dinheiro de %s para R$%d.", nomeentregador,nomerecebedor,grana);
SendClientMessageToAll(branco, string);
SetarGrana(plid, grana);
return 1;
}
else
{
SendClientMessage(playerid, cinza, "Jogador não conectado.");
return 1;
}
}
Pronto! Aí está o nosso code simplificado de setar grana(na verdade nem tanto, mas vc pode simplificar mais do jeito q vc quiser fazer seu stock function). Stocks podem fazer qualquer função, e são geralmente usados para fazer includes.

6. User Databases
User databases são includes usados para salvar os dados de algum jogador. Neste tutorial vou explicar como usar o DUDB.
O dudb, na minha opinião é o melhor include de database. Aqui vou explicar como utilizar as funções do dudb:

udb_Create(Filename[], password[]): Função utilizada para criar uma conta no dudb. Veja no exemplo de sistema de registro/login.
udb_UserSet(Filename[], key[], value[]): Função utilizada para salvar algum dado em formato de texto.

udb_UserSetInt(Filename[], key[], value): Função utilizada para salvar algum dado em formato de número INTEIRO. Ex: Variáveis de kills /mortes

udb_UserSetFloat(Filename[], key[], Float:value): Função utilizada para salvar algum dado em formato de float ( como por exemplo, as coordenadas de spawn de algum player )

udb_UserInt(Filename[], key[]): Checa o valor de alguma variável de número INTEIRO no arquivo, utilizada para carregar os dados.

udb_UserFloat(Filename[], key[]): Checa o valor de alguma variável de número FLOAT no arquivo, utilizada para carregar dados de coordenadas.

udb_CheckLogin(Filename[], password[]): Função utilizada nos sistemas de login/registro para ver se a senha está correta.
Bom, agora que sabemos as funções principais do DUDB e pra que elas servem, vamos criar nosso sistema de login/registro.
Vamos adicionar isso no nosso gamemode:
CÓDIGO
//No topo de tudo, junto com os outros includes:
#include&nbsp;
#define amarelo 0xFFFF00AA
#define verde 0x33FF33AA
#define vermelho 0xFF0000AA
#define vermelhoescuro 0xAA3333AA
#define branco 0xFFFFFFAA
#define rosa 0xCCFF00FFAA
#define azul 0x057ABDAA
#define cinza 0xC0C0C0AA
#define yellow 0xFFFF00AA
#define laranja 0xFFA500AA

//agora no OnPlayerConnect:
new nomepl[MAX_PLAYER_NAME];
GetPlayerName(playerid, nomepl, sizeof(nomepl))
if(!udb_Exists(nomepl))
{
SendClientMessage(playerid, azul, "[LOGIN/REGISTRO] Você não está registrado, registre-se com /registrar senha");
}
else
{
SendClientMessage(playerid, azul, "[LOGIN/REGISTRO] Você já está registrado, logue-se com /logar senha");
}

//agora no OnPlayerCommandText
if (strcmp(cmd, "/registrar", true)==0)
{
new tmp[256];
new senha[256];
tmp = strtok(cmdtext, idx);
if(!strlen(tmp))
{
SendClientMessage(playerid,vermelhoescuro,"/registrar [senha] - Exemplo: /registrar 123");
return 1;
}
if(udb_Exists(PlayerName(playerid)))
{
SendClientMessage(playerid, vermelhoescuro, "Você já tem uma conta no Renaissance RPG!");
return 1;
}
format(senha, sizeof(senha), "%s", tmp);
new nomepl[MAX_PLAYER_NAME];
GetPlayerName(playerid, nomepl, sizeof(nomepl));
udb_Create(nomepl, senha);
SendClientMessage(playerid, azul, "Registrado com sucesso!");
return 1;
}
if (strcmp(cmd, "/logar", true)==0)
{
new tmp[256];
new senha[256];
tmp = strtok(cmdtext, idx);
if(!strlen(tmp))
{
SendClientMessage(playerid,vermelhoescuro,"/logar [senha] - Exemplo: /logar 123");
return 1;
}
if(logado[playerid] == 1)
{
SendClientMessage(playerid, vermelhoescuro, "Você já está logado!");
return 1;
}
format(senha, sizeof(senha), "%s", tmp);
if (udb_CheckLogin(PlayerName(playerid),tmp)) {
SendClientMessage(playerid, azul, "Logado com sucesso!");
return 1;
}
else
{
SendClientMessage(playerid, vermelho, "Senha errada!");
}
return 1;
}

Agora acabamos de criar nosso sistema de login/registro. Vamos criar agora um sistema para salvar o dinheiro de alguém.
CÓDIGO
//no OnPlayerDisconnect
new grana;
new nomepl[MAX_PLAYER_NAME];
GetPlayerName(playerid, nomepl, sizeof(nomepl));
grana = GetPlayerMoney(playerid);
udb_UserSetInt(nomepl, "Grana", grana);

//agora no OnPlayerSpawn
new grana;
new nomepl[MAX_PLAYER_NAME];
GetPlayerName(playerid, nomepl, sizeof(nomepl));
grana = udb_UserInt(nomepl, "Grana");
GivePlayerMoney(playerid, grana);
Pronto! Agora criamos um sistema que salva o dinheiro de alguém quando esta pessoa volta ao servidor! Para fazer as variáveis salvarem, é muito simples, basta adicionar o seguinte code no /logar e no OnPlayerDisconnect:
CÓDIGO
//No OnPlayerDisconect
new nomepl[MAX_PLAYER_NAME];
GetPlayerName(playerid, nomepl, sizeof(nomepl));
udb_UserSetInt(nomepl, "Banco", banco[playerid]);/*isso faz salvar, coloquei a variável banco[playerid] por pedido do Cronos, mas dependendo do que vc quiser salvar, mude isto*/

//no sistema de /logar
new nomepl[MAX_PLAYER_NAME];
GetPlayerName(playerid, nomepl, sizeof(nomepl));
Banco[playerid] = udb_UserInt(nomepl, "Banco");//Isso faz carregar, mude a variável Banco[playerid] e "Banco" pela variavel que quiser carregar
Isso vai fazer salvar a variável banco quando o player desconectar, e carregar quando ele logar.

7. Y_Objects
Y_Objects é um streamer de objetos usado para poder exceder o limite de objetos no sa-mp, evitando crashs. O ponto bom dele, é que você pode criar muitos objetos, o ponto ruim é que deixa seu script muito pesado e demora um pouco para carregar os objetos dentro do jogo. Enfim, vamos ao que interessa:
Para começar criando seus objetos, você deve trocar a função CreateObject por CreateDynamicObject, sem mudar as funções dentro dos parênteses. Após ter feito isso, você bota o seguinte no gamemode:

CÓDIGO
//no OnGameModeInit
Object_Object();
AQUI VC POE OS CREATEDYNAMICOBJECT

//no OnPlayerDisconnect
Object_OnPlayerDisconnect();
Pronto! Agora os objetos foram criados.
Caso vc queira saber um pouco mais sobre o Y_Objects, vou colocar aqui o que faz cada função.


Object_AddToPlayer(objectid, playerid) - Adiciona um certo objeto apenas para um player(use uma variável para definir o objeto)
Object_AddToWorld(objectid, world) - Adiciona um certo objeto apenas para um mundo virtual(lembrando que se não definir o world, o objeto será setado no inicial)
Object_AddToAllPlayers(objectid) - Adiciona um certo objeto a todos os players conectados(quem se conectar depois não verá os objetos)
Object_AddToAllWorlds(objectid) - Adiciona um certo objeto a todos os virtual worlds
Object_RemoveFromPlayer(objectid, playerid) - Remove um certo objeto para apenas um player
Object_RemoveFromWorld(objectid, world) - Remove um certo objeto de apenas um world.
Object_RemoveFromAllPlayers(objectid) - Remove o objeto de todos os players
Object_RemoveFromAllWorlds(objectid) - Remove de todos os virtual worlds
Object_SetViewDistance(objectid, distance) - Seta a distância no qual o objeto pode ser visto (normal = 1000)
MoveDynamicObject(object, x, y, z, speed) - Move um objeto
StopDynamicObject(objectid) - Para de mover um objeto
IsValidDynamicObject(objectid) - Checa se o objeto (do Y_Objects) existe
CreateDynamicObject(object, x, y, z, rx, ry, rz) - Cria um objeto do include
DestroyDynamicObject(object) - Destroy o objeto
AttachDynamicObjectToPlayer - Puxa o objeto junto ao jogador, ou seja, se o jogador se mover, o objeto vai junto
AttachDynamicObjectToObject - Puxa o objeto junto ao objeto, ou seja, se o objeto se mover, este objeto vai junto
DettachDynamicObjectFromPlayer - Despuxa o objeto do jogador


8. Races
Para fazer corridas, é um processo meio complicado, estaremos usando a função SetPlayerRaceCheckpoint.
Primeiramente, vou explicar como funciona cada função necessária para se criar uma corrida:
CÓDIGO
SetPlayerRaceCheckpoint(playerid, tipo, x, y, z, nextx, nexty, nextz, tamanho):
No playerid, vamos colocar o id da pessoa que vai receber o checkpoint, no tipo, o tipo do checkpoint, estes são os tipos:
0 - Normal: Checkpoints normais da corrida, aqueles que tem a seta apontando para onde vai ser o próximo checkpoint.
1 - Finish: Checkpoint Final de uma Corrida.
2 - Nothing: Checkpoint que não tem nada dentro dele.
3 - Air: Checkpoint feito para corridas aéreas.
4 - Air Finish: Checkpoint feito para final de corridas aéreas.
Em x,y e z colocaremos as coordenadas onde serão os checkpoints, e em nextx, nexty e nextz, as coordenadas do próximo checkpoint(caso seja um checkpoint de final, deixe as coordenadas do mesmo).
E por último, em tamanho colocaremos o tamanho do checkpoint(o natural é geralmente colocar 8.0)

DisablePlayerRaceCheckpoint(playerid):
Usaremos essa função para trocar de checkpoint quando o player chegar em algum checkpoint da corrida.
Agora vamos fazer nossa corrida. Para simplificar as coordenadas de checkpoint da corrida, vamos usar um Float Multi-Array.
Acrescentaremos isso em cima de OnGamemodeInit:

CÓDIGO
new checkcorrida[MAX_PLAYERS];

new Float:ChecksAvenida[17][3] = {
{2021.9279,851.2713,6.4387},
{1806.2888,851.1154,10.3762},
{1597.7745,850.9503,6.4374},
{1315.8704,886.0849,6.5154},
{1227.3850,1170.5470,6.5219},
{1228.7192,1804.4149,6.4378},
{1229.5260,2301.2026,6.4387},
{1447.5416,2452.2988,6.4377},
{1849.4731,2497.6997,6.5153},
{2287.5569,2607.9063,6.4585},
{2609.7202,2514.3423,5.8201},
{2706.6592,2158.3931,6.4492},
{2705.5173,1674.4374,6.4448},
{2706.8931,1278.5399,6.4373},
{2624.1135,932.2136,6.4433},
{2312.5583,854.1749,6.4375},
{2049.3154,853.4050,6.4387}
};
Pronto! Acabamos de fazer um Multi-Array com as coordenadas dos checkpoints da corrida! Essa corrida está localizada na avenida, em LV. Agora vamos fazer um comando para o player entrar na corrida. (vamos fazer uma corrida singleplayer por enquanto, apenas para explicar como fazer a troca de checkpoints) Logo que ele digitar o comando, o checkpoint 1 da corrida vai aparecer no mapa do player, e quando entrar no checkpoint, vai aparecer o 2, e assim por diante... Então, aqui o code do comando:

CÓDIGO
if(strcmp(cmdtext,"/entrarcorrida",true)==0)
{
checkcorrida[playerid] = 0;
SetPlayerRaceCheckpoint(playerid, 0, ChecksAvenida[checkcorrida[playerid]][0], ChecksAvenida[checkcorrida[playerid]][1], ChecksAvenida[checkcorrida[playerid]][2], ChecksAvenida[checkcorrida[playerid] + 1][0], ChecksAvenida[checkcorrida[playerid] + 1][1], ChecksAvenida[checkcorrida[playerid] + 1][2], 8.0);
return 1;
}
E agora, faremos um sistema para trocar de checkpoint, usando o callback OnPlayerEnterRaceCheckpoint. Caso não o tenha no seu gamemode, adicione.
CÓDIGO
public OnPlayerEnterRaceCheckpoint(playerid)
{
if(checkcorrida[playerid] == 17)
{
SendClientMessage(playerid, 0xFFFF00AA, "Você terminou a corrida!");
DisablePlayerRaceCheckpoint(playerid);
checkcorrida[playerid] = 0;
return 1;
}
else
{
DisablePlayerCheckpoint(playerid);
checkcorrida[playerid] ++;
SetPlayerRaceCheckpoint(playerid, 0, ChecksAvenida[checkcorrida[playerid]][0], ChecksAvenida[checkcorrida[playerid]][1], ChecksAvenida[checkcorrida[playerid]][2], ChecksAvenida[checkcorrida[playerid] + 1][0], ChecksAvenida[checkcorrida[playerid] + 1][1], ChecksAvenida[checkcorrida[playerid] + 1][2], 8.0);
return 1;
}
}
Então, o que acabamos de fazer?
Criamos uma multi-array para simplificar as coordenadas dos checkpoints, e também criamos uma variável para definir o checkpoint que o player está. Então, cada vez que o player entrava em um checkpoint, a variável aumentava e se setava um checkpoint de acordo com o número que estava a variável. Quando a variável chegasse em 17, finalizaria a corrida. Assim fizemos nossa corrida.