Think about auto-repair: look at every Terran mechanical unit on the map, find the damaged ones, find an idle SCV near each, and order a repair. Done literally every cycle with 500 units on the map, the game slows down. The shipped autorepair uses four ideas, and all four apply to any system that periodically looks at every unit.
One thing to know first: only ONE EUDLoopNewUnit works per map. With two, only one fires - the loop ends by resetting 0xA5, and making several coexist would mean deferring that reset to the very end of trigger execution. The symptom is nasty: a unit created BETWEEN the two loops is seen by neither. “It works for pre-placed and production units but not for trigger-created ones” is almost always this, not a dat problem. Merge every new-unit loop in the map into one and dispatch from inside it - and note that an imported .py running its own loop silently steals yours.
The first idea is to use the unit's own cycleCounter at 0x120 as a phase. Instead of looking at all 500 every frame, hand out 0 to 7 round-robin at creation and only act on phase 0 - one eighth of the work per frame.
// 생성할 때: 0~7을 돌려가며 유닛의 cycleCounter에 넣습니다
cycle_counter = EUDXVariable(0, SetTo, 0, 0xFF0000)
VProc([epd, cycle_counter], [
epd.QueueAddTo(EPD(cycle_counter.getDestAddr())),
cycle_counter.AddNumberX(1 << 16, 7 << 16),
cycle_counter.SetDest(0x120 // 4),
])
// 스캔에서는 위상 0만 처리합니다
EUDContinueIfNot(DeathsX(CurrentPlayer, Exactly, 0, 0, 0xFF0000))It buys two things: an eighth of the work, and units created in the same frame no longer sharing a phase. The second matters more - without it, every wave spawn puts its whole batch on the same frame and you get a periodic lag spike.
Second: classify once, at creation. The scan needs “Terran AND mechanical”, and testing that per unit per pass is two dat reads per unit per pass. Testing it once at creation and recording the answer as UnitGroup membership is two reads per SPAWN.
| 0x664080 | Special Ability Flags, one dword per unit id. Mechanical is the 0x40000000 bit. |
|---|---|
| 0x6637A0 | Staredit Group Flags, packed four per dword - split with q, r = f_div(unitType, 4) and test the bit inside byte r. |
| CUnit 0xAC | remainingBuildTime - nonzero means a building still under construction, so exclude it. |
Third: do not ask “is one nearby” with Bring. The author tried it and the lag was unacceptable, so it became a coordinate comparison - and a plain box is wrong in a different way, because the same box around a Command Center and around a Vulture gives completely different effective ranges. Build the box out of the unit's own size from units.dat.
size = EPD(0x65FD00 + 6856) + unitType * 2 // dword 두 개: 좌상, 우하 xy1 = f_dwread_epd(size); // unit bounds LT xy2 = f_dwread_epd(size + 1); // unit bounds RB // 상자 = 위치 - LT - 64 ~ 위치 + RB + 64 (64px = 2타일 여유)
Order 34 is Repair, and the target goes into 0x5C as a POINTER, not an EPD - epd * 4 + 0x58A364. And exclude the SCV itself from the inner loop, or it repairs itself.
The fourth is a rule rather than a trick: when you are holding a handle, do not go looking by location. It bites immediately in the common pattern of giving every enemy its own effect unit - the effect has to go when its owner dies, and removing it by putting a point location on it and calling RemoveUnitAt deletes the wrong one whenever two effects overlap there. Rarely, and therefore at the worst possible time.
const extraDb = Db(1700*336); // CUnit 표와 같은 크기의 빈 블록
const dst = (extraDb - 0x59CCA8) / 4; // 거리가 상수라 점프가 한 번
// 만들 때: 주인의 추가 칸에 이펙트의 EPD를 써 둡니다
const probe_cunit = CUnit.from_next();
CreateUnit(1, "Protoss Probe", "LOC8", P8);
SetMemoryEPD(target + dst, SetTo, probe_cunit);
// 주인이 죽을 때: 바로 그 이펙트만
foreach(d : unit.dying) {
CUnit(dwread_epd(unit.epd + dst)).remove(); // RemoveUnitAt 이 아니라
}