SAMP Scripting Basics: Creating Your First GamemodeSan Andreas Multiplayer (SA-MP or SAMP) extends Grand Theft Auto: San Andreas into a multiplayer experience with custom servers, modes, and communities. Central to this extensibility is SAMP scripting—using the Pawn-based language to create gamemodes, filterscripts, and server-side logic. This article walks you through the basics of SAMP scripting and guides you to build your first simple gamemode: a team-based capture-the-flag-style mode with spawning, basic HUD, and score tracking.
What is a Gamemode in SAMP?
A gamemode is the main server-side script that defines how your server behaves: player spawning, chat commands, rules, scoring, game loops, and integration of filterscripts. Gamemodes are written in Pawn (.pwn), compiled to .amx, and loaded by the SA-MP server. A gamemode typically implements callbacks provided by the SA-MP server (OnPlayerConnect, OnPlayerDeath, OnGameModeInit, etc.).
Tools and Setup
You’ll need:
- SA-MP server package (official binaries for your OS).
- Pawn compiler (included in the SA-MP server package as pawncc).
- A text editor (VS Code, Sublime, Notepad++).
- Basic familiarity with programming concepts (variables, functions, arrays).
Folder structure (typical):
- server/
- samp-server.exe (or Linux binary)
- server.cfg
- gamemodes/
- mygamemode.pwn
- mygamemode.amx
- filterscripts/
- pawn/
- include/
- pawncc.exe
In server.cfg, point to your gamemode: gamemode0 mygamemode 1
Pawn Language Basics
Pawn is a simple C-like scripting language. Key concepts:
- Native functions: provided by SA-MP (e.g., SendClientMessage, SetPlayerPos).
- Callbacks: special functions called by the server (e.g., OnPlayerConnect).
- Enums and constants: define readable names for values.
- Arrays: used for per-player data.
- Public and forward declarations: for organizing code.
Example syntax:
#include <a_samp> new PlayerScore[1000]; public OnGameModeInit() { print("Gamemode loaded."); return 1; }
Designing Your First Gamemode: Capture Zone Rush (simple CTF-like)
Core features:
- Two teams (Red and Blue).
- Spawn points per team.
- A single flag object located in the center; capture by entering the zone with the opponent’s flag.
- Score tracking and round restart when a team reaches a score limit.
- Basic HUD display: team scores, timer.
High-level flow:
- Initialize game: set team spawn points, create flag zone.
- On player connect: set default team and spawn.
- On player death: respawn after delay.
- On player enter/leave dynamic area: pick up or drop flag.
- Track flag carriers and scores.
Step-by-step Implementation
1) Includes and Globals
Start by including SA-MP definitions and declaring constants and arrays.
#include <a_samp> #define MAX_PLAYERS 500 #define TEAM_RED 1 #define TEAM_BLUE 2 #define SCORE_LIMIT 5 #define ROUND_TIME 600 // seconds new PlayerTeam[MAX_PLAYERS+1]; new PlayerHasFlag[MAX_PLAYERS+1]; new TeamScore[3]; // index 1 = red, 2 = blue new FlagObject; // object id if using an object as a flag new FlagArea; // dynamic area id for flag pickup new FlagCarrier; // playerid holding flag, -1 for none new RoundTimer;
2) OnGameModeInit
Initialize teams, create flag zone and set a timer for the round.
public OnGameModeInit() { TeamScore[TEAM_RED] = 0; TeamScore[TEAM_BLUE] = 0; FlagCarrier = -1; // Create a dynamic area in the center of map for flag pickup // CreateDynamicArea(callback) is provided by some includes/filters; alternatively implement OnPlayerEnterDynamicArea print("Capture Zone Rush initialized."); SetTimerEx("RoundTimerProc", 1000, true, ""); // ticks every second return 1; }
3) Player Connect / Team Assignment / Spawning
Assign players to teams (balance) and spawn them.
public OnPlayerConnect(playerid) { AssignPlayerToTeam(playerid); SpawnPlayerForTeam(playerid); return 1; } AssignPlayerToTeam(playerid) { new red = GetTeamCount(TEAM_RED); new blue = GetTeamCount(TEAM_BLUE); if (red <= blue) PlayerTeam[playerid] = TEAM_RED; else PlayerTeam[playerid] = TEAM_BLUE; return 1; } GetTeamCount(teamid) { new count = 0; for (new i = 0; i <= MAX_PLAYERS; i++) { if (PlayerTeam[i] == teamid) count++; } return count; } SpawnPlayerForTeam(playerid) { if (PlayerTeam[playerid] == TEAM_RED) { SetPlayerPos(playerid, 100.0, 100.0, 20.0); // example coords SetPlayerSkin(playerid, 121); // example skin } else { SetPlayerPos(playerid, -100.0, -100.0, 20.0); SetPlayerSkin(playerid, 75); } ShowPlayerDialog(playerid, ...); // optional return 1; }
4) Flag Pickup and Drop
Use dynamic areas or distance checks. On enter area, give flag to eligible player; on death or disconnect, drop flag.
public OnPlayerEnterDynamicArea(playerid, areaid) { if (areaid == FlagArea && FlagCarrier == -1) { if (PlayerTeam[playerid] != GetOppositeTeamOfFlag()) { // can't pick own flag return 1; } FlagCarrier = playerid; PlayerHasFlag[playerid] = 1; SendClientMessageToAll(0xFFFFFFAA, "Player has captured the flag!"); } return 1; } public OnPlayerDisconnect(playerid, reason) { if (PlayerHasFlag[playerid]) DropFlag(playerid); PlayerTeam[playerid] = 0; return 1; } public OnPlayerDeath(playerid, killerid, reason) { if (PlayerHasFlag[playerid]) { DropFlag(playerid); } // Respawn after delay SetTimerEx("RespawnPlayer", 3000, false, "i", playerid); return 1; } DropFlag(playerid) { PlayerHasFlag[playerid] = 0; FlagCarrier = -1; // Move flag object to drop location SendClientMessageToAll(0xFFAAAAFF, "Flag dropped!"); return 1; }
5) Scoring and Round Management
When a carrier reaches their capture zone (or returns to base), increment score and check for round end.
FlagCaptured(playerid) { new team = PlayerTeam[playerid]; TeamScore[team]++; PlayerHasFlag[playerid] = 0; FlagCarrier = -1; SendClientMessageToAll(0x00FF00FF, "Team scored!"); if (TeamScore[team] >= SCORE_LIMIT) { EndRound(team); } else { ResetFlag(); } return 1; } EndRound(team) { new msg[64]; format(msg, sizeof(msg), "Team %d wins the round!", team); SendClientMessageToAll(0xFFFFFFAA, msg); // Reset scores and respawn players TeamScore[TEAM_RED] = 0; TeamScore[TEAM_BLUE] = 0; ResetAllPlayers(); return 1; }
6) HUD and Scoreboard
Update players with the current score, remaining time, and flag status using ShowPlayerDialog, SendClientMessage, or custom textdraws. Example: periodic broadcast.
public RoundTimerProc() { static timeLeft = ROUND_TIME; timeLeft--; if (timeLeft <= 0) { // round time over — decide winner by score if (TeamScore[TEAM_RED] > TeamScore[TEAM_BLUE]) EndRound(TEAM_RED); else if (TeamScore[TEAM_BLUE] > TeamScore[TEAM_RED]) EndRound(TEAM_BLUE); else { SendClientMessageToAll(0xFFFFFFAA, "Round ended in a draw."); ResetAllPlayers(); } timeLeft = ROUND_TIME; } // Send periodic HUD updates new msg[128]; format(msg, sizeof(msg), "Red: %d Blue: %d Time: %d", TeamScore[TEAM_RED], TeamScore[TEAM_BLUE], timeLeft); SendClientMessageToAll(0xFFFFFFFF, msg); return 1; }
Testing and Debugging
- Use print() on the server console for debugging values.
- Test with multiple clients locally or on a LAN.
- Watch for common errors: off-by-one player indices (players are 0..max), uninitialized variables, and forgetting to include a_samp.
- Use pawncc warnings to catch type issues.
Extending the Gamemode
Ideas to expand:
- Add weapon restrictions and loadouts per team.
- Implement respawn timers and spectator mode.
- Add admin commands for map rotation and force-reset.
- Create filterscripts for chat commands, admin systems, or economy that interact with the gamemode.
Final Notes
This article provided a conceptual walkthrough plus code snippets to build a simple team capture-style gamemode in SAMP. Use the snippets as a scaffold: refine spawn positions, implement robust dynamic area management, and add polish (sound cues, textdraw HUD, lag handling). Scripting in Pawn is lightweight and fast — start small, test often, and iterate.
Leave a Reply