Prerequisites
Before you start scripting, make sure you have:
CoD4 Unleashed Server installed and running
Basic understanding of programming concepts
A text editor (VS Code, Notepad++, or similar)
Access to your server’s file system
Your First Script
Let’s create a simple script that welcomes players when they connect.
Create the Script File
Create a new file at main/maps/mp/gametypes/_welcome.gsc: init() {
level thread onPlayerConnect();
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
player thread onPlayerSpawned();
}
}
onPlayerSpawned() {
self endon("disconnect");
for(;;) {
self waittill("spawned_player");
self iprintlnbold("^2Welcome to the server!");
wait 0.05;
}
}
Load the Script
Add this line to your gametype’s main() function (e.g., in dm.gsc): main() {
// Existing code...
maps\mp\gametypes\_welcome::init();
// More code...
}
Test Your Script
Restart your server and join. You should see the welcome message when you spawn!
Understanding the Code
Let’s break down what each part does:
init() {
level thread onPlayerConnect();
}
The init() function is called when your script loads. It starts the onPlayerConnect() function in a new thread.
for(;;) {
level waittill("connected", player);
player thread onPlayerSpawned();
}
This infinite loop waits for the “connected” event and gets the player entity. The wait is implicit in waittill.
self endon("disconnect");
This ensures the thread ends when the player disconnects, preventing memory leaks.
Working with Player Data
Let’s expand our script to track player statistics:
init() {
level thread onPlayerConnect();
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
// Initialize player data
player.pers["connections"] = 0;
if (!isDefined(player.pers["connections"])) {
player.pers["connections"] = 0;
}
player.pers["connections"]++;
player thread onPlayerSpawned();
player thread showPlayerInfo();
}
}
onPlayerSpawned() {
self endon("disconnect");
for(;;) {
self waittill("spawned_player");
connectionCount = self.pers["connections"];
self iprintlnbold("Welcome! You've connected " + connectionCount + " times.");
wait 0.05;
}
}
showPlayerInfo() {
self endon("disconnect");
wait 3; // Wait 3 seconds after connecting
// Get player information
uid = self getUid();
ip = self getIp();
ping = self getPing();
self iprintln("Your UID: " + uid);
self iprintln("Your IP: " + ip);
self iprintln("Your Ping: " + ping + "ms");
}
File Operations
Let’s create a script that logs player connections to a file:
File operations require proper permissions. Make sure your server has write access to the game directory.
init() {
level thread onPlayerConnect();
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
player thread logConnection();
}
}
logConnection() {
// Get player information
name = self.name;
uid = self getUid();
ip = self getIp();
// Get current time
timestamp = getEpochTime();
timeStr = epochTimeToString(timestamp, 1, "%Y-%m-%d %H:%M:%S");
// Create log entry
logEntry = timeStr + " - " + name + " (UID: " + uid + ", IP: " + ip + ") connected";
// Write to file
file = fs_fopen("connections.log", "append");
if (file) {
fs_writeline(file, logEntry);
fs_fclose(file);
}
}
HTTP Requests
CoD4 Unleashed allows you to make HTTP requests. Here’s an example using the built-in HTTP module:
#include unleashed\http;
init() {
level thread onPlayerConnect();
// Set base URL for all requests
unleashed\http::setOpt("http_base_path", "https://api.example.com");
// Set global headers
unleashed\http::setHeader("Content-Type", "application/json");
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
player thread registerPlayerOnAPI();
}
}
registerPlayerOnAPI() {
self endon("disconnect");
// Prepare player data
playerData = [];
playerData["name"] = self.name;
playerData["uid"] = self getUid();
playerData["ip"] = self getIp();
// Convert to JSON
jsonData = unleashed\json::encode(playerData);
// Make POST request
response = unleashed\http::post("/players", jsonData);
if (isDefined(response)) {
iprintln("Player registered on API: " + response);
}
}
Custom Player Commands
Create a command system for players:
init() {
level thread onPlayerConnect();
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
player thread monitorChat();
}
}
monitorChat() {
self endon("disconnect");
for(;;) {
self waittill("say", message);
// Check for commands
if (getSubStr(message, 0, 1) == "!") {
thread processCommand(message);
}
}
}
processCommand(message) {
// Remove the ! prefix
cmd = getSubStr(message, 1);
args = strTok(cmd, " ");
command = args[0];
switch(command) {
case "help":
self iprintln("Available commands: !help, !stats, !ping, !time");
break;
case "stats":
self showStats();
break;
case "ping":
ping = self getPing();
self iprintln("Your ping: " + ping + "ms");
break;
case "time":
timeStr = epochTimeToString(getEpochTime(), 1, "%H:%M:%S");
self iprintln("Server time: " + timeStr);
break;
default:
self iprintln("Unknown command. Type !help for available commands.");
break;
}
}
showStats() {
kills = self.pers["kills"];
deaths = self.pers["deaths"];
if (!isDefined(kills)) kills = 0;
if (!isDefined(deaths)) deaths = 0;
kd = 0;
if (deaths > 0) {
kd = kills / deaths;
} else {
kd = kills;
}
self iprintln("Stats - K: " + kills + " D: " + deaths + " K/D: " + kd);
}
Physics Modifications
Create a low gravity mode:
init() {
level.lowGravityEnabled = false;
level thread onPlayerConnect();
level thread monitorGravityToggle();
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
player thread applyGravitySettings();
}
}
applyGravitySettings() {
self endon("disconnect");
for(;;) {
self waittill("spawned_player");
if (level.lowGravityEnabled) {
self setGravity(200); // Low gravity
self setJumpHeight(128); // Higher jumps
self iprintln("^3Low Gravity Mode: ON");
} else {
self setGravity(800); // Normal gravity
self setJumpHeight(64); // Normal jumps
}
wait 0.05;
}
}
monitorGravityToggle() {
for(;;) {
// This would be triggered by an admin command
level waittill("toggle_gravity");
level.lowGravityEnabled = !level.lowGravityEnabled;
players = getEntArray("player", "classname");
for (i = 0; i < players.size; i++) {
if (level.lowGravityEnabled) {
players[i] setGravity(200);
players[i] setJumpHeight(128);
players[i] iprintlnbold("^2Low Gravity Mode: ON");
} else {
players[i] setGravity(800);
players[i] setJumpHeight(64);
players[i] iprintlnbold("^1Low Gravity Mode: OFF");
}
}
}
}
Detect when players press specific buttons:
init() {
level thread onPlayerConnect();
}
onPlayerConnect() {
for(;;) {
level waittill("connected", player);
player thread monitorInput();
}
}
monitorInput() {
self endon("disconnect");
for(;;) {
wait 0.05;
// Check for jump button
if (self jumpButtonPressed()) {
// Player is jumping
self iprintln("Jump!");
wait 0.5; // Debounce
}
// Check for sprint button
if (self sprintButtonPressed()) {
// Player is sprinting
// Increase sprint speed
self setMoveSpeed(250);
} else {
self setMoveSpeed(190); // Normal speed
}
// Check for reload button
if (self reloadButtonPressed()) {
self iprintln("Reloading!");
wait 0.5;
}
}
}
Array Utilities
Using the built-in array utilities:
#include unleashed\array;
init() {
// Create an array of player names
playerNames = [];
playerNames[playerNames.size] = "John";
playerNames[playerNames.size] = "Jane";
playerNames[playerNames.size] = "Bob";
playerNames[playerNames.size] = "Alice";
// Filter players whose name starts with 'J'
jPlayers = playerNames unleashed\array::filter(::startsWithJ);
// Map names to uppercase
upperNames = playerNames unleashed\array::mapArray(::toUpper);
// Check if array includes a name
hasJohn = playerNames unleashed\array::includes("John"); // true
// Find index
index = playerNames unleashed\array::indexOf("Bob"); // 2
}
startsWithJ(name, index, array) {
return getSubStr(name, 0, 1) == "J";
}
Debugging Tips
Use Print Statements
iprintln("Debug: Variable value = " + variable);
logPrint("Debug info: " + info);
Check Variable Types
if (!isDefined(variable)) {
iprintln("ERROR: Variable is undefined");
return;
}
if (!isArray(players)) {
iprintln("ERROR: Expected array, got: " + getType(players));
return;
}
Monitor Server Console
Watch the server console for script errors. They will show:
Error message
File name and line number
Stack trace
Use Assertions
assertEx(isDefined(player), "Player must be defined");
assertEx(isPlayer(player), "Entity must be a player");
assertEx(value > 0, "Value must be positive");
Common Pitfalls
Avoid these common mistakes:
Infinite Loops Without Wait
// BAD - Will freeze the server
for(;;) {
doSomething();
}
// GOOD
for(;;) {
wait 0.05;
doSomething();
}
Not Using endon
// BAD - Thread continues after disconnect
monitorPlayer() {
for(;;) {
wait 1;
checkPlayer();
}
}
// GOOD - Thread ends on disconnect
monitorPlayer() {
self endon("disconnect");
self endon("death");
for(;;) {
wait 1;
checkPlayer();
}
}
Not Validating Variables
// BAD
processData() {
value = self.customData;
result = value + 10; // Crashes if customData is undefined
}
// GOOD
processData() {
if (!isDefined(self.customData)) {
self.customData = 0;
}
value = self.customData;
result = value + 10;
}
Project Structure
Organize your scripts logically:
main/
├── maps/mp/
│ ├── gametypes/
│ │ ├── dm.gsc # Deathmatch gametype
│ │ ├── _custom/
│ │ │ ├── _welcome.gsc # Welcome messages
│ │ │ ├── _stats.gsc # Statistics tracking
│ │ │ └── _commands.gsc # Custom commands
│ │ └── _callbacksetup.gsc # Callback setup
│ └── mp_map.gsc # Map-specific script
└── unleashed/
├── http.gsc # HTTP utilities
├── json.gsc # JSON utilities
└── array.gsc # Array utilities
Next Steps
Now that you understand the basics, explore:
GSC Overview Learn more about GSC features and syntax
Server Functions Browse all available server functions
Player Methods Discover player-specific functions
Advanced Topics Learn about JSON, arrays, and HUD elements