epScript Reference
- Language Reference
- Description
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.
- If not, refer to: What is 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.
-
Download euddraft0.9.9.9.zip
Unpack euddraft to a path with only English letters and no spaces, e.g. D:\SCRMapDevTools\euddraft0.9.9.9 -
Download VSCode
Install it however you like.
Install the eps-server plugin from the VSCode plugin store. -
Enable file extension display in your operating system.
For Windows 10, refer to https://www.google.com/search?q=Open-file-extension-display-in-windows-10
For Windows 11, refer to https://www.google.com/search?q=Open-file-extension-display-in-windows-11
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
-
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.scxoutput: test.scx[main.eps]The above uses relative paths as an example, but absolute paths are also supported.
-
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 startsDisplayTextAll("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} -
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.eddThis 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 Worlddisplayed on screen.
Example Projects
- If the configuration steps above are unclear, browse one of the example projects below:
Running Mode
Script File Extension Differences
-
For
.pyformat scripts, the file extension can be omitted in the .eds/.edd file..epsformat scripts must include the extension.[main]input: basemap.scxoutput: 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.scxoutput: 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 triggersb.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 secondsSlower : 1 fr == 0.111 system secondsSlow : 1 fr == 0.083 system secondsNormal : 1 fr == 0.067 system secondsFast : 1 fr == 0.056 system secondsFaster : 1 fr == 0.048 system secondsFastest: 1 fr == 0.042 system secondsSo at the Fastest game speed, 1 game second is
0.042 × 16 = 0.672real seconds, and 1 system second is1 ÷ 0.042 ÷ 16 ≈ 1.488game seconds. -
Triggers Poll Interval
Triggers in StarCraft are single-threaded polling.
Without using eudTurbo and Wait actions, the trigger polling interval is31 fr, which is1.9375 game seconds.
The first poll after the game starts is at2 fr, which is0.125game seconds.Trigger polling times after the game starts
First poll at 2 game frames, 0.1250 game secondsSecond poll at 33 game frames, 2.0625 game secondsThird poll at 64 game frames, 4.0000 game secondsFourth poll at 95 game frames, 5.9375 game secondsFifth poll at 126 game frames, 7.8750 game secondsSixth poll at 157 game frames, 9.8125 game secondsSeventh poll at 188 game frames, 11.7500 game secondsEighth poll at 219 game frames, 13.6875 game secondsNinth 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());