# epScript 参考
## 示例工程 : 修改单位消耗人口限制
You can omit `$U` and `EncodeWeapon` by specifying function parameter type:
```js
function SetWeaponCooldown(wt: Weapon, frames) {
......
}
SetWeaponCooldown("C-10 Concussion Rifle", 0);
= SetWeaponCooldown(EncodeWeapon("C-10 Concussion Rifle"), 0);
```
## 示例工程 : 位置函数的使用
```js
StringBuffer().printfAt(0,"机枪兵({},{})(脸朝:{}) 与 鬼兵({},{})(脸朝:{}) 的距离为 {} 角度为 {}", x0, y0, marine_cu.currentDirection2, x1, y1, ghost_cu.currentDirection2, dist, ang);
StringBuffer().printfAt(1,"从机枪兵位置向 {} 度走 {} 的距离将到达 ({},{}) 鬼兵的位置", ang, dist, x, y);
```
☝️ I won't recommend to write this (every `StringBuffer()` will consume string table)
Use `printAt` instead:
```js
printAt(0,"机枪兵({},{})(脸朝:{}) 与 鬼兵({},{})(脸朝:{}) 的距离为 {} 角度为 {}", x0, y0, marine_cu.currentDirection2, x1, y1, ghost_cu.currentDirection2, dist, ang);
printAt(1,"从机枪兵位置向 {} 度走 {} 的距离将到达 ({},{}) 鬼兵的位置", ang, dist, x, y);
```
## 示例工程 : 游戏中文字菜单
```js
var x, y;
// these are all same result, same trigger count (1 trigger 2 actions):
x, y = 0, 0;
DoActions(x.SetNumber(0), y.SetNumber(0);
RawTrigger(actions=list(x.SetNumber(0), y.SetNumber(0)));
function GetMouseXY() {
return dwread(0x6CDDC4), dwread(0x6CDDC8);
} // you can write this without perf-penalty because 0x6CDDC4 is constant
```
Text color <01> (\x01) and <02> (\x02) are different: user should favor __<02>__.
<01> gets color from above (previous) line:
```js
DisplayText("\x07Hello,\n\x01World!"); // both lines are green colors
```
The relation between `VArray(assign_values)` and `EUDVArray(size)(list(initial_values))` are similar to `[assign_values]` and `EUDArray(list(initial_values))`:
- Former always initializes (resets) inner values
- Latter fills values on map file, like `static var`
- You can put variable or function output on former but can't on latter.
You can early return and save indentation
```js
if (getcurpl() == p) {
-> if (getcurpl() != p) return;
```
Use `IsUserCP()` condition (1 `Deaths` condition, can be used in `RawTrigger`):
```js
setcurpl(getuserplayerid());
if (p == getuserplayerid()) { /* 如果本机玩家恰好就是操作菜单的玩家 */
-> if (IsUserCP()) {
```
We can add comment on `isMouseOvered` for better understanding
```js
const isMouseOvered = EUDArray(7); // Desync array (user-local storage)
```
Format string arguments:
- `{:c}`: text color closest to player's current unit color (same as `PColor`)
- `{:n}`: name of player (same as `PName`)
- `{:s}`: string address (ptr, same as `pt2s`)
- `{:t}`: string EPD address (same as `epd2s`)
- `{:x}`: hexadecimal print (same as `hptr`)
```js
StringBuffer().printfAt(10, "{}{} \x01将游戏速度更改为 \x07{}\x01%", PColor(p), PName(p), currentSpeedSel);
= printAt(10, "{:c}{:n} \x01将游戏速度更改为 \x07{}\x01%", p, p, currentSpeedSel);
= printAt(10, "{0:c}{0:n} \x01将游戏速度更改为 \x07{1}\x01%", p, currentSpeedSel);
```
# 运行模式
## 脚本文件扩展名区别
`::` is version-compatible way to write comment in .eds/.edd file, like DOS batch files.
```bat
[main]
input: basemap.scx
output: outputmap.scx
[eudTurbo]
:: 它实际是加载的一个文件名为 eudTurbo.py 的脚本
[main.eps]
:: 如果是 eps 格式就这么加载
```
## 加载顺序
Nice explanation 👍
# 当前玩家与本机玩家
## 当前玩家
- We can list trigger actions which only executes for `CurrentPlayer`: `DisplayText`, `CenterView`, `PlayWAV`, `MinimapPing`, `TalkingPortrait`, `Transmission`, `SetMissionObjectives`
- We can also list trigger actions which takes and uses `CurrentPlayer`: `SetAllianceStatus`, `RunAIScript`, `RunAIScriptAt`, `Draw`, `Defeat`, `Victory`
## 本机玩家
> 可以使用 `getuserplayerid()` 来获取本机玩家的编号意味着你可以决定是否在本机执行或者不执行某些代码,当然你需要小心,千万不要试图以此直接或者间接污染需要同步的数据,它可能导致数据不同步而引发掉线
我不同意。`getuserplayerid()`函数和`IsUserCP()`条件是有用的,当你正确使用它们的时候,性能会更高。例如,`StringBuffer`经常使用`IsUserCP()`,只为将要看到它的玩家构建字符串,而不是为其他玩家构建字符串。它节省了大量的触发器执行次数,而且在游戏中活跃的人类玩家数量不会带来性能上的差异。
# 实现原理
## 虚拟触发器(Virtual Triggers)
There was 'MRGN loop' for initialization (2 triggers in fixed memory address), which was mandatory because memory address of STR section was not fixed for StarCraft 1.16.1. This is not a case in StarCraft: Remastered. (STR address is fixed to `0x191943C8`)
There're 8 `TriggerPlayerStruct`s which stores memory address of first trigger for each player. So current explanation is somewhat misleading because both `TRIG` and `STR` have uncertain memory address and both are runtime accessible. Of course `TRIG` is much harder to access because you can only access them as doubly-linked list via pointers and relative offsets are not fixed, but a better explanation would be `TRIG` can only store triggers but `STR` is much easier to access and can store any custom data.
- For understanding `TriggerPlayerStruct`, see https://blog.naver.com/whyask37/140206508304 (Korean)
- Trgk made 20 minute video explaining how eudplib works (Korean): https://youtu.be/N2A7qghf8ps
## 变量(EUDVariable)实现
Nice guide 👍
# 基本语法
## 字面量字符串
```cpp
DisplayText("You can put backslash\
at end of line to write\n\
multi-line string literal");
```
## 流程控制 : for 循环
for header : `for ( init_statements ; expression ; action_statements)`
You can write multiple statements with COMMA (,):
```js
var i1, i8;
for (i1, i8 = 0, 0 ; i1 < 10 ; i1++, i8 += 8) {
printAll("{} x 8 = {}", i1, i8);
}
```
## 流程控制 : foreach 编译期循环
> foreach 是编译期循环,会在编译期静态展开(once 也会展开成多个 once),不能使用 break 跳出 foreach
`foreach` iterates any iterable, and you can `continue;` or `break;` when iterable is EUD-defined trigger, and you can't `break;` when iterable is compile-time struct. So whether you can `break;` or not is determined by what you iterate for `foreach` loop. Yeah I know this is very confusing...
```js
foreach(i : py_range(10)) { // Python range is compile-time
break; // compile error!
}
foreach(unit : EUDLoopNewCUnit()) { // EUDLoopNewCUnit is run-time
continue; // OK
}
```
# 使用变量
## 变量说明
> epScript 代码中的所有变量均为私有变量 (non-shared variable),即其所在地址为 non-shared memory,属于非同步数据
var is shared just like how death counter is shared: it becomes user-local if you edit as user-local, it becomes shared if you edit only synchronously.
> 没有字符串变量!!没有字符串变量!!没有字符串变量!!
There're 3 string types: `Db` (bytes), `$T` (map string), `StatText`.
- `Db("string")` is equal to `Db(b"string\0")` (UTF-8)
- `$T` has its offset in string table, can be used directly on basic actions like `DisplayText`.
* `GetMapStringAddr` reads its address.
* Most parts of scenario.chk takes these string indexes.
* Its encoding is UTF-8 but can be CP949 as well.
* Run-time accessed strings *must* be UTF-8! Accessing CP949 string in run-time prevents any further string modification for EVERY map strings.
* This is why [main] has `decodeUnitName : UTF-8` option for CP949 input map.
- `StatText`s are entries of `stat_txt.tbl`:
* `GetTBLAddr` reads its address.
* Basic unit names, ranks, button texts, status texts, some error messages etc use it.
* Its encoding is primarily CP949 but can be UTF-8 as well.
* It decodes CP949 first, and fallbacks to UTF-8 or latin1 if fails.
* `settblf("StatText", offset, "format_string", arguments, encoding='utf-8');` puts Thin Space character '\u2009' at end of stat text to prevent from interpreting as CP949 and instead forcing UTF-8.
**Why the hell there's discrepancy between them?**
- Modding `stat_txt.tbl` was very popular in StarCraft 1.16.1 Korean EUD maps, so for UMS compatibility, SC:R had to priotize CP949 over UTF-8.
- On the other hand, string modification was harder and not popular so SC:R broke backward compatibility and moved on to UTF-8.
String modifying on UTF-8 string can be O(n) operations, and there's no zero-copy way to implement '字符串变量' like how Python does. You need Scalar value string to access and edit by so-called characters, but you need UTF-8 string for StarCraft to display, and this conversion is expensive for triggers.
tldr; strings are actually hard :-(
## 变量作用域
You can 'shadow' variable in code block:
```js
var 变量 = 1;
function 示例() {
simpleprint(变量); // prints 1
var 变量 = 10;
simpleprint(变量); // prints 10
{
var 变量 = 100;
simpleprint(变量); // prints 100
}
simpleprint(变量); // prints 10
}
```
## 对象类型
You can only declare `object` in module scope, and must put semicolon at end of definition.
You can define `constructor` and `destructor`:
```js
const objList = EUDArray(100);
var objCount = 0;
object Obj {
var a, b, c;
var index;
function constructor(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
this.index = objCount;
objList[objCount] = this;
objCount++;
}
function destructor() { // runs on Obj.free(instance)
objCount--;
const lastObj = objList[objCount];
objList[this.index] = lastObj;
}
};
const staticObj = Obj(1, 2, 3);
const dynObj = Obj.alloc(1, 2, 3);
```
(there's `constructor_static` but defining it in epScript has limitation.)
## const 和 var 的说明
> 在 epScript 能做的编译期交互不多,而且不是很方便
> ```js
> var v = 100;
> const i = 10;
> const s = py_str("Location ") + py_str(i);
> py_print(py_str("编译到了 const s = {} 的位置").format(s));
> py_print(py_str("变量 v 的实质是一个 {} 对象").format(v));
> ```
You can put multiple arguments for `py_print`:
```js
py_print("变量 v 的实质是一个 ", v, " 对象");
= py_print(py_str("变量 v 的实质是一个 {} 对象").format(v));
```
# 使用函数
## 参数和返回值类型
Use `CUnit.from_read(epd_address)`:
```js
function 创建一个新单位(player : TrgPlayer, ut : TrgUnit, loc : TrgLocation) : CUnit, TrgString {
const unit = CUnit.from_read(EPD(0x628438));
if (unit == 0) {
return 0, $T("无法再创建单位");
}
CreateUnit(1, ut, loc, player);
if (Memory(0x628438, Exactly, unit.ptr)) {
return 0, $T("CreateUnit 没能成功创建单位,可能是参数给错或是出口被堵住了")
}
return unit, $T("成功");
}
function onPluginStart() {
const cu, err = 创建一个新单位(P1, $U("Terran Marine"), $L("Location 1"));
if (cu == 0) {
DisplayTextAll(err);
} else {
cu.cgive(P8);
cu.set_color(P8);
}
}
```
# 内置基本对象类型
## 基本对象类型 : EUDVariable
These methods of `EUDVariable` are practically only usable in combination with `VProc`:
```js
function QueueAssignTo(dest){}
function QueueAddTo(dest){}
function QueueSubtractTo(dest){}
function SetDest(dest){}
function AddDest(dest){}
function SubtractDest(dest){}
function SetDestX(dest,mask){}
function AddDestX(dest,mask){}
function SubtractDestX(dest,mask){}
function SetModifier(modifier){}
function SetMask(v){}
function AddMask(v){}
function SubtractMask(v){}
function SetMaskX(v,mask){}
function AddMaskX(v,mask){}
function SubtractMaskX(v,mask){}
```
- `QueueAssignTo(dest)` = `SetDest(dest)` + `SetModifier(SetTo)`
- `QueueAddTo(dest)` = `SetDest(dest)` + `SetModifier(Add)`
- `QueueSubtractTo(dest)` = `SetDest(dest)` + `SetModifier(Subtract)`
```js
function VProc(single_var_or_list_of_vars, actions);
// runs actions first, and executes triggers of variables
// example
var a, b;
const c = a & b; // bitwise and operation
// rewrite into following:
var a, b, c;
c = a;
c &= b;
// rewriting `c = a;` with VProc
VProc(a, a.QueueAssignTo(c));
// rewriting `c &= b;` with SeqCompute
const Write = c.SetNumberX(0, 0xFFFFFFFF);
const operations =
list(EPD(Write + 0), SetTo, 0xFFFFFFFF), // Set bitmask of Write
list(EPD(Write + 0), Subtract, b); // bitmask becomes ~b
SeqCompute(operations); // executes 2 trigger 5 actions
DoActions(Write); // executes 1 trigger 1 action
// rewriting `c &= b;` with VProc
const Write = c.SetNumberX(0, 0xFFFFFFFF);
VProc(b, list(
SetMemory(Write + 0, SetTo, 0xFFFFFFFF),
b.QueueSubtractTo(EPD(Write + 0)),
));
DoActions(Write);
// rewriting whole statements with VProc
const Write = c.SetNumberX(0, 0xFFFFFFFF);
VProc(list(a, b), list(
a.QueueAssignTo(c),
SetMemory(Write + 0, SetTo, 0xFFFFFFFF),
b.QueueSubtractTo(EPD(Write + 0)),
)); // executes 3 trigger 9 actions
DoActions(Write);
```
# 内置函数库
## 当前玩家
### getcurpl()
> - `getcurpl()`
>
> 获取 cpcache 的值,如果 cpcache 没有值,则将`当前玩家`的值缓存到 cpcache 并返回
> `当前玩家`不是指本机玩家,甚至可能不是指一个玩家,它是一个变量,可能存储任何数值,由 setcurpl 设置
I think this explanation is way too much focusing on actual implementation and not its meaning. `getcurpl()` ALWAYS returns the epd value that CurrentPlayer uses. Using cpcache for optimization is implementation details and I don't think every user should know this.
`getcurpl()` is always correct on its value BUT its performance can be diminished and fallbacked to memory read when there is a cache miss.
```js
setcurpl(P1);
const cp = getcurpl(); // superfast, var copy.
dwwrite(0x6509B0, $P2); // directly modifying 0x6509B0 results in cache-miss
const cp2 = getcurpl(); // equal to $P2 (1) but its performance became dwread...
```
## 内存读写相关
### repmovsd_epd
> - repmovsd_epd(dstepdp, srcepdp, copydwn)
> 从 [srcepdp] 拷贝 [copydwn] 字节内存到 [dstepdp]
`repmovsd_epd` copys from src to dst per 4-byte (1 epd) unit:
```js
const src = Db(b"___1___2___3___4___5");
const dst = Db(20);
repmovsd_epd(EPD(src), EPD(dst), 5);
// dst becomes Db(b"___1___2___3___4___5")
```
It is similar to `memcpy(dst, src, copylen)` but both src and dst have to be epd (must be aligned, multiple of 4), and copy length must be multiple of 4.
### readgen
> - readgen_epd(mask, args)
> - readgen_cp(mask, args)
>
> 实现 read 函数的函数,不知道怎么用
`readgen_epd` and `readgen_cp` create custom read function.
```js
const myread_epd = readgen_epd(bitmask, args);
const myread_cp = readgen_cp(bitmask, args);
const my, read, values = myread_epd(epd_address);
```
single arg is `list(initial_value, lambda_function_for_every_bit_in_bitmask)`.
You can define your own `posread_epd` for 256x256 map:
```json
// 256 grids = 8192 pixels = x and y are within 0 ~ 8191 (0x1FFF)
// there's no way to write compile-time function without py_eval...
const posread_epd = readgen_epd(
0x1FFF1FFF,
list(0, py_eval('lambda x: x if x < 65536 else 0')),
list(0, py_eval('lambda y: y // 65536 if y >= 65536 else 0')),
);
const x, y = posread_epd(epd_address);
```
## 迭代器
### EUDLoopPlayer
It only iterates active players in game (not vacant or defeated), checks `playerexist`.
### EUDLoopUnit2
> - `EUDLoopUnit2()`
>
> 迭代所有单位的 ptr, epd
>
>示例
>
> ```JavaScript
> // Unit Node Table 是一个双向链表 (doubly linked list),有主链和支链
> // FirstUnitPointer -> Unit1 <-> Unit2 <-> "Terran Siege Tank" <-> Unit4 -> Null
> // |
> // "Tank Turret"
> // 主链和支链都会占用内存空间,比如以上的例子中,Bring 条件会判定总共有 4 个单位,但是实际上 1700 个单位空间有 5 个都被占用了。
> // EUDLoopUnit() 只会 loop 主链上的单位,因此会无视掉 "Tank Turret" -- 坦克头上的炮台
> // 而 EUDLoopUnit2() 会 loop 所有占用 Unit Node Table 空间的单位,因为它不是按照链表的顺序 loop 的,而是按照内存顺序 loop 的。
> // EUDLoopUnit(): 顺着主链 loop unit,无法 loop 到 sub unit 和 map revealer 等
> // EUDLoopUnit2(): 顺着 Memory index 来 loop unit,可以 loop 到全部 unit
> foreach (ptr, epd : EUDLoopUnit2()) {
> const u = CUnit(epd);
> if (u.unitID == $U("Terran Marine")) {
> u.hp = 0x100 * 10;
> }
> }
> ```
Nice explanation! Would be great to mention that `EUDLoopNewUnit` and `EUDLoopUnit` do *not* loop Scanner Sweep too. (both uses linked list of units)
### EUDLoopNewCUnit(allowance)
> 同一帧多次调用 EUDLoopNewUnit 或者 EUDLoopNewCUnit 将会遍历到相同的单位
> 第一次调用 EUDLoopNewUnit 或者 EUDLoopNewCUnit 相当于调用 EUDLoopUnit 或 EUDLoopCUnit
`EUDLoopUnit2` and `EUDLoopCUnit` iterate subunits, scanner sweep, map revealer etc while `EUDLoopNewCUnit` doesn't.
#### 原理
Appearing units and new units are linked on top of unit lists, and when the number of appearing units exceeds 'allowance', `EUDLoopNewCUnit` skips some new units behind appearing units...
- Appearing units: workers exit gas building, unit exits bunker, interceptor launches, scarab fired, units land from transport, etc.
Default value of 'allowance' = 2. Melee-based UMS map might want to increase this value. Every `EUDLoopNewCUnit` will run `cunitepdread_epd` at least 'allowance' times for lookahead.
## 数学
> - div_towards_zero(a, b)
> 向下取整有符号除法 [a] 除以 [b],返回商和余数
> - div_floor(a, b)
> 向下取整有符号除法 [a] 除以 [b],返回商和余数
> - div_euclid(a, b)
> 计算 [a] 除以 [b] 的欧几里得商和余数。
这是计算有符号除法,它计算商使得 `a = 商 * b + 余数`,并且 `0 <= 余数 < 绝对值(b)`。
换句话说,结果是将 a ÷ b 四舍五入到商的值,使得 `a >= 商 * b`。如果 `a > 0`,则等于向零舍入;如果 `a < 0`,则等于朝着正负无穷大方向(远离零)四舍五入。
- `div_towards_zero`: consistent with C, JavaScript, old hardware that uses 1's complement or magnitude.
- `div_floor`: Python, math uses
- `div_euclid`: math uses
Why Python chose `div_floor`, see: http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html
# MSQC 命令同步
## MSQC 原理:
> MSQC 插件就是巧妙地利用了这一特性:当某个非共享数据被读取时,MSQC 会先创造一个 “辅助单位(QCUnit)”
> (中略)
> 这一过程结束后,MSQC 会把这个辅助单位从地图上移除
> 游戏中每扫一轮触发,MSQC 就会完成一轮 “创建辅助单位、施加指令、数据转化、删除辅助单位” 的动作
[MSQC] creates “辅助单位(QCUnit)” when there's no QCUnit (on game start or QCUnit removed for any reason). Respawning QCUnit is kind of restoring attempt from logical error of code. Under normal circumstances, respawning does not happen.
## MSQC 参数:
> ```
> ## 参数语法1
> [MSQC]
> [Non-shared condition 1] ; [Non-shared condition 2] ; [Nonshared condition 3] : [UnitName / UniID / PVariableName], [value to add]
> ## 参数语法2
> Mouse : [LocationName / LocationID]
> ## 参数语法3
> [Non-shared condition 1] ; [Non-shared condition 2] ; Mouse : [LocationName / LocationID]
> ## 参数语法4
> [Non-shared condition 1] ; val, [Address / VariableName] : [UnitName / UniID / PVariableName]
> ## 参数语法5
> [Non-shared condition 1] ; xy, [Address / VariableName] : [UnitName / UniID / PVariableName]
> [Non-shared condition 1] ; xy, [Address / VariableName], [Address / VariableName] : [UnitName / UniID / PVariableName], [UnitName / UniID / PVariableName]
> ## 其它参数
> QCDebug : [True / False] (默认True)
> QCUnit : [UnitName / UniID / PVariableName] (默认Terran Valkyrie)
> QCLoc : [LocationID](默认0)
> QCPlayer : [Player ID] (默认10)
> QC_XY : [X], [Y] (默认128, 128,单位为像素)
> ```
`[Non-shared condition]` can be one of followings:
- val syntax, xy syntax, mouse syntax
- eudplib code
* can only uses global of non-array types here
- HexadecimalAddress, Comparison, Value
* comparison can be Exactly, AtLeast, AtMost
* Same as `Memory(address, comparison, value)`
- HexadecimalAddress, Value
* Same as `MemoryX(address, Exactly, value, value)`
- KeyDown, KeyUp, KeyPress(virtual_key_code)
* KeyDown sends once you press key
* KeyUp sends once you release key
* KeyPress sends while you're pressing key
- NotTyping
* player is not typing chat message, same as `Memory(0x68C144, Exactly, 0)`
- MouseDown, MouseUp, MousePress(button)
* button can be L, R, M, Left, Right or Middle.