Working on a script library for Alien Swarm has given me a lot of inspiration for how to really milk the most value out of Squirrel’s features. It’s also made me realise how much work it is to get to grips with scripting.

Reading the manual can be rather tedious and isn’t the easiest way to learn how to script. I’d like to demonstrate some techniques using natural game-centric examples. Lets start with Generators.

Problem

A common task involves iterating (stepping though) lists of entities. The game provides access to these entities through the Entities instance.

Here’s an example where we try to stun every alien drone:

local drone = null
while (drone = ::Entities.FindByClassname(drone, "asw_drone")) {
drone.ElectroStun(10.0)
}

While this does work, there’s a lot of potential for error with defining a variable outside the loop and repeating drone so many times. We can use a generator to improve the code.

Generators

A generator is a function which can pause itself, yielding a value, and have its execution resumed later. This makes them very useful for generating a series of values, or traversing other objects sequentially.

Here’s a generator that represents a sequence of integers:

function range(start, end) {
for (local i = start; i < end; ++i)
yield i
}

The yield statement acts like return but pauses the function instead of ending it completely. The generator can then be resumed, from where it yielded, using the resume keyword. The generator permanently ends when it reaches a return statement. Note that reaching the bottom of a function is an implied return.

Here’s the generator in action. It should print the numbers 5 to 9:

local series = range(5, 10)
while (series.getstatus() != "dead")
::printl(resume series)

The above example is contrived to show how generators work. In practice, you would use a foreach loop, which automatically resumes generators for you:

foreach (i in range(5, 10))
::printl(i)

Iterating

Recall our original problem. We can use a generator to wrap calls to Entities.FindByClassname:

function entities_with_classname(classname) {
local entity = null
while (entity = ::Entities.FindByClassname(entity, classname))
yield entity
}

Now we can use a foreach to iterate through all drones:

foreach (drone in entities_with_classname("asw_drone"))
drone.ElectroStun(10.0)

Doing it this way reduces the amount of repetition and neatly contains all variables within the loop. Errors are much less likely. It’s also a lot easier to read.

Conclusion

You might not see much value in doing this if you’re only doing one loop, but if you’re doing a lot, it pays off. This is the kind of thing that will make it into my script library.