SkillAgentSearch skills...

Langums

Programming language and compiler for StarCraft: Brood War custom (UMS) maps

Install / Use

/learn @LangUMS/Langums
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

LangUMS

LangUMS is an imperative programming language with C-like syntax for creating custom maps for the game StarCraft: Brood War and the 2017 StarCraft: Remastered edition.

It supersedes the trigger functionality in editors such as SCMDraft 2 and the official StarEdit. You still have to use a map editor to make the actual map, place locations and units but the game logic (triggers) is done by LangUMS.

Table of Contents

Bugs, issues and feature requests should go to the issues section.

Discord channel for support and discussion.

Usage

  1. Get the latest langums.exe from here.

  2. You will need to install Microsoft Visual Studio 2015 Redistributable (x86) if you don't have it already.

  3. Run langums.exe with your map, source file and destination file e.g.

langums.exe --src my_map.scx --lang my_map.l --dst my_map_final.scx
  1. Now you can run my_map_final.scx directly in the game or open it with an editor.

  2. Get the VS Code extension. It has syntax highlighting, code completion and support for the experimental debugger.

  3. Read the rest of this document and run langums.exe --help to see the available command-line arguments.

Language features

  • C-like syntax
  • Single primitive type - unsigned 32-bit integer
  • Local (block scoped) and global variables
  • Static arrays
  • Functions with arguments and a return value
  • Expressions e.g. ((foo + 42) - bar)
  • Unsigned integer arithmetic with overflow detection
  • Postfix increment and decrement operators - x++, y--
  • Arithmetic operators - +, -, /, *
  • Comparison operators - <, <=, ==, !=, >=, >
  • Boolean operators - &&, ||
  • if and if/else statements
  • while loop
  • Event handlers
  • Metaprogramming facilities
  • Experimental debugger

Language basics

You can try out all the examples below using the test.scx map from here.

Every program must contain a main() function which is the entry point where the code starts executing immediately after the map starts. The "hello world" program for LangUMS would look like:

fn main() {
  print("Hello World!"); // prints Hello World! for all players
}

The example above will print Hello World! on every player's screen.

Important notes:

  • All statements must end with a semicolon (;).
  • Block statements (code enclosed in {}) do not need a semicolon at the end.
  • C-style comments with // are supported.

The more complex example below will give 36 minerals total to Player1. It declares a global variable called foo, a local variable bar and then calls the add_resource() built-in within a while loop. The quantity argument for add_resource() is the expression foo + 2. Some built-ins support expressions for some of their arguments.

global foo = 10;

fn main() {
  var bar = 3;

  while(bar--) {
    add_resource(Player1, Minerals, foo + 2);
    foo = foo + 10;
  }
}

You can define your own functions that accept arguments and return a value.

fn get_unit_count(wave) {
  return 10 + wave * 5;
}

fn spawn_units(count, wave) {
  if (wave == 1) {
    spawn(TerranMarine, Player1, count, "TestLocation");
    return;
  }
  
  if (wave == 2) {
    spawn(ProtossZealot, Player1, count, "TestLocation");
    return;
  }
  
  if (wave == 3) {
    spawn(ZergZergling, Player1, count, "TestLocation");
    return;
  }
}

fn main() {
  var wave = 2;
  var count = get_unit_count(wave);
  spawn_units(count, wave);
}

Note that all function arguments are passed by value.

You can also have static arrays. At the moment arrays can only be indexed with constants.

global my_array[8];

fn main() {
  my_array[Player1] = 42;
  my_array[Player2] = 0x07;
  
  if (my_array[Player2] == 15) {
    print("foo");
  }
  
  var foo[4];
  foo[0] = 10;
  foo[1] = 13 * 2;
}

Notes:

  • Number literals can be entered as hexadecimals by preceding them with 0x e.g. 0xB4DF00D.
  • You can index arrays with the Player constants.

Event handlers

Any useful LangUMS program will need to execute code in response to in-game events. The facility for this is called event handlers.

An event handler is somewhat like a function that takes no arguments and returns no values. Instead it specifies one or more conditions and a block of code to execute.

Whenever all specified conditions for the handler are met its associated body of code will be executed.

Here is an event handler that runs whenever Player 1 brings five marines to the location named BringMarinesHere and has at least 25 gas:

bring(Player1, Exactly, 5, TerranMarine, "BringMarinesHere"),
accumulate(Player1, AtLeast, 25, Gas) => {
  print("The marines have arrived!");
}

Once we have our handlers setup we need to call the built-in function poll_events() at regular intervals. A complete example demonstrating this:

bring(Player1, Exactly, 5, TerranMarine, "BringMarinesHere"),
accumulate(Player1, AtLeast, 25, Gas) => {
  print("The marines have arrived!");
}

fn main() {
  while(true) {
    poll_events();
  }
}

You can do other kinds of processing between the poll_events() calls and you can be sure that no event handlers will be interleaved with your program's execution. Events are buffered so if you don't call poll_events() for a long time it will fire off all buffered events one after another the next time it's called.

A slighly more contrived example of events. Also demonstrates usage of the preprocessor #define directive.

#define MAX_SWAPS 3

global allowedSwaps = MAX_SWAPS;

bring(Player1, AtLeast, 1, TerranMarine, "TestLocation2"),
elapsed_time(AtLeast, 15) => {
  if (allowedSwaps == 0) {
    print("Sorry, you have no more swaps left.");
  } else { 
    allowedSwaps--;
    kill(TerranMarine, Player1, 1, "TestLocation2");
    spawn(ProtossZealot, Player1, 1, "TestLocation");
    
    print("Here is your zealot.");
  }
}

bring(Player1, AtLeast, 1, TerranMarine, "TestLocation2"),
elapsed_time(AtMost, 15) => {
  print("You have to wait 15 seconds before being able to swap.");
}

bring(Player1, AtLeast, 1, ProtossZealot, "TestLocation2") => {
  if (allowedSwaps == 0) {
    print("Sorry, you have no more swaps left.");
  } else {
    allowedSwaps--;
    kill(ProtossZealot, Player1, 1, "TestLocation2");
    spawn(TerranMarine, Player1, 1, "TestLocation");
    
    print("Here is your marine.");
  }
}

fn main() {
  spawn(TerranMarine, Player1, 1, "TestLocation");
  
  while (true) {
    poll_events();
  }
}

Built-in functions

Notes:

  • Arguments named Expression may either be numeric constants e.g. 42 or expressions like x * 3.
  • Text arguments must be passed in " quotes e.g. "This is some text".
  • Location arguments may be passed without quotes if they do not contain spaces e.g. MyLocation, but "My Location" has to be in quotes.
  • The special value AnyLocation may be passed to Location type arguments.
  • The special value All may be passed to Quantity and QuantityExpression arguments involving unity quantities e.g. kill(Player1, TerranMarine, All).

Unit functions

| Function prototype | Description | |--------------------------------------------------------------------------------------------------|------------------------------------------------------| | spawn(Unit, Player, Expression, Location, optional: Props) | Spawns units at a location with optional properties. | | kill(Unit, Player, Expression, optional: Location) | Kills units at an optional location. | | remove(Unit, Player, Expression, optional: Location) | Removes units at an optional location. | | move(Unit, Player, Expression, SrcLocation, DstLocation) | Moves units from one location to another. | | order(Unit, Player, Order, SrcLocation, DstLocation) | Orders units to move, attack or patrol. | | modify(Unit, Player, Expression, UnitMod, ModQuantity, Location) | Modifies a unit's HP, SP, energy or hangar count. | | give(Unit, SrcPlayer, DstPlayer, Expression, Location) | Gives units to another play

View on GitHub
GitHub Stars41
CategoryDevelopment
Updated3mo ago
Forks2

Languages

C++

Security Score

87/100

Audited on Jan 4, 2026

No findings