Make abilities or die trying pt I

Abilities were planned as an important part of the architecture almost ever since the game development was started. Scripted actions, totally customizable and very loosely coupled with the rest of the system, they were supposed to cover everything unit would ever want to do.  Unlimited modding capabilities, fast development, easy changes.

Then the reality kicked in.

2-300x186

As you can see in this advanced picture, the idea was rather simple and obvious: make separate body parts responsible for separate abilities. Then you can walk around the entire construction and obtain the final list of everything this unit can do.

And, of course, any changes in the body plan, including picking up items, dropping items, losing a limb or two, would automatically update the list of available actions:

3-300x186

Care was taken to avoid false additions, so as when soldier picks up item he doesn’t know how to use, or picking up things that only supposed to give abilities when they are properly attached to the body (goggles in your hand will do nothing).

I was able to find a solution for scripting, both support and performance-wise, so scripting is practically a breeze. But as I’ve been implementing various parts of the design, architecture problems started to rise.

There are three main problems: GUI interactions, ability cancelling and state concurrency.

This post is about the first one.

GUI interaction

You need a target to be able to shoot. Ability code, which makes the gun able to shoot, requires either a direction or a target point to throw projectile at. The problem is, in the classic control flow target is specified after the fire command itself:

Player wants to shoot -> press “fire” button -> select target -> confirm

which in our case is:

Player wants to shoot -> select ability “Shoot”-> [ability code] -> select target -> confirm

Do you see the problem? Ability code runs before the target selection, gun shoots before being aimed – it’s not a good practice.

Also, some abilities could target only living things, like telepathic attacks. Some are AoE-based. Would you like to be able to see an approximate spread for your shotgun blasts? What about grenade throwing arcs and more complex weapons (like first X-Com’s blaster bombs with waypoint navigation)?

We will need an additional layer of scripts — GUI helper scripts, which will be our set of tools to get structured input from the player, while providing useful visual information in exchange. These can be as simple as  “Draw a line from A to B”, while potentially capable of displaying, selecting and refining large amounts of data, as well as providing interfaces for tools like motion detector and medikit. We could insert them somewhere in the ability code, and wait for the return value (which is possible if the ability code itself is running in the separate thread – and is safe to suspend)

Player wants to shoot -> select ability “Shoot”-> [ability calls appropriate GUI script and waits for the signal] -> select target -> confirm -> [GUI script fills the request with the target provided by the player, notifies ability] -> [ability code continues]

_

That’s how it was for a while. It works, but there are drawbacks. Let’s look at the ability code:

private void Fire()
{
   if(_inputCalled) return;
   _inputCalled=true;
   var data = new object();
   GameManager.RequestGUIScript("TargetSimple", ParentObject, data);
   Monitor.Wait(data); 
   var target = (GSVector3)data;
   if(target==null) return;
   _inputCalled=false;
   ... create projectile, set velocity towards target
}

What happens here:

At first, we need to ensure we are not waiting for the input. Otherwise return.

Then we create container and ask the GameManager to reroute the call to the GUI script called “TargetSimple” which will fill this container .

Monitor.Wait(object) will wait (indefinitely) for someone to signal. Only after the signal we may continue, ensure that the data is correct, and reset the inputCalled flag.

Finally we can proceed with the ability code.

This piece of code suffers from all kind of possible problems. One mistake on the GUI side, and signal will never be raised, hanging the thread till the end of the game session,  creating the memory leak, and worst of all – blocking the ability from being used again.

Also, it’s not very intuitive, and when you remember you need to write this every time…

Surely, most of this code can be transferred into the RequestGUIScript method, but that won’t give us the guarantee of the signal ever arriving. So I went a little bit further and refactored the starting part of the call pipeline:

layer wants to shoot -> select ability “Shoot”-> [GUI looks for the GUISCRIPT token, runs the  script] -> select target _-> confirm -> [GUI script signals completion] -> [GUI calls for the ability with the targeting data as parameter, already defined]

Additional token for the ability:

[ABILITY:Fire]   <- ability name

[GUISCRIPT:TargetSimple] <- Script that will run on the GUI side, providing input

Ability code:

private void Fire(object data)
{
   var vec = (GSVector3)data;
   ... create projectile, set velocity towards target
}

That’s it – nice and clean. As a bonus, abilities are now decoupled from the data selection, so it’s now possible to use them from the AI, who doesn’t need any GUI scripts at all.