Skip to main content

epScript Reference



Getting Started

If anything is unclear, try searching online for answers.

Here we assume you already know how to use ScmDraft2 for basic terrain design.

  • If not, refer to: SCMD

Here we assume you already have a basic understanding of EUD.

Environment Preparation

Prepare a Windows 10 or higher PC, or a virtual machine.
Prepare ScmDraft2. If not already prepared, look back a few lines.

Map Preparation

Prepare a map file; you can create a new one in ScmDraft2 and save it as a Starcraft: Remastered Broodwar Map (*.scx).
Also save it to a path with only English letters and no spaces, e.g. D:\Projects\test\basemap.scx.

New Project

  1. Create a new text document and change its extension to edd, e.g. D:\Projects\test\test.edd
    Open this edd file with VSCode (just drag the file into the open VSCode window).
    Then change its content to:

    [main]
    input: basemap.scx
    output: test.scx

    [main.eps]

    The above uses relative paths as an example, but absolute paths are also supported.

  2. Create a new text document and change its extension to eps, e.g. D:\Projects\test\main.eps
    Open this eps file with VSCode.
    Then change its content to:

    function onPluginStart() { // This function will be executed once when the game starts
    DisplayTextAll("Hello World");
    }

    function beforeTriggerExec() { // This function will be executed once each frame before classical triggers

    }

    function afterTriggerExec() { // This function will be executed once per frame after classical triggers

    }
  3. Create a new text document and change its extension to bat, e.g. D:\Projects\test\build.bat
    Open this bat file with VSCode and change its content to:

    D:\SCRMapDevTools\euddraft0.9.9.9\euddraft.exe test.edd

    This assumes euddraft is unpacked to D:\SCRMapDevTools\euddraft0.9.9.9. Update the path if yours differs.

    The project is now ready. Double-click build.bat to generate test.scx. Place this map in the StarCraft: Remastered map directory and load it in-game to see Hello World displayed on screen.

Example Projects



Running Mode

Script File Extension Differences

  • For .py format scripts, the file extension can be omitted in the .eds/.edd file. .eps format scripts must include the extension.

    [main]
    input: basemap.scx
    output: outputmap.scx

    [eudTurbo]
    :: It actually loads a script named eudTurbo.py

    [main.eps]
    :: Load it like this if it is .eps format

Load Order

  • The order of plugin names in the configuration file determines their loading order after the game starts. After the game starts, onPluginStart() in the script will be executed once, and beforeTriggerExec(), triggers, and afterTriggerExec() will be executed cyclically on all players' machines.

    For example, with the following main.edd configuration:

    [main]
    input: in.scx
    output: out.scx

    [eudTurbo]
    [a.eps]
    [b.eps]

    The execution order after the game starts is:

    eudTurbo.onPluginStart()
    a.onPluginStart()
    b.onPluginStart()
    Executed cyclically every frame:
    eudTurbo.beforeTriggerExec()
    a.beforeTriggerExec()
    b.beforeTriggerExec()
    SCMD triggers
    b.afterTriggerExec()
    a.afterTriggerExec()
    eudTurbo.afterTriggerExec()

The Difference Between .edd And .eds

euddraft handles these two extensions differently.

  • For .edd Format

    After successfully compiling and generating the map, it will keep waiting. If the files in the project directory change, it will automatically recompile and generate the map again.
    If unsuccessful, it will output error messages. You can press R to recompile and generate after modifying.

  • For .eds Format

    After successfully compiling and generating the map, it will exit.
    If unsuccessful, it will output error messages and wait for you to press Enter to exit.

Data Synchronization

If you assign desync-data (such as the player's current mouse position) to a variable, the value of the variable will be different for each player's machine. If you execute an action that requires sync-data (such as creating a unit) based on the state of that variable, it may cause the sync-data to be out of sync for players in multiplayer games (e.g. a unit is created on player A's machine but not on player B's machine), leading to a drop.

Such tasks can usually be assisted by the MSQC plugin.



Game Time

The game time in StarCraft 1 is different from real time.

  • Game Frame (fr)

    The minimum unit of game time in StarCraft is the game frame (fr):
    1 fr == 1/16 game seconds

  • Game Seconds

    The formula for converting game seconds and game frames in StarCraft:
    1 game seconds == 16 fr

  • Game Speed

    StarCraft has seven standard game speeds.
    At different game speeds, the system time represented by each game frame is different.
    System time is usually equal to real world time.

    For each game frame, the corresponding system time (accurate value, not approximate) with no network latency
    Slowest: 1 fr == 0.167 system seconds
    Slower : 1 fr == 0.111 system seconds
    Slow : 1 fr == 0.083 system seconds
    Normal : 1 fr == 0.067 system seconds
    Fast : 1 fr == 0.056 system seconds
    Faster : 1 fr == 0.048 system seconds
    Fastest: 1 fr == 0.042 system seconds

    So at the Fastest game speed, 1 game second is 0.042 × 16 = 0.672 real seconds, and 1 system second is 1 ÷ 0.042 ÷ 16 ≈ 1.488 game seconds.

  • Triggers Poll Interval

    Triggers in StarCraft are single-threaded polling.
    Without using eudTurbo and Wait actions, the trigger polling interval is 31 fr, which is 1.9375 game seconds.
    The first poll after the game starts is at 2 fr, which is 0.125 game seconds.

    Trigger polling times after the game starts
    First poll at 2 game frames, 0.1250 game seconds
    Second poll at 33 game frames, 2.0625 game seconds
    Third poll at 64 game frames, 4.0000 game seconds
    Fourth poll at 95 game frames, 5.9375 game seconds
    Fifth poll at 126 game frames, 7.8750 game seconds
    Sixth poll at 157 game frames, 9.8125 game seconds
    Seventh poll at 188 game frames, 11.7500 game seconds
    Eighth poll at 219 game frames, 13.6875 game seconds
    Ninth poll at 250 game frames, 15.6250 game seconds
    ...And so on...

    The ElapsedTime condition parameter compares game seconds using the integer part.

    function beforeTriggerExec() {
    if (ElapsedTime(Exactly, 6)) {
    DisplayTextAll("This message will not output");
    }
    }

    So this condition will not be met, because the fourth poll is at 5.9375 game seconds with an integer part of 5, and the fifth poll is at 7.8750 game seconds with an integer part of 7, so ElapsedTime(Exactly, 6) will never be true.
    If you want to execute an action once after 6 game seconds, you can write like this:

    function beforeTriggerExec() {
    once (ElapsedTime(AtLeast, 6)) {
    DisplayTextAll("This message will output once after 6 game seconds");
    }
    }

    Similarly, the CountdownTimer condition also takes the integer part of the countdown at the top of the screen.
    Therefore, for time-based conditions, avoid using Exactly (==) and instead use AtLeast (>=) or AtMost (<=).

Current Player And Local Player

Current Player and Local Player are two different concepts.

Current Player

The Current Player is a global variable. In some trigger actions, Current Player is used as an execution parameter.
Some trigger conditions and actions accept a Player parameter, in which case you can set it to 13 to use the value of the Current Player global variable.
The value of the Current Player global variable does not necessarily have to be any player's ID, it can store any integer value.

Actions that only take effect on machines where Current Player == Local Player (allow desync use, can be used individually on some player machines)
  • DisplayText
  • CenterView
  • PlayWAV
  • MinimapPing
  • TalkingPortrait
  • Transmission
  • SetMissionObjectives
Actions that only take effect on Current Player (must be used synchronously on all player machines, otherwise disconnected)
  • SetAllianceStatus
  • RunAIScript
  • RunAIScriptAt
  • Draw
  • Defeat
  • Victory

The setcurpl function can be used to set the value of the Current Player global variable.
The getcurpl function can be used to get the current value of the Current Player global variable.
No matter what value you set for the Current Player, the code will execute on all players' machines.

setcurpl(P1);
DisplayText("Printed content for player 1");
setcurpl(P2);
DisplayText("Printed content for player 2");
setcurpl(P3);
DisplayText("Printed content for player 3");

// $CurrentPlayer is the constant number 13. It can cause some player-related conditions or actions to access the current player value
// $CurrentPlayer != getcurpl()
if ($CurrentPlayer == 13) {
DisplayTextAll("Well, right");
}

// Set Fastest game speed x2
setcurpl(-122787 + 6);
SetDeaths($CurrentPlayer, SetTo, 21, 0);

Local Player

getuserplayerid() returns the local player ID, which differs per machine and is unrelated to the value set by setcurpl.
This means you can decide at runtime whether to execute certain code on the local machine.
This helps improve performance: with many players, not all code needs to run for every player — for example, you don't need to generate text prompts for all players when only targeting one specific player.
Of course, if you pollute sync-data directly or indirectly due to unfamiliarity with synchronization rules, using getuserplayerid() can also lead to desync and dropped connections.

setcurpl(P1);
println("Current player ID: {}", getuserplayerid());
setcurpl(P2);
println("Current player ID: {}", getuserplayerid());
setcurpl(P3);
println("Current player ID: {}", getuserplayerid());