Skip to content

Environment Setup

Fabian B edited this page Feb 15, 2020 · 10 revisions

This page covers how to get your development environment set up using sampctl and how to structure your project folders and files.

Workspace

If you don't already have a dedicated workspace for your SA:MP/open.mp-related projects, I strongly recommend you make one, something along the lines of C:/Users/<username>/PawnProjects. It's common practice anyway and keeps everything clean and organized.

Inside your workspace directory, create a new folder for your new project, like my-awesome-gamemode. Then open your Visual Studio Code Editor and open your project folder such that it appears on the left-hand side. Right now, there is nothing in there but that's going to change soon.

Initializing a Package

Once you have created your project folder, use the command line to navigate to it (cd <path>) and initialize a new sampctl package using the following command:

sampctl package init

This runs you through a setup wizard for your project. It should look something like this:

Preferred package format json
Your Name - If you plan to release, must be your GitHub username. <your-github-username>
Package Name - If you plan to release, must be the GitHub project name. <project-name>
Add a .gitignore and .gitattributes files? Yes
Add a README.md file? Yes
Select your text editor vscode
Add standard library dependency? Yes
Scan for dependencies? Yes
Initialise a git repository? Yes
Add a .travis.yml for unit testing? No
No .pwn or .inc files - enter name for new script main.pwn

You'll see sampctl spitting out stuff into the console log. This is the package manager making sure all necessary dependencies for SA:MP are downloaded. Also, you'll notice that your project folder isn't empty anymore:

<project-folder>/
├── .vscode/
├── utils/
├── .gitattributes
├── .gitignore
├── pawn.json
└── README.md

You don't need to care too much about what each and every one of these files/folders mean. The folder structure will be covered a bit later on.

Entry Script

You might remember when we initialized our package, sampctl asked us to enter a script name at the very end which is main.pwn for us. This is your entry script. Now, what is an entry script you might ask.

In order to understand why we need an entry script, it's important to understand that standalone libraries, or "includes", cannot be compiled on their own. They need to be embedded into a script that can actually be run, or to be more precise, a script that contains the main() {} game loop.

So, an entry script is the script that contains the main game loop and includes all of the modules in one place so they can be compiled together. A bare-bones entry script looks like this:

#include <a_samp> // Includes all of SA:MP's natives.

main() {} // The main game loop that makes the server a runnable program.

Create a main.pwn in the root directory of your project with the contents above and you're ready to move on.

Folder Structure

Now, let's explore how you should organize your project folders and files. Go ahead and create the following folder structure:

<project-folder>
├── .vscode/
├── config/
├── dependencies/
├── filterscripts/
├── gamemodes/
├── libs/
├── modules/
├── plugins/
├── scriptfiles/
└── utils/
  • config/ contains global definitions/constants to be used gamemode-wide, i.e. a list of colors with their respective color codes inside of a colors.pwn or cardinal directions inside of a cardinal_dir.pwn.
  • dependencies/ contains dependency folders which are managed by sampctl. Do not touch anything in here. If you, for example, want to use the streamer, you simply let sampctl handle it for you by passing the command sampctl package install samp-incognito/samp-streamer-plugin:v2.9.4 into the command line.
  • filterscripts/ contains filterscripts. Check out the sampctl documentation on how to add filterscripts to your gamemode.
  • gamemodes/ contains the entry script of your gamemode.
  • libs/ contains all non-sampctl ("legacy") libraries.
  • modules/ contains all modules of the gamemode.
  • plugins/ contains all non-sampctl plugins.
  • scriptfiles/ contains files specifically required by non-sampctl plugins.
  • utils/ contains utility/helper functions as well as hooked functions, i.e. a strings.pwn for common string manipulation or a hooks.pwn with a hooked SetPlayerHealth native.

Don't forget to move your existing main.pwn entry script into the gamemodes folder where it belongs!

This change also needs to be reflected in the pawn.json which contains an array of configuration settings. Open it and change the value (right side) of the "entry" field to "gamemodes/main.pwn" and the value of the "output" field to "gamemodes/main.amx". You'll end up with a pawn.json like this:

{
  "user": "<your-github-username>",
  "repo": "<project-name>",
  "entry": "gamemodes/main.pwn",
  "output": "gamemodes/main.amx",
  "dependencies": [
    "sampctl/samp-stdlib"
  ]
}

Inclusion Order

Now that we've established a folder structure and determined where certain files belong, we need to take a closer look at the order in which all of these .pwn files are included such that...

  • the compiler doesn't need to reparse for functions used ahead of declaration and
  • modules can be included in any order.

Here's the order in which you should include your files in your entry script main.pwn:

  1. SA-MP natives (a_samp)
  2. Configuration (#defines)
  3. Utilities (Hooked functions/natives, utility and helper functions)
  4. Dependencies (Third-party libraries downloaded via sampctl, like streamer)
  5. Custom libraries (Your own non-sampctl libraries)
  6. Modules (<module>.pwn)
  7. Main game loop (main() {})

Hooks

In order to be able to write modules as conveniently as possible, we need to introduce one dependency that is absolutely mandatory - y_hooks which is part of Y-Less's YSI Framework. It allows us to hook functions and callbacks while avoiding all the visual clutter associated with classic ALS hooking.

Download the YSI 5.x library using the following command:

sampctl package install pawn-lang/YSI-Includes@5.x

Once it is downloaded, go ahead and include YSI_Coding\y_hooks into your entry script. It should look something like this:

#include <a_samp>

// Configuration

// Utilities

// Dependencies
#include <YSI_Coding\y_hooks>

// Custom libraries

// Modules

main() {}

pawn.json

You may have noticed that your pawn.json also got updated after downloading the YSI Framework. You'll find a new "runtime" object which is basically a JSON-version of the server.cfg file.

One more object we'll need is the "build" object in which we specify the folder for custom libraries (includes in the libs/ folder) and set some of our global compile-time constants like the MAX_PLAYERS value and feature toggles for YSI in the nested "constants" object.

The last thing we'll need to do is add the "local" entry in our root object and set it to true in order to run the package in our local directory.

Your pawn.json should now look something like this:

{
  "user": "<your-github-username>",
  "repo": "<project-name>",
  "entry": "gamemodes/main.pwn",
  "output": "gamemodes/main.amx",
  "local": true,
  "dependencies": [
    "sampctl/samp-stdlib",
    "pawn-lang/YSI-Includes@5.x"
  ],
  "build": {
    "includes": ["libs"],
    "constants": {
      "MAX_PLAYERS": "<maxplayers>",
      "YSI_NO_OPTIMISATION_MESSAGE": "",
      "YSI_NO_VERSION_CHECK": ""
    }
  },
  "runtime": {
    "version": "0.3.7",
    "mode": "<servermode>",
    "echo": "-",
    "rcon_password": "<password>",
    "port": 7777,
    "hostname": "<hostname>",
    "maxplayers": "<playerslots>",
    "filterscripts": [],
    "language": "",
    "mapname": "San Andreas",
    "weburl": "<website>",
    "gamemodetext": "<gamemodetext>",
    "announce": true,
    "lanmode": false,
    "query": true,
    "rcon": false,
    "logqueries": false,
    "sleep": 5,
    "maxnpc": 0,
    "stream_rate": 1000,
    "stream_distance": 200,
    "onfoot_rate": 30,
    "incar_rate": 30,
    "weapon_rate": 30,
    "chatlogging": true,
    "timestamp": true,
    "messageholelimit": 3000,
    "messageslimit": 500,
    "ackslimit": 3000,
    "playertimeout": 10000,
    "minconnectiontime": 0,
    "lagcompmode": 1,
    "connseedtime": 300000,
    "db_logging": false,
    "db_log_queries": false,
    "conncookies": true,
    "cookielogging": false,
    "output": true
  }
}

Clone this wiki locally