Client-side scripting and weapons

Posted by unknownworlds 15 years ago

In the client-server network architecture used by the Source engine, all of the important game decisions are made on the server. These “decisions” include things like what happens when a player is killed, when the game ends, and what is the function of a turret factory. To keep things simple, our original plan for scripting in Natural Selection 2 was limited to scripts that executed on the server and controlled those aspects that didn’t require any complex networking with the client.

Even with server-side-only scripting there is still a lot of power to alter the game; if you’ve ever played on Natural Selection 1 servers that have plug-ins like the Lerk lift or Armory healing you’ve gotten a taste of what can be done with a few server-side tweaks.

However, the ability to easily mod Natural Selection 2 is only one of the reasons we’re integrating a scripting system into the game. Perhaps the most important reason is to speed up our own development, and allow us to quickly and easily prototype different ideas.

With that in mind, server-only scripting felt too limited. When we started building Commander Mode, we decided that user interfaces were a perfect candidate for scripting and it was worth the effort to run scripts on the client as well as on the server.

The nice thing about user interfaces is that they exist only on the client. This means that adding scripts to control them was fairly easy since we could still sidestep the issue of networking in script code. But just as we became dissatisfied with the server-only scripting, it didn’t take long before these non-networked client scripts seemed a little too limited for our tastes.

Although some stuff happens only on the server, and other stuff happens only on the client, a lot of stuff needs to happen on both the server and the client, and data needs to be shared across the network to keep things in sync. This includes anything where the latency (“lag”) of the network connection is going to become evident. The most prominent example is probably the weapons.

If the light machine gun was implemented so that when you pressed the trigger it had to send a message to the server and wait for a response telling it the the gun fired, you’d feel a noticeable delay and would probably have a pretty hard time hitting the Skulk that’s about to bite your head off. Instead, the Source engine runs the weapon firing code instantly on your client (where you see the effect immediately), and once the server receives the message that the gun was fired it also runs the weapon firing code doing the actual damage to your target. The Source networking system does some clever stuff where it rewinds time and does the firing test on the server with the skulk at the position he was when you actually pulled the trigger. This gives the guy with a 1000 ping a chance to make it to another tour of duty.

Since Garry is also using Lua with the Source engine, I imagine that some people probably think that there’s a switch you flip that makes the two work together nicely. Unfortunately this isn’t the case, which is why we started off with the more limited approach to scripting. But full scripting of weapons became too compelling to pass up, so we bit the bullet (no pun intended) and have been working on getting those objects that exist on both the client and server scriptable.

The Source engine has lots of neat little macros that make it pretty easy to setup weapons in C++ code. These macros are magical single lines of code that expand into a whole mess of complex code when you compile your game. Since a lot of this exists in the publicly available game code, you can check out the Source wiki if you’re interested in getting a deeper look at what exactly we’re talking about.

With a scripting approach, the weapon code doesn’t ever get compiled, which is one of the big time savers with using a scripting language. In fact the game doesn’t even know those weapons exist until the the map is loaded. These are good things in terms of development, but it means those handy macros that the Source engine provides don’t work at all when building a weapon in script.

So the task that’s consumed me for the past week is delving through all of that networking code, figuring out how it works, and building a layer between Lua and Source that replicates the functionality of the C++ macros. This is one of those times that working on a stand-alone game with full access to the engine code has really come in handy, since ultimately we needed to modify how some of the low level networking code works to allows us to build that layer. It’s also quite nice be able to fire off an email to Valve and get a quick response from the guy who wrote that networking code.

Right now we’re at the stage where we’ve got our scripted entities hooked into the core network system. With that in-place, the engine will instantiate client-side versions of the entities when the server tells them they’ve come into the player’s view. Those entities are hooked into script files which control how the entity behaves on the client and the server. In practice what that means is we’ve got a scripted pistol up and running that works as you’d expect with the appropriate client-side prediction and the code running on both the client and the server. The screen shot shows the current code for the pistol inside our Lua IDE, called Decoda.

Sharp readers will notice that the pistol code doesn’t include any mention of ammo or reloading. Adding in network propagation of variables – such as the amount of ammo left in the gun – is the next step for our Lua integration. This builds on the core changes we’ve just finished implementing, so hopefully things will continue smoothly. Smooth or not, I’m sure we’ll be writing more about that in a future blog.

Comments are closed.