(lightbulb) Looking for info on the NEXUS faction in the game? It's in the Warzone Encyclopaedia

Introduction

What problem does the NEXUS Project solve?

 

Why start this project?

I've been dabbling with Warzone scripting for some time. But for some reason I'd not managed to release any useful mods, something just didn't feel right.

To give myself a break, I started on three mammoth documentation projects: Javascript APIWarzone Modding Guide and Warzone 2100 Encyclopaedia. These three projects are all still ongoing, and probably always will be because the game keeps evolving. However, whilst working on those projects, I started to get a much deeper understanding of Warzone – it's gameplay, background story and customisations.

In May 2012, I was building an Audio Directory and Video Directory to help myself gain a better understanding of the Warzone story and timeline, with an ultimate aim to making myself useful should I get the chance to participate in development of a custom Campaign. I'd been watching other modding projects take shape and develop in the forums and was keen to get involved.

Without warning, Shadow Wolf TCJ released an Enhanced SitRep Mod - just a component of his vast Contingency Mod. The SitRep mod added loads of new "situation reporting" audio messages to the game, making the gameplay more engaging. Having just completed the Audio Directory, I realised that I could probably take the mod to another level by adding a load of new reports.

Initially, I focussed on refactoring the SitRep mod - splitting player initialisation and situation reporting out in to separate JS includes to make the main rules.js code easier to read. I then started adding new situation reports and that's when suddenly everything clicked in to place.

The problem...

I found myself doing certain coding tasks over and over again, particularly around object classification and message playing. The situation reporting code was infested with huge numbers of nested "if..then..else" or "switch..case..default" statements, making it time consuming to write and debug, and making it increasingly unreadable:

// original code that I was trying to extend...
function eventObjectSeen(viewer, detectedObj)
{
  if (detectedObj.player != selectedPlayer && allianceExistsBetween(selectedPlayer,detectedObj.player) != true && (viewer.player == selectedPlayer || allianceExistsBetween(selectedPlayer,viewer.player))) //Does the viewer belong to either the player or one of his/her allies, and is the detected object an enemy object?
  {
    if (detectedObj.type == DROID) //Enemy unit detected
    {
      if (detectedObj.player == scavengerPlayer) //Scavenger unit detected
      {
        if (gameTime > lastDetectTime[0] + 1000)
        {
          playSound("pcv373.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[0] = gameTime;
      }
      else if (detectedObj.droidType == DROID_TRANSPORTER || detectedObj.droidType == DROID_SUPERTRANSPORTER) //Enemy transport detected
      {
        if (gameTime > lastDetectTime[1] + 1000)
        {
          playSound("pcv388.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[1] = gameTime;
      }
      else if (detectedObj.isVTOL) //Enemy VTOL detected
      {
        if (gameTime > lastDetectTime[2] + 1000)
        {
          playSound("pcv388.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[2] = gameTime;
      }
      else if (detectedObj.hasIndirect && detectedObj.range > 2000) //Enemy artillery detected. NOTE: We need to exclude the more short-ranged indirect-fire units, like Mini-Rocket Arrays.
      {
        if (gameTime > lastDetectTime[3] + 1000)
        {
          playSound("pcv387.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[3] = gameTime;
      }
      else
      {
        if (gameTime > lastDetectTime[4] + 1000) //Play generic enemy unit detected sound
        {
          playSound("pcv378.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[4] = gameTime;
      }
    }
    else if (detectedObj.type == STRUCTURE && detectedObj.stattype != WALL && detectedObj.stattype != GATE) //Enemy structure detected (excluding walls or gates)
    {
      if (detectedObj.player == scavengerPlayer && !(detectedObj.hasIndirect && detectedObj.range > 2000)) //Scavenger structure detected
      {
        if (detectedObj.stattype == DEFENSE) //Scavenger defense detected
        {
          if (gameTime > lastDetectTime[5] + 1000)
          {
            playSound("pcv375.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
          }
          lastDetectTime[5] = gameTime;
        }
        else //Scavenger base detected
        {
          if (gameTime > lastDetectTime[6] + 1000)
          {
            playSound("pcv374.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
          }
          lastDetectTime[6] = gameTime;
        }
      }
      else if (detectedObj.hasIndirect && detectedObj.range > 2000) //Enemy artillery detected. NOTE: We need to exclude the more short-ranged indirect-fire units, like Mini-Rocket Arrays.
      {
        if (gameTime > lastDetectTime[7] + 1000)
        {
          playSound("pcv387.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[7] = gameTime;
      }
      else //Enemy player's structure detected
      {
        if (gameTime > lastDetectTime[8] + 1000)
        {
          playSound("pcv379.ogg", detectedObj.x, detectedObj.y, detectedObj.z);
        }
        lastDetectTime[8] = gameTime;
      }
    }
  }
}

If you look at most Warzone scripts, they are filled with lots of stuff that obfuscates code.

The solution...

There was so much stuff wrapping each playSound() call that I couldn't see what was going on. So, my first task was to reduce that down by creating an audioAlert(filename,pos,despam) function - it abstracted away the "gameTime > lastDetectTime..." stuff, and also played the sound. That instantly shrank the number of lines of code to about ½ of what it was before.

But the classification was still an issue, and resulting in lots of excess code. The classification used by the JS API, things like .stattype and .droidType, didn't match up very well to the audio files that were available which was resulting in lots of excess code. So now it was time to abstract away the object classification and see if there was a way to define situation reports in a more concise and readable manner...

After quite a lot of trial and error, defining situation reports suddenly became a much more trivial task:

reports.msg = {
 
  // deafult detection messages (rarely heard):
  "objectSeen.Ally":        {ogg:"pcv380", one: true}, // ally detected
  "objectSeen.Enemy":       {ogg:"nmedeted", despam: 120000}, // enemy detected
  "objectSeen.Scav":        {ogg:"pcv373", despam: 60000}, // scavengers detected
  // droids:
  "objectSeen.droid.Enemy": {ogg:"pcv378"}, // enemy unit detected
  "objectSeen.droid.Scav":  {ogg:"pcv373"}, // scavengers detected
  "objectSeen.droid.weapon.Scav":  {ogg:"sit-scavfirst", one:true}, // scavenger raiders approaching base. defend base, then destroy scavenger 
 
  // ... etc. ...
}

There was a small amount of code that would hook in to JS API events and when triggered it would classify the object (eg. ".droid.weapon") and prefix that with a shortened version of the event name. That would then get passed to a function that would add a suffix (eg. ".Scav"). The end result would be a task ID like "objectSeen.droid.weapon.Scav" – this specifies everything I wanted to know:

  • What was the event? (eg. "objectSeen")
  • What sort of object is it? (eg. "droid.weapon")
  • Who does it belong to? (eg. "Scav")

I now had a simple lookup table defining all my situation reports – adding new reports was a trivial process: report.msg.taskId = someData.

The system quickly evolved. First I made it "search" for tasks - so if there was no action defined for "droid.weapon" it would try again with just "droid". This makes adding defaults very easy, and adding very specific scenarios also very easy. Second, I added more features to the data objects - such as a "one" property that ensures message only gets played once, or a "despam" property that overrides the default throttle time for the message.

And so it began...

The NEXUS Project is a direct descendant of what I've described above. It's evolved in to a more modular system, it has a bunch of new features, but ultimately it's just an abstract way to take some external input, classify it to get a Task ID, find an action associated with that task and perform the action.

See Also

More information about the project:

  • What can I use it for?The NEXUS Project is designed with several usage scenarios in mind...
  • Project ObjecivesThis project is a work in progress. Here's a list of the project's objectives (which will change over time)...