No More Room in Hell

No More Room in Hell

46 ratings
VScript guide
By durkhaz
A guide for VScript in No More Room in Hell
2
2
   
Award
Favorite
Favorited
Unfavorite
🌰 VScript in NMRIH
VScript is a virtual machine that adds a scripting language to many source engine titles. The way these are implemented can be quite different depending on the game. This guide focusses on NMRIH's scripting language: Squirrel[developer.electricimp.com]. Even though Squirrel is used in other source engine games, functionality varies greatly.

Squirrel in NMRIH is based on MapBase's[github.com] implementation, which in turn is based on the one of AlienSwarm.

Important VScript caveats in NMRIH:
  • All scripts only run server-side
  • Not all MapBase script functions also exist in NMRIH
  • File system I/O and script server console access is limited

To keep things simple, this guide will refer to NMRIH's implementation of VScript as just "VScript".


What can you use it for?

VScript allows you to enhance and simplify a maps logic. What used to cause a huge headache in the normal hammer I/O system, can now be reduced to a few lines of code.
With the recent addition of mutators, you can now use VScript to create map independant gameplay mods that servers can subscribe to from the workshop.

Use-cases include:
🔗 Useful Links
Below you find a collection of useful ressources. If you're new to VScript. some of these might now make any sense to you yet, but don't worry. They'll be explained in this guide.

VScript Ressources
◻️ NMRIH script functions and constants[pastebin.com]
This list contains all available functions and constants in NMRIH. You can print this list yourself using the console command "script_help"
◻️ NMRIH root table[pastebin.com]
This is the root table of vanilla NMRIH. It consists of all global functions, singletons and classes.
◻️ List of NMRIH script events[pastebin.com]
These are all available events your can register a callback to.

Squirrel Ressources
◻️ Squirrel Programming Guide[developer.electricimp.com]
Describes Squirrel in detail. It covers the points where Squirrel diverges from other languages and where it offers unique functionality and methodologies.
◻️ notepad++ Squirrel syntax highlighting[gist.github.com]
Notepad++ Squirrel language highlighter and VS theme - includes CSGO and L4D2 keywords
◻️ Sublime Squirrel syntax highlighting[packagecontrol.io]
Squirrel Programming Language support for Sublime Text.
🚀 Your first script
Let's make a simple script that prints a text to console. In order to create a script that is recognized by the game, it needs to be in this folder:
steam\steamapps\common\nmrih\nmrih\scripts\vscripts\

Create a text document with the name mapspawn and change file extension to .nut
Open the file and paste this line:
printcl(0,255,255,"Hello VScript");
Then save, and create a game.

It is important you create your own server, because your scripts do not execute when you join a server. You should now see this in your console:



Congratulations! You've made your first script.
­
▶️ Script Execution
The script you just made was executed by the game automatically. However, scripts can be executed in other ways as well. Here is a short overview with their detailed explanations below:



Don't be confused by the term scope yet. It will be explained later on.

‎‎‎‎‎Executing from entities
This is the most common way to run your scripts. Any entity with an edict can define a script file and think function to run. Even though most entities can do this, NMRIH comes with an entity called logic_script that is specifically made for this purpose. You can think of it as a dedicated "script container".



As you can see, it already has the keyvalues Entity Scripts (called "vscripts" without SmartEdit) and Script think function (called "thinkfunction" without SmartEdit) predefined.

Most entities don't have these keyvalues, but they can be added by hand. In the image above we're calling the file vscripts/YourSpawnScript.nut. Entity Scripts are called before the entity is spawned by the game. They can but don't have to define the functions Precache() and OnPostSpawn(). Put this code in YourSpawnScript.nut:
printcl(0,255,255,"Running entity script of "+self); // This function is used to precache assets needed by the entity e.g. PrecacheModel() etc function Precache() { printcl(0,255,255,"Precache of "+self); } // This function is used run code after the entity was spawned function OnPostSpawn() { printcl(0,255,255,"OnPostSpawn of "+self); }

When loading into the map you'll get this:



mapspawn.nut is executed first when the map is loaded. The game then starts spawning all entities. This is when YourSpawnScript.nut with its functions is executed.

Executing from entity output
Another way to execute scripts is from entity inputs. The input RunScriptCodeQuotable looks like this in hammer:



This will print the player that destroyed the crate, because the code is executed on the activator - which is the players weapon in this case. CallScriptFunction works accordingly, but instead of a line of code, it calls a function defined on the target entity.

RunScriptFile runs a script file on the target entity. To understand what this does, try executing YourSpawnScript.nut on an entity that isn't the logic_script it was originally meant for, like so:



It will now print the attacker, but won't execute Precache() and OnPostSpawn() because the entity was already Precached and Spawned before making this call.

Autoexec scripts
You've already learned about mapspawn.nut. It is executed automatically when any map is loaded and BEFORE any entities are spawned. This means it's only useful to do setup that doesn't require any entities. Keep in mind, that this file can only exist once, and will execute on any map.
Therefore it needs to be packed with your map in order to avoid collisions with other mods.
Much for reasonable is the map-specific variant: scripts/vscripts/mapspawn_<mapname>.nut

The other way to have the game execute your script automatically is by having it loaded as a mutator. I'll explain how these work in a later chapter.

Executing from console
You can call scripts directly from console.
This is useful for debugging purposes.
No game restart needed to see the changes made to the script.
🔳 Script Scope
Script scopes are an important concept to understand in order to learn VScript. In the previous chapter I mentioned "calling a function on an entity", but what does that mean?

To explain script scopes, we need to start with tables. A table looks like this:



This table has three slots (MapName, Kills, Extracted). When accessing the key "Extracted" in table "GameState" it will yield "false". This is how it looks like it code:

// Create a slot using the "new slot" operator "<-" and assign table "GameState" GameState <- { MapName = "nmo_subside", Kills = 69, Extracted = false }; // Access value of key printl(GameState.Extracted); // will print "false" to console

Values in a table can be of any data type, even another table. In our example, this could look like this:






Here we added a table called Jumpscares to the table GameState.



Code example:
// Create a slot on "GameState" using the "new slot" operator "<-" and assign table "Jumpscares" GameState.Jumpscares <- { Lobby = "scream.wav", Basement = "spook.wav"}; // Access value of key printl(GameState.Jumpscares.Basement); // will print "spook.wav" to console

Now you might be wondering how we were able to create a new slot on "nothing" for the table GameState in our first code example. The explanation is easy:
The entire VScript environment consists of tables. We created our first table on the root table, which contains every other table, function, variable and so on.

To get a picture of how the root table looks like, you can print it with the console command
script __DumpScope(0,getroottable())
It will print this.[pastebin.com] As you can see, our two nested tables show up. All of the keys on the root table are global, meaning that they can be accessed from any script in the environment.

Now that you know what a table is:
What is a script scope?
❗ A script scope is a table created for an entity containing all its user defined data
You can't store your functions and variables on the entity directly. It's always in the entity's script scope.

We can go back to the example mentioned previously in Executing from entities to show how that looks like. Add these lines to the bottom of YourSpawnScript.nut:
SomeVariable <- "pancakes"; // assign a test variable __DumpScope(0, this); // "this" refers to the table(script scope) of the entity
Note We’ll discuss the keyword "this", as used in the example above, in the next section.
The script will print the following when the entity is spawned:
DispatchOnPostSpawn = (function : 0x057E6CC0) self = ([69] logic_script) SomeVariable = "pancakes" __vname = "00000032_logic_script" PrecacheCallChain = (instance : 0x063B72D8) OnPostSpawnCallChain = (instance : 0x063B7368) OnPostSpawn = (function : 0x057BAC80) DispatchPrecache = (function : 0x057E6C00) Precache = (function : 0x057E6CF0)

This is the script scope / table of the entity with our key SomeVariable and functions OnPostSpawn and Precache assigned. The other keys are created by the game when the script scope is created for the entity. You won't be needing any of these, except self, which holds a reference to the owning entity.

When printing the entire root table again, you'll see this[pastebin.com].
You can now find the entity table "00000032_logic_script(TABLE)" in the root table.

An entities script scope is created either automatically by the game when a script attempts to read or write to it, or manually by calling GetOrCreatePrivateScriptScope() or ValidateScriptScope() on the entity.
GetScriptScope() will return the table of the entity, or null if it has none.

To test this, you can execute this code from anywhere:

Text Version[pastebin.com]

You probably noticed the keyword local that was used when we created the entity. Now that you're familiar with the concepts of a table, the next section will wrap everything up nicely.
👥 Self, this and local
You'll be using these keywords a lot.



this is the context object of a script. For entities this is the script scope. It's implicit within its scope.
self only exists inside entity scripts scopes. They hold the entity class instance. It's part of this.
local is a keyword used to create and assign a local variable. It's local to the block it was created in.

The following piece of code shows how self and local can be used. In it we create a function in the entities script scope to teleports the entity upwards.


Text Version.[pastebin.com]

Note how we don't assign the local variable with the "<-" operator but instead with "=". That's because it's not assigned to any table. Using "<-" on a local variable will throw an error.
🛠️ Entity interaction
You already learned how to execute code from entities using the inputs "RunScriptFile", "RunScriptCode" and "CallScriptFunction". This section covers script alternatives.


Connecting to outputs:

An alternative to "CallScriptFunction" are output connections. These are done in VScript directly.

Given is this trigger with the script interaction.nut attached:


You can set a function inside the entities script scope to be called when an output is triggered. Here we connect the function "IgnitePlayer" to the output "OnStartTouch". Note that the local variables "activator" and "caller" are already set for us by the scripting environment. They are identical to the hammer keywords !activator and !caller you're probably already familiar with.

interaction.nut

Text Version.[pastebin.com]

When a player touches the trigger, he will catch fire for one second. To show how caller works, the trigger is then killed - making it essentially a trigger_once.

Connecting to inputs:

Similar to Outputs, you can define a function to be called when an entities input is fired. The major difference from output connections is, that the functions called from inputs are executed BEFORE the input itself is triggered. This allows you to block an input from being executed.

Given is this button with the script interaction2.nut attached::


Normally, this button would set the player on fire that pressed the button. Let's say we want to prevent infected players from pressing this button. In order for a player to press a button, the player entity fires the input "Use" on the button. We can connect a function to this input using this syntax:

interaction2.nut


When an input is fired, the game first checks if there's a function of the name "Input<Name of Input>()". If the function exists, it'll use its return value (true or false) to determine whether the input should be discarded or not.

Note:
These inputs don't actually have to be recognized by any entity. You can connect arbitrary ones like so:

This will fire InputUpdateTerrain() on the entity.



Firing I/O events:

This is a powerful feature that lets you fire inputs from script directly. It works similar to the console command ent_fire.

Here are a couple use cases:

Text Version[pastebin.com]

You can use this for more than just destroying entities of course. Any output works, even CallScriptFunction. That means you can make delayed script calls, like so:


Test Version[pastebin.com]

🌟 Entity Spawning
There are two main ways to spawn an entity in VScript:
  • SpawnEntityFromTable
  • CreateByClassname

SpawnEntityFromTable

SpawnEntityFromTable is more advanced and should usually be preferred over CreateByClassname. The function takes a classname and a reference to a table containing all its keyvalues it should be spawned with.

The following code calls SpawnEntityFromTable with an inline table to spawn an ambient_generic:

Text Version[pastebin.com]

CreateByClassname

Spawning an entity using CreateByClassname requires a bit more work.
The equivalent of the code above would be this:

Text Version[pastebin.com]
📅 Events and Hooks
Events and Hooks allow you to designate a function to be called when a specific condition is met.
The main difference between events and hooks is, that hooks can have a return value, meaning that you can modify the passed parameters and feed them back into the game.

List of all events[pastebin.com] You can find all available hooks at the bottom of this list[pastebin.com].
(CTRL+F "Hook")

How to use events

You can listen to game events using this function:
Function: ListenToGameEvent Signature: int ListenToGameEvent(string, handle, string) Description: Register as a listener for a game event from script. Params: Event name, function handle, context name

Usage:
mapspawn.nut

Text Version[pastebin.com]

Note: always pass a string unique to your project as context. It makes sure there won't be any collisions with third party scripts that might be using the same events.

If needed, you can later stop listening to events using "StopListeningToGameEvent" by passing the ID returned by "ListenToGameEvent", or "StopListeningToAllGameEvents" - which stops all listeners of a given context.

How to use hooks

Hooks come in two flavors: Entity hooks and Global hooks.
Entity hooks are specific to one entity, while global hooks encompass everything.
An easy example for this:
Entity Hook: "the zombie died", Global Hook: "a zombie died".

Entity hooks require the entity's script scope to work. Let's take this hook as an example:
Hook: CAI_BaseNPC -> QuerySeeEntity Signature: bool CAI_BaseNPC -> QuerySeeEntity(handle [entity]) Description: Called when the NPC is deciding whether to see an entity or not.

The game will attempt to call the function "QuerySeeEntity" on the NPCs script scope. If it exists, it'll pass an invisible parameter "entity" to the function, then use the return value to evaluate if the npc should see the entity or not.

Code example:

Text version[pastebin.com]

This piece of code will make the zombie ignore players that are already infected.


Global hooks on the other hand, are accessed using the singleton "Hooks". You can find it on the root table (see link section):
Hooks(TABLE) { Add = (function : 0x050B14F0) Remove = (function : 0x0554A380) ScopeHookedToEvent = (function : 0x050E7A78) Call = (function : 0x050A80D8) }

Usage:
mapspawn.nut

Text Version[pastebin.com]

Note: Just like events, it's important to pass a context. Hooks can only be removed using Hooks.Remove("YourContext")
🌐 Netprops
Netprops is a singleton that exposes script functions capable of getting and setting networked properties[wiki.alliedmods.net].
This comes in handy when you're trying to access a variable on an entity, but there's no VScript function for it. In order to see which variables exist for an entity, you can use this console command:
"ent_info_datatable" - Usage: ent_info_datatable <class name>

Keep in mind, that only some of these will work. Sadly, it's trial and error.

Quick example: Let's say we want to allow players to goomba stomp zombies. To implement that, we somehow need to find out what a player is standing on. None of the normal VScript function seem very useful here.

When dumping all variables for the player entity, we get this[pastebin.com].
The following variable seems promising:
EHANDLE (13): m_hGroundEntity

The implementation could look like this:

mapspawn.nut

Text Version[pastebin.com]

When a player joins, he's assigned a think function that runs every 0.1 seconds, In that function we fetch the ground entity through NetProps, and if it's a valid npc, we kill it.
­
☣️ Mutators
Mutators can define a script to modify gameplay behavior. They can be compared to sourcemod plugins, except they can be directly subscribed to on the workshop and are less likely to stop working after a game update.

How to setup
Create a text-file in "nmrih/rulesets/mutators/" named "<mutator_name>.txt"
Paste this and fill in your data:
"Ruleset" { "Name" "<mutator_name>" "Author" "<your_name>" "Description" "Test" // your script filename goes here, without the path (should be @ nmrih/scripts/vscripts) "Script" "your_mutator.nut" }

To activate the mutator, use the * button in create game or the console command "sv_mutators <mutator_name>".

The in-game mutator selection menu:


Your script file is automatically loaded by the game once a map is loaded.
Note: It won't unload itself when removed from sv_mutators. It's only unloaded on map-change. That's why it's good practice to define a console command inside your mutator script that allows server operators to disable the mutator at runtime.

7 Comments
Crestfall 21 Nov, 2024 @ 1:41am 
Had a new interesting mod idea (custom item backpacks that extends max inventory) only to find out there's no CNMRiH_Player::SetMaxCarriedWeight. Sad!
Crestfall 20 Nov, 2024 @ 7:02am 
How is this the only documentation for VScript in NMRIH bruh
maybe, y have advice for me, i wanna make script! based on infinity ammo / and another on infinity shots in weapon, i know how to make + mutators combo, to on/off them mb! if i think correct but, i new in Vscripts so, question.. mb y can say me some advice about that, or i need only yr guide at that point?!?!? thnks by the way for the excellent guide!!!:steamthumbsup::steamthis:
NvC_DmN_CH 5 Mar, 2023 @ 4:44pm 
noice
dW 31 Jan, 2023 @ 2:16pm 
wow, this is an excellent guide! well-formatted
樹海 Jukai 25 Jan, 2023 @ 8:42am 
Top notch
Porkchops4lunch 21 Jan, 2023 @ 11:44pm 
If only I could read...