Ads keep us online. Without them, we wouldn't exist. We don't have paywalls or sell mods - we never will. But every month we have large bills and running ads is our only way to cover them. Please consider unblocking us. Thank you from GameBanana <3

How to Make A Mod for NS2.

A Tutorial for Natural Selection 2

No ads for members. Membership is 100% free. Sign up!
_**How to make a mod for Natural Selection 2**_ Good day everybody! This will be a tutorial series on how to mod Natural Selection 2. As there isn't anything out yet, I thought, why not write one myself. The whole series will be about showing you how to start and how to progress while making your own mod. I'm giving you examples based on a new project I'm planing called Randomize Aliens. Ever dreamed of a belly sliding onos, a lerk with blink or a skulk with stomp? Well, this will be the mod for you. As I'm not that familiar with the NS2 code as others might be, some of my definitions may be wrong. So if you want to correct me, feel free to do! If you have any suggestions, criticism, ideas or questions, just tell me and I will add them or answer them. A requirement to work with this series is a solid knowledge of Lua. So if you are not familiar with Lua at all, go and read some Lua tutorials first! I'm also going to talk about server-side mods, but you can use most of it also for client-side mods. The difference between both is, that server-side mods run on a server and can change behaviour on both server and client and client-side mods run only on the client. However, the problem with client-side mods is, that if servers have enabled consistency checking, which is enabled by default and it would be stupid to turn it off, because people could modify whatever they want, client-side mods won't work. This includes changes to Lua code and shaders, but not, for example, models or maps! But, because this tutorial is all about Lua and shaders, it restricts exactly what we want to do. Content: 1. Setup the mod 2. Hello (unknown) world! 3. Hooking into the system 4. Adding new sounds 5. Creating your own entities 6. Profit (well, not really)
1. Setup the mod ================ So the first thing which might be a bit tricky is: How the hell am I going to start? Well, it isn't that difficult. There are a few steps you have to do which are: 1. Find your Natural Selection 2 folder! 2. Create a new mod 3. Create a game setup file 4. Create a Decoda project 4. Create the lua files for the server, client and predict VMs 5. Run it! 6. Publish it! Let's see, the first step should be easy. Your Natural Selection 2 folder is located in your /Steam/common/ folder and if it isn't, go search for it! You will notice that NS2 comes with a lot of handy stuff. Most of the NS2 tools are in this folder and most of the NS2 code and assets are in the ns2 folder. What you want to do is start LaunchPad.exe, so we can go to step 2, which is creating a new mod. The Spark Launch Pad has a list of all the tools which are available with NS2 like Decoda, which we will use for our Lua code later. But as we want to create a new mod, let's click on "Create Mod" on the top left. ![]( "") I'm going to enter "Randomize Aliens" as name, this will be displayed at the workshop later. Your output directory should be located in your NS2 folder and if you haven't created it yet, just create it (mine is called randomizealiens). I haven't yet figured out what the option "kind" does, so let's select "Game" as kind, because we are kinda doing a new game type. Next step is creating a setup file for our new mod. In your game setup file, you can set the name of your mod, the Lua entry files for the server, client and predict VMs and your sound info file. So, go and create a new file called game_setup.xml in your mod directory. The layout looks like this: randomizealiens Natural Selection 2: Randomize ALL the aliens! lua/RandomizeAliensClient.lua lua/RandomizeAliensServer.lua lua/RandomizeAliensPredict.lua lua/Loading.lua sound/NS2.soundinfo The name is displayed in the server browser later and the description is displayed in the NS2 window title, when you open the game. You can use spaces in the name, I've seen some mods doing this, but I prefer not to, because it should be a short game identifier and not the full mod name. As you can see, we are using three custom files for our VMs. But wait, what the hell are VMs? (Note that this is my assumption on VMs, based on what I read so far. So if someone can come up with something better, please go ahead) NS2 is using three different virtual machines for Lua. Each is in control of a different area in the Spark Engine and they are isolated from each other. You can however have the same class on all virtual machines and tell the Spark Engine to sync specific attributes, but I'll get to that at a later point. Server VM: The server VM is running all the server code. If you play a mod on your client and don't host a listen server, you don't run any code from the server VM. Server code is, for example, managing the player states, calculating how much damage a bullet does if it hits or when a round starts and ends. Client VM: The client VM, as opposed to the server VM, is running all the Lua code for the client. If you are running a dedicated server, no client VM for you. Client code is, for example, all the things you see or hear on your screen like the GUI, the HUD, screen effects, sounds. Predict VM: I actually have no idea, what the predict VM is for. It does some fancy tech tree prediction in the NS2 code, but I haven't figured out yet, what it's good for. If someone has an answer to this one, please tell me. So, what exactly do those files contain? Well, they load all the other Lua files our mod needs and that's exactly what we are going to do next. There's one important thing you have to consider: If you load a file (Lua, shader, models, whatever) and it doesn't exist in your mod folder, NS2 will automatically look in the ns2 folder and if it's not in the ns2 folder, it looks in a folder called core (which contains some basic engine functionality). So that's why we prefix all of our files, so we can distinguish them from the NS2 files, except if we want to overwrite the NS2 files. I'm using the NS2 sound info file, because I don't want to modify the sounds (yet). There's also a file for the loading screen when you join a server which is located at lua/Loading.lua. I'm using the default NS2 one, because I don't want to modify the loading screen either. If you want to replace the loading screen, you have to create a file and change the file in the loading element like we did with the VMs. We specified three Lua files in our game setup, so we should create them now. Your Lua code should be located in a folder named lua in your mod folder, so let's create that folder first. You can always change the folder structure in your mod (like renaming lua to src or whatever silly things you can think of), but I usually want to be consistent, so I'm adapting the folder structure of NS2. You can use whatever tool you want to write your Lua code (like Notepad or whatever fancy text editor your want to use), but I think Decoda is a very handy tool (it's not perfect, but it does the job pretty good), that's why I'm showing you, how to work with Decoda. Decoda is an IDE and debugger for Lua. I tried the debugger part, but it didn't work out for me (it was slow, hard to set up, maybe I'm stupid, whatever), so I'm mostly describing the IDE part. You can still debug in game, thanks to hotloading (I'm getting to that later)! If you still want to try the debugger, select Debug in the menu and click on "Start Debugging". The game will load and you will see no menu (that means, that it worked!). You have to open the console and enter "map ns2_summit" or whatever map you want, to start the server. After that, go and debug! You can set breakpoints and do other cool stuff. For the IDE part: We are going to create a Decoda project for our new mod first. Select Project in the menu and click on "Save Project". Save your project in your lua folder and give it an awesome name. ![]( "") Now that we have our Decoda project, let's add some files to it. Select Project and click on "Add New File..." or press Ctrl + Shift + A. We want to create three files: RandomizeAliensClient.lua, RandomizeAliensServer.lua and RandomizeAliensPredict.lua. ![]( "") Make sure to save your project again, after adding new files. Alright, we created our Lua VM files, now what are we going to write into those files? That's easy: // RandomizeAliensClient.lua Script.Load("lua/Client.lua") // RandomizeAliensServer.lua Script.Load("lua/Server.lua") // RandomizeAliensPredict.lua Script.Load("lua/Predict.lua") What the hell, why would we load the default NS2 files? The answer is: Now we can load the default NS2 gameplay code, but also our own files. In those files, we can start to overwrite specific parts of the existing NS2 gameplay code, without touching the whole NS2 code at all. This will be helpful, because if the NS2 code changes it will most likely don't break our code at all. We are going to discuss this more detailed in the third part of this series: Hooking into the system. So there is only one step left, until we see, if everything works: Run it! This one is pretty simple. Create a new shortcut to your NS2.exe, which is located in your NS2 folder, and add the following parameters: "-game randomizealiens -hotload". ![]( "") So what are these parameters doing? The game parameter specifies the name of our mod, which we want to load. The hotload parameter enabled a very cool feature of the Spark Engine: Every time we change a file in our mod folder (Lua, shaders, models), the engine will reload that file for us, so we don't need to restart the game every time we make a change to it. There's probably one thing you should do, when starting the game with hotload enabled: TURN OFF YOUR SOUND! Somehow, if the engine reloads changes to Lua files, the sounds don't get unloaded, but loaded again. This will cause an effect where it plays the current sound louder than before, so after some reloads, your ears start to bleed (no joking). After you made sure, that your sound is turned off, start the game, open your console and type "map ns2_tram". This will load Tram and also your mod, so your are able to test it. ![]( "") Now that we are running our own mod, which is exactly like the normal NS2, but with a new name, we can publish it on the workshop and get all the fame for it! To publish our mod on the workshop, go and open LaunchPad again. In LaunchPad, you have to select your mod first on the top left dropdown. It should be selected by default, if you don't have any other mods. After selecting your mod, click on "Publisher" on the top left. ![]( "") The publisher requires a name for our mod, so let's enter one: Randomize Aliens. The tags will be inserted by the publisher based on our changes (if we only modify Lua files, it will tag it as script, if we also modify shaders, it will additionally tag it as shaders). The description will be shown on your Steam workshop page, so go and describe your mod. Kind is the same as we selected in our mod, so for me it is "Game". You don't have to enter a required build, but you can, if your mod depends on a specific build. You can also select an image, or you can use the default on, if you don't have one. The last thing you can select is the visibility of your mod. We are going to set the visibility to Public, so everyone can download it. After selecting all kinds of things, click on "Publish All". If you, at a later point, just want change the description or upload a picture, click on "Publish Info", which only changes the workshop informations instead of also upload all your mod files. Well, that's it. You have your own mod running and published on the Steam workshop for all the world to see. Isn't that great? Okay, let's be honest, it's kind of sad, because our mod is only a rip off from NS2, so let's go to series 2! 2. Hello (unknown) world! ========================= Here we are, sad, frustrated because we haven't done anything. No, I'm just kidding. Let's get into Lua coding! I want to give you a quick introduction in how you would start your code, based on Hello (unknown) world! What we want to do is print out some cool text, so let's open our Decoda project and create a new file called HelloUnknownWorld.lua: // HelloUnknownWorld.lua Print("Hello Unknown Worlds!") That's it. We are done. See you in the next series... nah. We created a new file which simply prints a text to the console. But we probably should load our script first, but on which VM? Well, on all of them! Let's create a file called RandomizeAliensShared.lua where we load every script which should run on every VM. // RandomizeAliensShared.lua Script.Load("lua/HelloUnknownWorld.lua") Now we need to load that Shared file on every VM, so let's open the three files we created for our VMs and copy/paste: // RandomizeAliensClient/Server/Predict.lua Script.Load("lua/RandomizeAliensShared.lua") There we go. Now we have a central file called RandomizeAliensShared.lua which loads all the scripts which are executed on every VM. That was pretty easy. Let's do something more complicated. We want to welcome the client and thank him for playing our mod. We could do it in two ways: Print a neat message to the user, when he finished loading and enters the ready room, or print it in his console. Well, why not both? - If we want to print a message in the clients console, we need to load the script in the client VM. - If we want to send a message from the server to the client, we need to load the script in the server VM. - Or, we could load one script which does the client code, if the script is loaded on the client VM and server code, if the script is loaded on the server VM.
// RandomizeAliensWelcomeMessage.lua
	local kWelcomeMessage1 = "Hello %s!"
	local kWelcomeMessage2 = "Thanks for playing this awesome mod, I really appreciate it!"

if (Server) then // Send a message to the player! elseif (Client) then Print(kWelcomeMessage1, "Player") Print(kWelcomeMessage2) end
The if/elseif allows us to check if the current script is running on either server or the client VM. Because we already now how to print a message to the console, I just added it. So, what about the server part? If you dig around the NS2 code a bit, you will see that there is a server function called SendNetworkMessage which has a category called "Chat". This seems like the right function to use, so what do we need? Well, first of all, a network message sends a custom type of message to the client and each message can be distinguished by its category. You can pass a table along the network message, which can contain all the information you want to send to the client. In our case, the chat network message contains informations like the location of the marine, the team you want to send the message to or the message prefix. Fortunately, because we have no idea, what the parameters of the network message looks like, every network message must provide kind of an interface with all it's parameters. This is how the chat network message interface looks like: local kChatMessage = { teamOnly = "boolean", playerName = "string (" .. kMaxNameLength .. ")", locationId = "integer (-1 to 1000)", teamNumber = "integer (" .. kTeamInvalid .. " to " .. kSpectatorIndex .. ")", teamType = "integer (" .. kNeutralTeamType .. " to " .. kAlienTeamType .. ")", message = string.format("string (%d)", kMaxChatLength) } Alright, we see the data type of each parameter and also the range or max length of it. Because chat messages are build very often, NS2 provides a function called BuildChatMessage which wraps our parameters into the network message table. // We don't have a location, so let's choose -1 local locationId = -1 local chatMessage = string.format(kWelcomeMessage1, player:GetName()) Server.SendNetworkMessage(player, "Chat", BuildChatMessage(true, "Randomize Aliens", locationId, player:GetTeamNumber(), kNeutralTeamType, chatMessage), true) Looks good... wait, why is the playerName "Randomize Aliens"? Well, every chat needs to display the player name next to the message of the player, but because we are the server and not a player, we can put there whatever we want. So, there's just one thing left. Where the hell do we get the player from and how do we know, when the player enters the ready room for the first time? There's only one thing we can do: Dig through the NS2 code! Notice that every time, you finished loading and enter the ready room, there's a message in the console, something like that: `Client Authed. Steam ID: xyz`? Now we have our first hint. Let's see if we can find that text. Open the Decoda project of NS2 which is located in the ns2/lua/ folder. Now we use the most awesome feature of Decoda: "Find in files". This function has saved me so much time, I can't even tell how much. Let's search for the text we just found and let Decoda look in the current project. Yay, we found something: Find all "Client Authed. Steam ID:" Gamerules.lua:175: Shared.Message(string.format('Client Authed. Steam ID: %s', steamid)) To get to that file and that specific line, just double-click on the line. Let's see what function uses this: function Gamerules:OnClientConnect(client) OnClientConnect, well that sounds promising. Let's do another search for OnClientConnect: ... Gamerules_Global.lua:44: Event.Hook("ClientConnect", OnClientConnect) ... Well hello there. We found an event! Events are always awesome, because you can just hook into them and add functionality. local function OnClientConnect(client) local player = client:GetControllingPlayer() local locationId = -1 local chatMessage = string.format(kWelcomeMessage1, "Player") Server.SendNetworkMessage(player, "Chat", BuildChatMessage(true, "Randomize Aliens", locationId, player:GetTeamNumber(), kNeutralTeamType, chatMessage), true) Server.SendNetworkMessage(player, "Chat", BuildChatMessage(true, "Randomize Aliens", locationId, player:GetTeamNumber(), kNeutralTeamType, kWelcomeMessage2), true) end Event.Hook("ClientConnect", OnClientConnect) So, what is this vodoo magic? There are some events in NS2 that get triggered by the Spark Engine, for example ClientDisconnect is one of those events. You can hook into that event and provide a function which gets passed the client and maybe additional parameters. Each client has a player assigned to them, which we can get with the GetControllingPlayer function. Some people might say: But Rio, there's a function called Player:OnClientConnect, doesn't that function fit more? Yes, it does! But we would need to overwrite that method, to add our changes. Doesn't sound that scary? Alright, let's do this! Adding new sounds This chapter is based on the tutorial by DerAkademiker: Give him some credit for this one. First of all, you need to download Fmod Version 4.38.00. Why this specific version? Well, it was successfully tested with 4.38.00 and other versions may be different to what is described in this tutorial. After installing Fmod, go and start Fmod Designer and create a new project.
Sign up to access this!
  • Devieus avatar
    Devieus username pic Joined 9y ago
    95,203 points Ranked 36th
    70 medals 6 legendary 13 rare
    • 1st Place - Tutorial Contest Medal icon
    • 15+ Entries! GameBanana’s Christmas Giveaway 2015 Medal icon
    • Returned 5000 times Medal icon
    • 15+ Entries! GameBanana’s Christmas Giveaway 2016 Medal icon
    • 2017 Top Contributor Medal icon
    • 15+ Entries! GameBanana’s Christmas Giveaway 2017 Medal icon
    access_time 6y
    Even if you just redistribute, it's perfectly fine to proofread the tutorial and fix the formatting; there's not going to be an inquisition of you do these things and in the end, it would only improve on readability.
    Sentinel of the TV remote avatar
    Sentinel of the TV remote
    URL to post:


Compartir banner
URL de la Imágen
Incrustar código HTML
Código BB incrustado
Markdown embed code


Original Author


KillsteR. avatar
KillsteR. username pic Joined 8y ago
Hammer Studio Flag Affiliation: Hammer Studio
11,801 points Ranked 496th
17 medals 2 rare
  • Submitted 20 Maps Medal icon
  • 6 years a member Medal icon
  • Became a Club Leader Medal icon
  • Reached 1,000 Points Medal icon
  • Reached 2,500 Points Medal icon
  • Reached 7,500 Points Medal icon
KillsteR. avatar
Hammer Studio Flag
Hammer Studio

Are you the owner? Request Ownership
Sign up to access this!
Sign up to access this!
Sign up to access this!


Sign up to access this!



Difficulty Level




  • Share on Reddit
  • Share on Twitter
  • Share on Facebook
  • 0
  • 5.9k
  • 1
  • 6y
  • 6y

More from Submitter

More Other/Misc Tutorials