跳到主要内容

内建函数



触发器条件和动作

  • 普通条件函数

    普通条件函数是依据传统触发器中的条件封装的函数,就像 ScmDraft2 里面的触发器条件那样
    所有的触发器条件函数都会返回一个触发器条件表达式常量(而非逻辑值),条件表达式条件表达式的结果这两个概念需要区分清楚
    你如果需要用变量储存条件表达式判断的结果,则应该将其传入一个触发器的条件列表或者当作 if 语法参数,也可用 l2v 取条件表达式的运行时结果,参考以下示例

    var vc0 = Accumulate(P1, AtLeast, 500, Ore);  // 这是不对的!!!它不返回一个逻辑值。
    const c1 = Accumulate(P1, AtLeast, 500, Ore); // 这样可以,它返回的就是一个常量条件表达式,它可以当作 RawTrigger 或者 Trigger 的 conditions 参数

    // 使用变量存储条件返回的逻辑值方法
    var vc1 = 0;
    Trigger(
    conditions = Accumulate(P1, AtLeast, 500, Ore),
    actions = vc1.SetNumber(1),
    );
    var vc2 = l2v(Accumulate(P1, AtLeast, 500, Ore));

    • Accumulate

      • Accumulate(玩家 : TrgPlayer, 不少于/不多于/等于 : TrgComparison, 数值, 资源类型 : TrgResource) : Condition
        判断 [玩家] 收集的 [资源类型] 是否 [不少于/不多于/等于] [数值]

      示例

      if ( Accumulate(P1, AtLeast, 500, Ore) ) {
      // 如果 玩家1 收集不少于 500 水晶矿
      }

    • Bring

      • Bring(玩家 : TrgPlayer, 不少于/不多于/等于 : TrgComparison, 数值, 单位类型 : TrgUnit, 指定区域 : TrgLocation) : Condition
        判断 [玩家] 在 [指定区域] 的 [单位类型] 是否 [不少于/不多余/等于] [数值] 个

        Bring 第二个参数为 AtMost (不多于)的情况下,会检测到尚未建造完成的建筑、孵化中的虫卵;不会检测到被装载的单位、尚在训练中的单位;会忽略掉 [指定区域] 的高度设置
        Bring 第二个参数为 AtLeast/Exactly (不少于/等于)的情况下,会检测到被装载的单位;不会检测到尚在训练中的单位、尚未建造完成的建筑、孵化中的虫卵
        Bring 无法检测到 Scanner Sweep(扫雷达特效单位)和 Map Revealers(地图小雷达)
        使用 KillUnit 或 KillUnitAt 杀死的单位在当前帧仍然可以被 Bring 条件检测到;使用 RemoveUnit 或 RemoveUnitAt 移除的单位在当前帧不再可以被 Bring 条件检测到,并且也会让在这之前用 KillUnit 或 KillUnitAt 杀死的单位也不再被 Bring 检测到。

        Bring 条件 Bug

      示例

      KillUnitAt(All, "Terran Marine", $L("Location 1"), P1); // 杀死 玩家1 在 Location 1 所有的 机枪兵,这个动作之后在当前帧 Bring 条件仍然可以检测到 玩家1 在 Location 1 的 机枪兵
      RemoveUnitAt(1, "Map Revealer", "Anywhere", P1); // 这个动作不会移除任何单位,但是会刷新当前帧在这之前用 KillUnit、KillUnitAt 杀死的所有单位,以确保 Bring 不再会检测到当前帧被杀死的单位
      if ( Bring(P1, AtLeast, 15, "Terran Marine", $L("Location 1")) ) {
      // 如果 玩家1 在 Location 1 这个区域的机枪兵数量不少于 15 个
      }

    • Command

      • Command(玩家 : TrgPlayer, 不少于/不多于/等于 : TrgComparison, 数值, 单位类型 : TrgUnit) : Condition
        判断地图上受 [玩家] 控制的 [单位类型] 是否 [不少于/不多于/等于] [数值] 个

        Command 条件的第二个参数为 AtMost (不多于)的情况下,会检测到被装载的单位、尚在训练中的单位、尚未建造完成的建筑、孵化中的虫卵
        Command 条件的第二个参数为 AtLeast/Exactly (不少于/等于)的情况下,会检测到已装载的单位;不会检测到尚在训练中的单位、尚未建造完成的建筑、孵化中的虫卵
        Command 可以检测到 Scanner Sweep(扫雷达特效单位)和 Map Revealers(地图小雷达)
        使用 KillUnit、KillUnitAt、RemoveUnit、RemoveUnitAt 杀死移除的单位在当前帧仍然可以被 Command 条件检测到。

      • CommandMost(单位类型 : TrgUnit) : Condition
        判断地图上受到当前玩家控制的 [单位类型] 是否比其它任何一个玩家都多(包括中立玩家)

      • CommandLeast(单位类型 : TrgUnit) : Condition
        判断地图上受到当前玩家控制的 [单位类型] 是否比其它任何一个玩家都少(包括中立玩家)

      • CommandMostAt(单位类型 : TrgUnit, 指定区域 : TrgLocation) : Condition
        判断 [指定区域] 受到当前玩家控制的 [单位类型] 是否比其它任何一个玩家都多(包括中立玩家)

      • CommandLeastAt(单位类型 : TrgUnit, 指定区域 : TrgLocation) : Condition
        判断 [指定区域] 受到当前玩家控制的 [单位类型] 是否比其它任何一个玩家都少(包括中立玩家)

      示例

      const cp = getcurpl();

      foreach (p: EUDLoopPlayer()) {
      setcurpl(p);
      if ( Command(CurrentPlayer, AtMost, 0, "(buildings)") ) { // 当 Command 条件的第二个参数为 AtMost 的时候,统计会包含尚未建造完的单位/建筑
      Defeat(); // 当前玩家的建筑物不多于 0 的情况下,判定为失败
      }

      if ( CommandMost("Terran Marine") ) {
      println("玩家 {} 的机枪兵最多", p);
      }

      if ( CommandLeast("Terran Marine") ) {
      println("玩家 {} 的机枪兵最少", p);
      }

      if ( CommandMostAt("Terran Marine", $L("Location 1")) ) {
      println("玩家 {} 在 Location 1 的机枪兵最多", p);
      }

      if ( CommandLeastAt("Terran Marine", $L("Location 1")) ) {
      println("玩家 {} 在 Location 1 的机枪兵最少", p);
      }
      }

      setcurpl(cp);

    • CountdownTimer

      • CountdownTimer(不少于/不多于/等于 : TrgComparison, 秒数) : Condition
        判断倒数计时器剩余的秒数是否 [不少于/不多于/等于] [秒数] 游戏秒

      该条件不应该使用 等于(Exactly)方法判断,因为并不是每游戏秒都会有一次触发器轮询,一游戏秒为 16 游戏帧(Frame),不等于一现实秒

      示例

      if ( CountdownTimer(AtMost, 1) ) {
      PauseTimer();
      }

    • Deaths

      • Deaths(玩家 : TrgPlayer, 不少于/不多于/等于 : TrgComparison, 数值, 单位类型 : TrgUnit) : Condition
        判断 [玩家] 的 [单位类型] 死亡数量是否 [不少于/不多于/等于] [数值] 个

        当 [玩家] 或者 [单位类型] 不在合理的范围时

        它就是 EUD 条件,功能为判断 0x58A364 + ([玩家] * 4 + [单位类型] * 48) 这个内存地址上存储的 32 位正整数值是否 [不少于/不多于/等于] [数值]
        其同步性取决于0x58A364 + ([玩家] * 4 + [单位类型] * 48)这个内存地址上存储的数据的同步性

      • DeathsX(玩家: TrgPlayer, 不少于/不多于/等于: TrgComparison, 数值, 单位类型: TrgUnit, 掩码) : Condition

        这个条件通常不用于判断玩家单位死亡数

        通常用于判断 0x58A364 + ([玩家] * 4 + [单位类型] * 48) 这个内存地址上存储的 32 位 (正整数值 & [掩码]) 是否 [不少于/不多于/等于] [数值]
        其同步性取决于0x58A364 + ([玩家] * 4 + [单位类型] * 48)这个内存地址上存储的数据的同步性

      Note 使用触发器动作杀死或者移除的单位不计入死亡数(Deaths);
      自爆单位(Zerg Scourge 爆蚊、Infested Terran 自爆人、Vulture Spider Mine 蜘蛛雷)成功自爆不会计入死亡数(Deaths),但被其它单位杀死(没有成功自爆)则计入死亡数;
      被己方或同盟杀死的单位也会计入死亡数(Deaths)。

      示例

      if ( Deaths(P1, AtLeast, 15, "Terran Marine") ) {
      // 如果 玩家1 有不少于 15 个机枪兵死亡
      }

    • Memory

      • Memory(内存地址, 不少于/不多于/等于: TrgComparison, 数值) : Condition
        判断 [内存地址] 上存储的 32 位正整数值是否 [不少于/不多于/等于] [数值]
        其同步性取决于 [内存地址] 上存储的数据的同步性

      • MemoryX(内存地址, 不少于/不多于/等于: TrgComparison, 数值, 掩码) : Condition
        判断 [内存地址] 的上存储的 32 位 (正整数值 & [掩码]) 是否 [不少于/不多于/等于] [数值]
        其同步性取决于 [内存地址] 上存储的数据的同步性

      • MemoryEPD(epd, 不少于/不多于/等于: TrgComparison, 数值) : Condition
        判断0x58A364 + ([epd] * 4)这个内存地址上存储的 32 位正整数值是否 [不少于/不多于/等于] [数值]
        其同步性取决于0x58A364 + ([epd] * 4)这个内存地址上存储的数据的同步性

      • MemoryXEPD(epd, 不少于/不多于/等于: TrgComparison, 数值, 掩码) : Condition
        判断0x58A364 + ([epd] * 4)这个内存地址上存储的 32 位 (正整数值 & [掩码]) 是否 [不少于/不多于/等于] [数值]
        其同步性取决于0x58A364 + ([epd] * 4)这个内存地址上存储的数据的同步性

      示例

      function MorphLarvaEPD(epd, newUnit: TrgUnit) {
      if (MemoryXEPD(epd + 0x64/4, Exactly, 35, 0xFFFF)) {
      SetMemoryXEPD(epd + 0x4D/4, SetTo, 42 << 8, 0xFFFF00);
      SetMemoryXEPD(epd + 0x98/4, SetTo, newUnit, 0xFFFF);
      }
      }

    • Kills

      • Kills(玩家 : TrgPlayer, 不少于/不多于/等于 : TrgComparison, 数值, 单位类型 : TrgUnit) : Condition
        判断 [玩家] 的 [单位类型] 击杀数是否 [不少于/不多于/等于] [数值] 个
        击杀数 不是 击杀分数,注意区分
        杀死己方单位不计入击杀数

      示例

      if ( Kills(P1, AtLeast, 15, "Terran Marine") ) {
      // 如果 玩家1 杀死了不少于 15 个机枪兵
      }

    • ElapsedTime

      • ElapsedTime(不少于/不多于/等于 : TrgComparison, 游戏秒) : Condition
        判断游戏逝去时间是否 [不少于/不多于/等于] [数值] 游戏秒

      该条件不应该使用 等于(Exactly)方法判断,因为并不是每游戏秒都会有一次触发器轮询,一游戏秒为 16 游戏帧(Frame),不等于一现实秒

      示例

      if ( ElapsedTime(AtLeast, 5) ) {
      // 游戏逝去时间超过 5 游戏秒
      }

    • LeastKills/MostKills

      • LeastKills(单位类型 : TrgUnit) : Condition
        判断当前玩家击杀 [单位类型] 数量是否全场最少

      • MostKills(单位类型 : TrgUnit) : Condition
        判断当前玩家击杀 [单位类型] 数量是否全场最多

      示例

      if ( LeastKills("Terran Marine") ) {
      // 当前玩家杀死的机枪兵最少
      }

      if ( MostKills("Terran Marine") ) {
      // 当前玩家杀死的机枪兵最多
      }

    • LeastResources/MostResources

      • LeastResources(资源类型 : TrgResource) : Condition
        判断当前玩家的 [资源类型] 是否全场最少

      • MostResources(资源类型 : TrgResource) : Condition
        判断当前玩家的 [资源类型] 是否全场最多

      示例

      if ( LeastResources(Ore) ) {
      // 当前玩家水晶矿全场最少
      }

      if ( MostResources(Gas) ) {
      // 当前玩家气矿全场最多
      }

    • Opponents

      • Opponents(玩家 : TrgPlayer, 不少于/不多于/等于 : TrgComparison, 数值) : Condition
        判断 [玩家] 在当前游戏中的对手是否 [不少于/不多于/等于] [数值] 个

      示例

      if ( Opponents(P1, AtMost, 2) ) {
      // 玩家1 的对手 不多于 2 个
      }

    • Score

      • Score(玩家 : TrgPlayer, 得分类型 : TrgScore, 不少于/不多于/等于 : TrgComparison, 数值) : Condition
        判断 [玩家] 的 [得分类型] 分数是否 [不少于/不多于/等于] [数值] 分

      • LowestScore(得分类型 : TrgScore) : Condition
        判断当前玩家现在的 [得分类型] 是不是全场最低分

      • HighestScore(得分类型 : TrgScore) : Condition
        判断当前玩家现在的 [得分类型] 是不是全场最高分

      示例

      if ( Score(P1, Kills, AtLeast, 10000) ) {
      // 玩家1 的 击杀分数 不少于 10000 分,击杀分数 不是 击杀数,注意区别
      }

      if ( LowestScore(Buildings) ) {
      // 如果当前玩家现在的 建筑分 是最低分
      }

      if ( HighestScore(Kills) ) {
      // 如果当前玩家现在的 击杀分 是最高分
      }

    • Switch

      • Switch(开关 : TrgSwitch, 开关状态 : TrgSwitchState) : Condition
        判断 [开关] 的状态是否为 [开关状态]

      示例

      if ( Switch($S("Switch 1"), Set) ) {
      // Switch 1 的状态是 Set
      }

      if ( Switch($S("Switch 1"), Cleared) ) {
      // Switch 1 的状态是 Cleared
      }

    • Always/Never

      • Always() : Condition
        总是无条件执行,在 epScript 中无用

      • Never() : Condition
        总是不执行,在 epScript 中绝大多数情况下该函数无用



  • 扩展条件函数

    • IsUserCP

      • IsUserCP(): Condition
        非同步条件,用于检测本机玩家是否为当前玩家

    • Is64BitWireframe

      • Is64BitWireframe(): Condition
        非同步条件,检测本机星际客户端是否为 64 位的


  • 普通动作函数

    普通动作函数是依据传统触发器中的动作封装的函数,就像 ScmDraft2 里面的触发器动作那样
    所有的触发器动作函数(也包括扩展触发器函数)都返回一个动作表达式常量,动作表达式执行动作表达式这两个概念需要区分清楚
    若两个分号之间非注释代码仅为动作函数调用,epScript 会将它传入一个无条件触发器 DoActions 执行,参考示例

    const a1 = CenterView($L("Location 1")); // 这是声明一个动作常量表达式,并不会执行它
    CenterView($L("Location 1")); // 这表示执行一个动作,等同于 DoActions(CenterView($L("Location 1")));
    DoActions(a1); // 第一行声明的 a1 这个时候才执行

    • CenterView

      • CenterView(指定位置 : TrgLocation) : Action
        允许非同步执行,将当前玩家的镜头设置到指定位置

      示例

      setcurpl(P1);
      CenterView($L("Location 1"));

    • CreateUnit

      • CreateUnit(个数, 单位类型 : TrgUnit, 指定位置 : TrgLocation, 玩家 : TrgPlayer) : Action
        给 [玩家] 在 [指定位置] 创建 [个数] 个 [单位类型],单位创建的一瞬间,单位所占用的人口会立刻(在当前帧)上升。

      • CreateUnitWithProperties(个数, 单位类型 : TrgUnit, Where : 指定位置 : TrgLocation, 玩家 : TrgPlayer, 属性 : TrgProperty) : Action
        给 [玩家] 在 [指定位置] 创建 [个数] 个 [单位类型] 并附带 [属性] 属性,单位创建的一瞬间,单位所占用的人口会立刻(在当前帧)上升。

      示例

      CreateUnit(2, "Terran Siege Tank", $L("Location 1"), P1);
      CreateUnitWithProperties(1, "Terran Marine", $L("Location 1"), P1, UnitProperty(
      hitpoint = 100, // 生命百分比
      shield = 100, // 护盾百分比
      energy = 100, // 能量百分比
      hanger = 0, //
      resource = 0, //
      cloaked = False, // 是否隐形
      burrowed = False, // 是否已钻地
      intransit = False, // 是否正在被运输
      hallucinated = False, // 是否是个幻影
      invincible = False) // 是否无敌
      );

    • Defeat/Victory/Draw

      • Defeat() : Action
        当前玩家败北结束游戏

      • Victory() : Action
        当前玩家获胜结束游戏

      • Draw() : Action
        所有玩家平局结束游戏


    • DisplayText

      • DisplayText(文本 : TrgString) : Action
        允许非同步执行,在当前玩家屏幕上的文本区的下一行显示 [文本]

        该动作的参数 [文本] 实际为该文本条目在地图字符串表(Map String Table)中的编号,假如这个条目在地图字符串表不存在,那么 epScript 会先将该 [文本] 插入到地图字符串表中然后将其编号作为它的参数。

      示例

      const idx = $T("Hello StarCraft!");
      dbstr_print(GetMapStringAddr(idx), "WTF StarCraft!");
      DisplayText("Hello StarCraft!"); // 输出 WTF StarCraft!

    • GiveUnits

      • GiveUnits(个数 : TrgCount, 单位类型 : TrgUnit, 所有者 : TrgPlayer, 指定区域 : TrgLocation, 接手者 : TrgPlayer) : Action
        将 [指定区域] 的最多 [个数] 个 [所有者] 玩家的 [单位类型] 送给 [接手者] 玩家,[个数] 为 0 代表所有(All)

        这个动作会导致单位的集结点丢失

      示例

      // 将 Location 1 区域的 玩家2 的最多 3个 机枪兵送给 玩家1
      GiveUnits(3, "Terran Marine", P2, $L("Location 1"), P1);

    • KillUnit

      • KillUnit(单位类型 : TrgUnit, 玩家 : TrgPlayer) : Action
        杀死 [玩家] 所有的 [单位类型],包含尚在建造队列中的单位,可以杀死核弹井中尚未发射的核弹

      • KillUnitAt(个数 : TrgCount, 单位类型 : TrgUnit, 指定区域 : TrgLocation, 玩家 : TrgPlayer) : Action
        杀死最多 [个数] 个 [玩家] 在 [指定区域] 的 [单位类型],[个数] 为 0 代表所有(All),不包含尚在建造队列中的单位,不会杀死核弹井中尚未发射的核弹

        KillUnitAt(All, "Scanner Sweep", "Anywhere", P1) 无法杀死雷达特效单位

        Warning
        该动作存在一个这样的 bug:
        假如执行该动作杀死了区域中的某个装载单位(运输机/碉堡等)里面的任意一个单位,那么该装载单位(运输机/碉堡等)里面的所有同类型单位都会被杀死,并且这些被杀死单位不会算在 [个数] 参数指定的个数以内
        假如执行 KillUnitAt(1, "Terran Marine", "Location 1", P1) 这个动作杀死了 Location 1 区域的一个碉堡中的机枪兵,那么该碉堡内的所有机枪兵都会被杀死,并且这个区域碉堡外如果还有机枪兵,还会被杀死一个

      示例

      KillUnit("Terran Marine", P1); // 杀死 玩家1 所有的机枪兵
      KillUnitAt(3, "Terran Siege Tank", $L("Location 1"), P1) // 杀死 玩家1 在 Location 1 区域的最多 3 个坦克

    • LeaderBoard

      • LeaderBoardComputerPlayers(状态 : TrgPropState) : Action
        设定排行榜对电脑玩家的启用状态

      • LeaderBoardControl(单位类型 : TrgUnit, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家 [单位类型] 控制数从大到小的排行榜

      • LeaderBoardControlAt(单位类型 : TrgUnit, 指定区域 : TrgLocation, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家在 [指定区域] 的 [单位类型] 控制数从大到小的排行榜

      • LeaderBoardGoalControl(目标个数, 单位类型 : TrgUnit, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家 [单位类型] 控制数最接近 [目标个数] 的排行榜

      • LeaderBoardGoalControlAt(目标个数, 单位类型 : TrgUnit, 指定区域 : TrgLocation, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家在 [指定区域] 的 [单位类型] 控制数最接近 [目标个数] 的排行榜

      • LeaderBoardGoalKills(目标个数, 单位类型 : TrgUnit, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家击杀 [单位类型] 数量最接近 [目标个数] 的排行榜

      • LeaderBoardGoalResources(目标数量, ResourceType : TrgResource, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家采集 [资源类型] 数量最接近 [目标数量] 的排行榜

      • LeaderBoardGoalScore(目标分数, 得分类型 : TrgScore, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家 [得分类型] 分数最接近 [目标分数] 的排行榜

      • LeaderBoardGreed(目标数量) : Action
        显示所有玩家采集水晶矿和气矿最接近 [目标数量] 的排行榜

      • LeaderBoardKills(单位类型 : TrgUnit, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家击杀 [单位类型] 个数的排行榜

      • LeaderBoardResources(资源类型 : TrgResource, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家收集 [资源类型] 数量的排行榜

      • LeaderBoardScore(得分类型 : TrgScore, 标签 : TrgString) : Action
        显示描述为 [标签] 所有玩家的 [得分类型] 得分的排行榜

      示例

      LeaderBoardGoalControlAt(10, "Terran Marine", $L("目的地"), "机枪兵到达目地");

    • MinimapPing

      • MinimapPing(指定位置 : TrgLocation) : Action
        允许非同步执行,为当前玩家在小地图 [指定位置] 显示 Ping

      示例

      // 给 玩家1 小地图的 Location 1 位置显示 Ping
      setcurpl(P1);
      MinimapPing($("Location 1"));

    • ModifyUnit

      • ModifyUnitEnergy(个数 : TrgCount, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 指定区域 : TrgLocation, 百分数) : Action
        更改 [玩家] 在 [指定区域] 的最多 [个数] 个 [单位类型] 的能量为百分之 [百分数],[个数] 为 0 代表所有(All)

      • ModifyUnitHangarCount(新增, 个数 : TrgCount, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 指定区域 : TrgLocation) : Action
        给 [玩家] 在 [指定区域] 的最多 [个数] 个 [单位类型] 增加最多 [新增] 装载单位,[个数] 为 0 代表所有(All)

        例如航母的小飞机、金甲的子弹;注意,该动作对无法增加雷车的地雷数量

      • ModifyUnitHitPoints(个数 : TrgCount, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 指定区域 : TrgLocation, 百分数) : Action
        更改 [玩家] 在 [指定区域] 的最多 [个数] 个 [单位类型] 的生命值为百分之 [百分数],[个数] 为 0 代表所有(All)

      • ModifyUnitResourceAmount(个数 : TrgCount, 玩家 : TrgPlayer, 指定区域 : TrgLocation, 新值) : Action
        将 [玩家] 在 [指定区域] 的最多 [个数] 个单位的资源值改为 [新值],[个数] 为 0 代表所有(All)

      • ModifyUnitShields(个数 : TrgCount, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 指定区域 : TrgLocation, 百分数) : Action
        更改 [玩家] 在 [指定区域] 的最多 [个数] 个 [单位类型] 的护盾值为百分之 [百分数],[个数] 为 0 代表所有(All)

      示例

      // 将 玩家1 在 Location 1 区域的最多 100个 机枪兵 的生命值设置为 100%
      ModifyUnitHitPoints(100, "Terran Marine", P1, $L("Location 1"), 100);

    • MoveLocation

      • MoveLocation(需要移动的位置 : TrgLocation, 目标单位类型 : TrgUnit, 玩家 : TrgPlayer, 目标区域 : TrgLocation) : Action
        将 [需要移动的位置] 的中心对准到 [玩家] 在 [目标区域] 的某个 [目标单位类型] 身上

      示例

      // 将 Location 1 区域的中心移动到 玩家1 在地图上任意位置的某个机枪兵身上
      MoveLocation($L("Location 1"), "Terran Marine", P1, $L("AnyWhere"));

    • MoveUnit

      • MoveUnit(个数 : TrgCount, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 起始区域 : TrgLocation, 目标区域 : TrgLocation) : Action
        将 [玩家] 在 [起始区域] 最多 [个数] 个 [单位类型] 瞬间移动到 [目标区域],[个数] 为 0 代表所有(All)

      示例

      // 将 玩家1 在 Location 1 区域的最多 10个 机枪兵 瞬移到 Location 2
      MoveUnit(10, "Terran Marine", P1, $L("Location 1"), $L("Location 2"));

    • MuteUnitSpeech/UnMuteUnitSpeech

      • MuteUnitSpeech() : Action
        允许非同步执行,给当前玩家所有单位讲话静音(触发器单位讲话例外)

      • UnMuteUnitSpeech() : Action
        允许非同步执行,给当前玩家所有单位讲话取消静音(触发器单位讲话例外)


    • Order

      • Order(单位类型 : TrgUnit, 玩家 : TrgPlayer, 起始区域 : TrgLocation, 命令 : TrgOrder, 目标区域 : TrgLocation) : Action
        对 [玩家] 在 [起始区域] 的 [单位类型] 发布 [命令] 指向 [目标区域] 中心

      示例

      // 命令 玩家1 在 Location 1 的所有 机枪兵 攻击-移动到 Location 2 区域中心
      Order("Terran Marine", P1, $L("Location 1"), Attack, $L("Location 2"))

    • PauseGame/UnpauseGame

      • PauseGame() : Action
        为所有玩家暂停游戏

      • UnpauseGame() : Action
        为所有玩家继续游戏


    • PauseTimer/UnpauseTimer

      • PauseTimer() : Action
        为所有玩家暂停倒数计时器

      • UnpauseTimer() : Action
        为所有玩家继续倒数计时器


    • PlayWAV

      • PlayWAV(WAVName : TrgString) : Action
        允许非同步执行,为当前玩家播放一个 WAV 文件 [WAVName]

    • PreserveTrigger

      • PreserveTrigger() : Action
        保留触发器,因为传统触发器执行一次后就失效,所以需要重复触发则需要加上这一条,在 epScript 中无用

    • RemoveUnit

      • RemoveUnit(单位类型 : TrgUnit, 玩家 : TrgPlayer) : Action
        从地图上移除 [玩家] 所有的 [单位类型],包含尚在建造队列中的单位,可以移除核弹井中尚未发射的核弹,单位被移除后,单位所占用的人口要下一帧才会下降。

      • RemoveUnitAt(个数 : TrgCount, 单位类型 : TrgUnit, 指定区域 : TrgLocation, 玩家 : TrgPlayer) : Action

        从地图上移除 [玩家] 在 [指定区域] 最多 [个数] 个 [单位类型] ,[个数] 为 0 代表所有(All),不包含尚在建造队列中的单位,不会移除核弹井中尚未发射的核弹,单位被移除后,单位所占用的人口要下一帧才会下降。

        RemoveUnitAt(All, "Scanner Sweep", "Anywhere", P1) 无法移除雷达特效单位
        RemoveUnitAt(All, "Map Revealer", "Anywhere", P1) 无法移除地图小雷达单位

        Warning
        该动作存在一个这样的 bug:
        假如执行该动作移除了区域中的某个装载单位(运输机/碉堡等)里面的任意一个单位,那么该装载单位(运输机/碉堡等)里面的所有同类型单位都会被移除,并且这些被移除单位不会算在 [个数] 参数指定的个数以内
        假如执行 RemoveUnitAt(1, "Terran Marine", "Location 1", P1) 这个动作移除了 Location 1 区域的一个碉堡中的机枪兵,那么该碉堡内的所有机枪兵都会被移除,并且这个区域碉堡外如果还有机枪兵,还会被移除一个


    • RunAIScript

      • RunAIScript(Script : TrgAIScript) : Action
        对当前玩家执行 AI 脚本 [Script]

      • RunAIScriptAt(Script : TrgAIScript, 指定区域 : TrgLocation) : Action
        对当前玩家在 [指定区域] 执行 AI 脚本 [Script]

        示例

        RunAIScriptAt("Terran Custom Level", $L("Location 1"));

    • SetAllianceStatus

      • SetAllianceStatus(目标玩家 : TrgPlayer, 状态 : TrgAllyStatus) : Action
        设置当前玩家对 [目标玩家] 的联盟状态为 [状态]

        示例

        // 设置 玩家1 对 玩家2 友善,玩家2 对 玩家1 敌对
        setcurpl(P1);
        SetAllianceStatus(P2, Ally);
        setcurpl(P2);
        SetAllianceStatus(P1, Enemy);

    • SetCountdownTimer

      • SetCountdownTimer(设为/增加/减少 : TrgModifier, 游戏秒) : Action
        设置倒数计时器 [设为/增加/减少] [游戏秒] 游戏秒,一游戏秒为 16 帧

        示例

        SetCountdownTimer(SetTo, 100); // 将倒数计时器设置为 100 游戏秒
        SetCountdownTimer(Add, 5); // 给倒数计时器加 5 游戏秒
        SetCountdownTimer(Substract, 3); // 给倒数计时器减少 3 游戏秒

    • SetDeaths

      • SetDeaths(玩家 : TrgPlayer, 设为/增加/减少 : TrgModifier, 数值, 单位类型 : TrgUnit) : Action
        设置 [玩家] 的 [单位类型] 的死亡个数 [设为/增加/减少] [数值] 个

        当 [玩家] 或者 [单位类型] 不在合理的范围时

        它将是一个 EUD 动作,功能为将 0x58A364 + ([玩家] * 4 + [单位类型] * 48) 这个内存地址上存储的当前值 [设为/增加/减少] [数值]
        其是否允许非同步执行取决于0x58A364 + ([玩家] * 4 + [单位类型] * 48)这个内存地址上存储的数据的同步性

      • SetDeathsX(玩家 : TrgPlayer, 设为/增加/减少: TrgModifier, 数值, 单位类型: TrgUnit, 掩码) : Action

        这个函数通常不用于设置玩家单位死亡数量
        设为(SetTo)   :将 `0x58A364 + ([玩家] * 4 + [单位类型] * 48)` 这个内存地址上存储的当前值设为 `当前值 - (当前值 & 掩码) + (数值 & 掩码)`
        增加(Add) :将 `0x58A364 + ([玩家] * 4 + [单位类型] * 48)` 这个内存地址上存储的当前值设为 `当前值 - (当前值 & 掩码) + ( ((当前值 & 掩码) + (数值 & 掩码)) & 掩码 )`
        减少(Subtract):将 `0x58A364 + ([玩家] * 4 + [单位类型] * 48)` 这个内存地址上存储的当前值设为 `当前值 - (当前值 & 掩码) + ( ((当前值 & 掩码) - (数值 & 掩码)) & 掩码 )` (公式中的减法最小可以减为 0)

        其是否允许非同步执行取决于0x58A364 + ([玩家] * 4 + [单位类型] * 48)这个内存地址上存储的数据的同步性

        示例

        SetDeaths(P1, Add, 10, "Terran Marine"); // 将 玩家1 的 机枪兵 死亡数 +10

    • SetMemory

      • SetMemory(内存地址, 设为/增加/减少 : TrgModifier, 数值) : Action
        将 [内存地址] 上的存储的 32 位正整数值 [设为/增加/减少] [数值]
        其是否允许非同步执行取决于 [内存地址] 上存储的数据的同步性

      • SetMemoryX(内存地址, 设为/增加/减少 : TrgModifier, 数值, 掩码) : Action

        支持掩码访问的 SetMemory,可以修改 32 位中任何一位或多位的值
        设为(SetTo)   :将 [内存地址] 上存储的当前值设为 `当前值 - (当前值 & 掩码) + (数值 & 掩码)`
        增加(Add) :将 [内存地址] 上存储的当前值设为 `当前值 - (当前值 & 掩码) + ( ((当前值 & 掩码) + (数值 & 掩码)) & 掩码 )`
        减少(Subtract):将 [内存地址] 上存储的当前值设为 `当前值 - (当前值 & 掩码) + ( ((当前值 & 掩码) - (数值 & 掩码)) & 掩码 )` (公式中的减法最小可以减为 0)

        其是否允许非同步执行取决于 [内存地址] 上存储的数据的同步性

      • SetMemoryEPD(epd : TrgPlayer, 设为/增加/减少 : TrgModifier, 数值) : Action
        0x58A364 + ([epd] * 4)这个内存地址上存储的 32 位正整数值 [设为/增加/减少] [数值]
        其是否允许非同步执行取决于0x58A364 + ([epd] * 4)这个内存地址上存储的数据的同步性

      • SetMemoryXEPD(epd : TrgPlayer, 设为/增加/减少 : TrgModifier, 数值, 掩码) : Action

        支持掩码访问的 SetMemoryEPD,可以修改 32 位中任何一位或多位的值
        设为(SetTo)   :将 `0x58A364 + ([epd] * 4)` 这个内存地址上存储的当前值设为 `当前值 - (当前值 & 掩码) + (数值 & 掩码)`
        增加(Add) :将 `0x58A364 + ([epd] * 4)` 这个内存地址上存储的当前值设为 `当前值 - (当前值 & 掩码) + ( ((当前值 & 掩码) + (数值 & 掩码)) & 掩码 )`
        减少(Subtract):将 `0x58A364 + ([epd] * 4)` 这个内存地址上存储的当前值设为 `当前值 - (当前值 & 掩码) + ( ((当前值 & 掩码) - (数值 & 掩码)) & 掩码 )` (公式中的减法最小可以减为 0)

        其是否允许非同步执行取决于0x58A364 + ([epd] * 4)这个内存地址上存储的数据的同步性

      示例

      function MorphLarvaEPD(epd, newUnit: TrgUnit) {
      if (MemoryXEPD(epd + 0x64/4, Exactly, 35, 0xFFFF)) {
      SetMemoryXEPD(epd + 0x4D/4, SetTo, 42 << 8, 0xFFFF00);
      SetMemoryXEPD(epd + 0x98/4, SetTo, newUnit, 0xFFFF);
      }
      }

    • SetDoodadState

      • SetDoodadState(State : TrgPropState, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 指定区域 : TrgLocation) : Action
        将 [玩家] 在 [指定区域] 中的 [单位类型] 的 Doodad 状态设置为 [State]

      示例

      SetDoodadState(Enable, "Terran Marine", P1, $L("Location 1"));

    • SetInvincibility

      • SetInvincibility(State : TrgPropState, 单位类型 : TrgUnit, 玩家 : TrgPlayer, 指定区域 : TrgLocation) : Action
        设置 [玩家] 在 [指定区域] 的 [单位类型] 的无敌状态为 [State]

      示例

      SetInvincibility(Enable, "Terran Marine", P1, $L("Location 1")); // 将 玩家1 在 Location 1 区域的 机枪兵 的无敌状态设置为 启用
      SetInvincibility(Disable, "Terran Marine", P1, $L("Location 1")); // 将 玩家1 在 Location 1 区域的 机枪兵 的无敌状态设置为 禁用

    • SetMissionObjectives

      • SetMissionObjectives(文本 : TrgString) : Action
        允许非同步执行,设置当前玩家任务目标描述为 [文本]

        该动作的参数 [文本] 实际为该文本条目在地图字符串表(Map String Table)中的编号,假如这个条目在地图字符串表不存在,那么 epScript 会先将该 [文本] 插入到地图字符串表中然后将其编号作为它的参数。

      示例

      SetMissionObjectives("我们的目标是:\n没有蛀牙!");

    • SetNextScenario

      • SetNextScenario(文本 : TrgString) : Action
        设置本局游戏完成后要载入的下一张地图名 [文本],必须要在同一目录

        Note 只能用于单人游戏,并且在 EUD 地图中无用,epScript 中无用


    • SetResources

      • SetResources(玩家 : TrgPlayer, 设为/增加/减少 : TrgModifier, 数值, 资源类型 : TrgResource) : Action
        设置 [玩家] 的 [资源类型] 资源 [设为/增加/减少] [数值]

      示例

      SetResources(P1, Add, 1000, Ore); // 给 玩家1 加 1000 水晶矿
      SetResources(P1, Substract, 1000, Gas); // 给 玩家1 扣掉 1000 气矿
      SetResources(P1, SetTo, 5000, OreAndGas); // 将 玩家1 的 水晶矿 和 气矿 都设置为 2000

    • SetScore

      • SetScore(玩家 : TrgPlayer, 设为/增加/减少 : TrgModifier, 数值, 得分类型 : TrgScore) : Action
        设置 [玩家] 的 [得分类型] 得分 [设为/增加/减少] [数值]

      示例

      SetScore(P1, Add, 1000, Kills); // 给 玩家1 加 1000 分的 击杀分数

    • SetSwitch

      • SetSwitch(开关名 : TrgSwitch, 开关操作 : TrgSwitchAction) : Action
        将开关 [开关名] 的状态设置为 [开关操作]

      示例

      SetSwitch($S("Switch 1"), Set); // 设置 Switch 1 的状态为 Set
      SetSwitch($S("Switch 1"), Clear); // 设置 Switch 1 的状态为 Cleared
      SetSwitch($S("Switch 1"), Toggle); // 转换 Switch 1 的状态,如果它本来是 Set 将会转换成 Cleared,如果本来是 Cleared 那么会转换到 Set
      SetSwitch($S("Switch 1"), Random); // 设置 Switch 1 为随机状态,使用之后 Switch 1 的状态可能是 Set 也可能是 Cleared

    • TalkingPortrait

      • TalkingPortrait(单位类型 : TrgUnit, 毫秒) : Action
        允许非同步执行,在当前玩家单位头像那里显示 [单位类型] 的头像持续 [毫秒] 游戏毫秒

      示例

      // 让 机枪兵 为 玩家1 做出指示精神
      setcurpl(P1);
      TalkingPortrait("Terran Marine", 5000);

    • Transmission

      • Transmission(单位类型 : TrgUnit, 指定区域 : TrgLocation, WAVName : TrgString, 设为/增加/减少 : TrgModifier, Time, 文本 : TrgString) : Action
        允许非同步执行,为当前玩家播放一段声音 [WAVName] 并且让玩家单位头像那里显示 [指定区域] 内 [单位类型] 头像 [设为/增加/减少] [Time] 游戏毫秒,同时在小地图该单位上发出 Ping 同时输出文字信息 [文本]

        Note 该函数内部包含 Wait,会影响触发器控制流程,不建议在 epScript 中使用

      示例

      Transmission("Terran Marine", $L("Location 3"), "sound\\Zerg\\Advisor\\ZAdUpd00.WAV", Add, 5000, "我们的目标是:\n没有蛀牙!");

    • Wait

      • Wait(毫秒) : Action

        等待 [毫秒]

        Note 该函数会影响触发器控制流程,不建议在 epScript 中使用



  • 扩展动作函数

    扩展动作函数是一些无法在传统触发器中找到对应的动作的函数,但它仍然可以看作是触发器动作,会返回动作常量表达式或表达式列表
    可以加入 RawTrigger 或 DoActions 的动作列表,它可能不再是单一的触发器动作


    • SetKills

      • SetKills(玩家: TrgPlayer, 设为/增加/减少 : TrgModifier, 数值, 单位类型: TrgUnit) : Action | [Action]
        设置 [玩家] 的对 [单位类型] 击杀数 [设为/增加/减少] [数值]
        击杀数 不是 击杀分数,注意区分
        由一条或者三条传统触发器构成,取决于玩家编号是否为 CurrentPlayer

      示例

      // https://armoha.github.io/eud-book/offsets/KilledUnitCountsTable.html
      // 事实上传统触发器是没有 SetKills 这个动作的,它是用 EUD 模拟出来的

      // 如果 玩家编号 不是 CurrentPlayer(13) 的情况下,它的内部实现大概是这样,返回一条常量表达式
      SetDeaths(玩家编号 - 2736, 设为/增加/减少, 数值, 单位类型);

      // 如果 玩家编号 是 CurrentPlayer(13) 的情况下,它的内部实现大概是这样,返回包含三条常量表达式的 tuple
      DoActions(
      SetDeaths(EPD(0x6509B0), Add, -2736, 0),
      SetDeaths(CurrentPlayer, 设为/增加/减少, 数值, 单位类型),
      SetDeaths(EPD(0x6509B0), Add, 2736, 0),
      );

    • SetCurrentPlayer

      • SetCurrentPlayer(playerid : TrgPlayer) : [Action]
        允许非同步执行,将当前玩家和 cpcache 设置为 [playerid]
        由三条传统触发器动作构成

      示例

      RawTrigger(actions = list(
      SetCurrentPlayer(P1),
      DisplayText("给玩家1显示的内容"),
      SetCurrentPlayer(P2),
      DisplayText("给玩家2显示的内容"),
      SetCurrentPlayer(P3),
      DisplayText("给玩家3显示的内容"),
      ));

    • AddCurrentPlayer

      • AddCurrentPlayer(playerid : TrgPlayer) : [Action]
        允许非同步执行,将当前玩家和 cpcache 自增 [playerid]
        由三条传统触发器动作构成

      示例

      RawTrigger(actions = list(
      SetCurrentPlayer(P3),
      DisplayText("给玩家3显示的内容"),
      AddCurrentPlayer(-1),
      DisplayText("给玩家2显示的内容"),
      AddCurrentPlayer(-1),
      DisplayText("给玩家1显示的内容"),
      ));

      // https://armoha.github.io/eud-book/offsets/GameSpeedRefreshRate.html
      // 将 Slowest 到 Fastest 游戏速度都设置为 200%
      RawTrigger(actions = list(
      SetCurrentPlayer(EPD(0x5124D8)),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      AddCurrentPlayer(1),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      AddCurrentPlayer(1),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      AddCurrentPlayer(1),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      AddCurrentPlayer(1),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      AddCurrentPlayer(1),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      AddCurrentPlayer(1),
      SetDeaths(CurrentPlayer, SetTo, 21, 0),
      ));

    • DisplayTextAll

      • DisplayTextAll(文本 : TrgString) : [Action]
        为所有的玩家(包括观察者)文本区的下一行显示 [文本]
        由两条传统触发器动作构成

    • PlayWAVAll

      • PlayWAVAll(声音路径) : [Action]
        为所有的玩家(包括观察者)播放声音 [声音路径]
        由两条传统触发器动作构成

    • MinimapPingAll

      • MinimapPingAll(位置) : [Action]
        为所有的玩家(包括观察者)在小地图上的 [位置] 发一个 Ping
        由两条传统触发器动作构成

    • CenterViewAll

      • CenterViewAll(位置) : [Action]
        将所有的玩家(包括观察者)的镜头设置到 [位置]
        由两条传统触发器动作构成

    • SetMissionObjectivesAll

      • SetMissionObjectivesAll(文本 : TrgString) : [Action]
        设置所有玩家(包括观察者)的任务目标内容更改为 [文本]
        由两条传统触发器动作构成

    • TalkingPortraitAll

      • TalkingPortraitAll(单位类型, 毫秒) : [Action]
        在所有玩家(包括观察者)单位头像那里显示 [单位类型] 的头像持续 [毫秒] 游戏毫秒
        由两条传统触发器动作构成

    • SetNextPtr

      • SetNextPtr(trg, dest) : Action
        设置触发器 [trg] 的下一个触发器指针为 [dest]
        它实质是一个 SetDeaths 动作 SetDeaths(EPD(trg + 4), SetTo, dest, 0)

      示例

      // 变量的 .getDestAddr() 方法能在编译期获得变量触发器中的 目标地址
      // 变量的 .getValueAddr() 方法能在编译期获得变量触发器中的 值地址
      // 变量的 .GetVTable() 方法能在编译期获得变量的虚拟触发器地址
      // 变量的 .SetModifier(method) 方法设置变量触发器中的数字修改方法为 method
      // SetNextPtr(trg, ptr) 函数用于设置触发器 trg 的下一个触发器为 ptr
      function afterTriggerExec() {
      var a, b = 3, 5;
      const next = Forward();
      RawTrigger(
      actions = list(
      SetMemory(b.getValueAddr(), Add, 1),
      SetMemory(a.getValueAddr(), Add, 1),
      SetMemory(b.getDestAddr(), SetTo, EPD(a.getValueAddr())),
      b.SetModifier(Add), // 它内部实现大概是 SetMemoryX(b.getValueAddr() + 4, SetTo, (Add << 24), 0xFF000000)
      SetNextPtr(b.GetVTable(), next), // 设置 b 的下一个触发器为 next,它的内部实现可能是这样的 SetMemory(b.GetVTable() + 4, SetTo, next)
      ),
      nextptr = b.GetVTable(), // 本触发器的下一个触发器为 b
      );
      next.__lshift__(NextTrigger()); // 这个的实质是将 next 这个 Forward 指向下一个 Trigger
      // 结果是 a:10 b:6
      }



扩展函数

  • 编译期

    • 编号索引

      • $L(区域名称 : 字面量字符串) : py_int

      • GetLocationIndex(区域名称 : py_str) : py_int

      • EncodeLocation(区域名称 : py_str) : py_int
        获取在地图编辑器(通常是 SCMD)中划定的区域的 [区域名称] 转换为对应的区域编号,所有函数的 TrgLocation 类型参数都会自动使用这个宏处理

      • $T(文本 : 字面量字符串) : py_int

      • GetStringIndex(文本 : py_str) : py_int

      • EncodeString(文本 : py_str) : py_int
        获取一个 [文本] 条目在地图字符串表(Map String Table)中的编号,所有函数的 TrgString 类型参数都会自动使用这个宏处理,也就是说接受 TrgString 类型作为参数的函数实际上接受的参数是字符串条目在地图字符串表中的编号。

        该宏可以任意提供 [文本] 键,假如地图字符串表已经存在该 [文本] 条目,则返回该条目在地图字符串表中的编号,如果地图字符串没有 [文本] 条目,就会新建一个 编号→文本 键值对插入地图字符串表中,并返回这个编号。

        比如 $T("Force 1") 会得到 4,因为已经存在了。而 $T("\x03啥也不是的农民") 有可能会返回 3(同时在地图字符串字典中新建一项 3 : "\x03啥也不是的农民" )

      • $S(开关名称 : 字面量字符串) : py_int

      • GetSwitchIndex(开关名称 : py_str) : py_int

      • EncodeSwitch(开关名称 : py_str) : py_int
        获取在地图编辑器(通常是 SCMD)中定义的 [开关名称] 转换为对应的开关编号,所有函数的 TrgSwitch 类型参数都会自动使用这个宏处理

      • $U(单位类型 : 字面量字符串) : py_int

      • GetUnitIndex(单位类型 : py_str) : py_int

      • EncodeUnit(单位类型 : py_str) : py_int
        获取地图 unit.dat 的 [单位类型] 转换为对应的单位类型编号,所有函数的 TrgUnit 类型参数都会自动使用这个宏处理

      • $B(TBLKey : 字面量字符串) : py_int

      • EncodeTBL(TBLKey : py_str) : py_int
        获取地图 TBL 字典中 [TBLKey] 对应的索引编号,所有函数的 StatText 类型参数都会自动使用这个宏处理

      • EncodeWeapon(武器类型 : py_str) : py_int
        获取地图 weapon.dat 的 [武器类型] 转换为对应的武器类型编号,所有函数的 Weapon 类型参数都会自动使用这个宏处理

      • EncodeTech(科技类型 : py_str) : py_int
        获取地图 tech.dat 的 [科技类型] 转换为对应的科技类型编号,所有函数的 Tech 类型参数都会自动使用这个宏处理

      所有的 $ 语法宏($L、$T、$S、$U、$B)都仅支持字面量字符串作为参数

      示例

      const l1 = $L("Location 1");
      const s2 = $S("Switch 2");
      const ut = $U("Terran Marine"); // 返回 0
      const aiid = $B("AI Harass Here"); // 返回 1538

      // 更改 SCV 单位的名称字符串编号,$T("\x03啥也不是的农民") 会在编译时插入一个新字符串到地图中,并把这个字符串的编号返回到这儿
      // https://armoha.github.io/eud-book/offsets/Units.dat-MapString.html
      wwrite(0x660260 + 2 * $U("Terran SCV"), $T("\x03啥也不是的农民"));

      // 可以直接使用 $ 语法的常量
      // EncodePlayer
      if (EncodePlayer(P1) == $P1) { py_print($P1, $P2, $CurrentPlayer, $AllPlayers, $Force1, $NonAlliedVictoryPlayers); }
      // EncodeModifier
      if (EncodeModifier(SetTo) == $SetTo) { py_print($SetTo, $Add, $Subtract); }
      // EncodeComparison
      if (EncodeComparison(AtLeast) == $AtLeast) { py_print($Exactly, $AtLeast, $AtMost); }
      // EncodeResource
      if (EncodeResource(OreAndGas) == $OreAndGas) { py_print($Ore, $Gas, $OreAndGas); }
      // EncodeSwitchAction
      if (EncodeSwitchAction(Set) == $Set) { py_print($Set, $Clear, $Toggle, $Random); } // $Set 代表 EncodeSwitchAction(Set)
      // EncodeSwitchState
      if (EncodeSwitchState(Set) != $Set) { py_print($Cleared); } // 注意这个!!!$Set 不代表 EncodeSwitchState(Set)
      // EncodeAllyStatus
      if (EncodeAllyStatus(Ally) == $Ally) { py_print($Enemy, $Ally, $AlliedVictory); }
      // EncodeOrder
      if (EncodeOrder(Move) == $Move) { py_print($Move, $Patrol, $Attack); }
      // EncodePropState
      if (EncodePropState(Enable) == $Enable) { py_print($Enable, $Disable, $Toggle); }
      // EncodeScore
      if (EncodeScore(Total) == $Total) { py_print($Total, $Units, $Buildings, $UnitsAndBuildings, $Razings, $KillsAndRazings, $Custom); } // 没有 $Kills,EncodeScore(Kills) == 4
      // EncodeCount
      if (EncodeCount(All) == 0) { py_print(EncodeCount(All)); } // EncodeCount(All) 为 0,EncodeCount 任何正整数都等于那个正整数本身

      // 还有一些不常用的我就没写文档和例子,用法一致,参照常量对照表使用
      // EncodeAIScript
      // EncodeFlingy
      // EncodeIcon
      // EncodeImage
      // EncodeIscript
      // EncodePortrait
      // EncodeProperty
      // EncodeSprite
      // EncodeUnitOrder
      // EncodeUpgrade

    • list

      • list(*args) : py_list
        创建并返回一个平坦的编译期 Python 列表,至少需要一个参数,这个函数会自动拉平列表,不能创建多维列表
        如果要创建一个空的编译期列表,可以用 py_list 函数
        编译期列表可以使用 foreach 语法在编译期迭代,数组下标(索引)只能是常量

      示例

      var a, b, c, d;
      const list1 = list(a, b, c, d); // list 是编译期容器,它这里只是把 a/b/c/d 的引用装起来,而非 a/b/c/d 的值
      const list2 = list(15, 4, list(99, 47)); // 列表会被拉平,不会创建多维列表,这个等同 list(15, 4, 99, 47)
      foreach(i, v : py_enumerate(list2)) {
      list1[i] = v;
      }
      println("{}, {}, {}, {}", a, b, c, d); // 15, 4, 99, 47

    • EUDCreateVariables

      • EUDCreateVariables(count) : py_list[EUDVariable]
        编译期创建 [count] 个变量,返回一个编译期列表,列表内包含被创建的变量的引用
        编译期列表可以使用 foreach 语法在编译期迭代,数组下标(索引)只能是常量

      示例

      const vs = EUDCreateVariables(3);
      vs[0] = 1;
      vs[1] = 2;
      vs[2] = 3;
      // vs[0]、vs[1]、vs[2] 是三个变量,vs 在运行时不存在,所以 vs 的下标都必须是编译期常量

    • SetVariables

      • SetVariables(变量列表 : py_list, 目标列表 : py_list, 操作符列表 : py_list)
        使用最少一个触发器将 [变量列表] 中所有的变量按照 [操作符列表] 中对应的操作为 [目标列表] 值,该宏用于优化
        传入目标值是变量也不会在动作完成之前动态生效,仅仅会保持目标值为它开始执行时的状态

      示例

      var a, b, c = 10, 10, 10;
      SetVariables(
      list( a, b, c ),
      list( 3, 2, 4 ),
      list(SetTo, Add, Subtract),
      );
      // 以上代码相当于
      const op1 = list(a, SetTo, 3),
      list(b, Add, 2),
      list(c, Subtract, 4);
      SeqCompute(op1);

    • SCMD2Text

      • SCMD2Text(text) : py_str
        编译期将文本 [text] 中的尖括号 16 进制数字值 <XX> 转换成对应的 ASCII 字符

      示例

      // 以下两行效果等价
      simpleprint(SCMD2Text("<03>哈哈<02>")); // 打印出黄色的 哈哈
      simpleprint("\x03哈哈\x02"); // 打印出黄色的 哈哈

    • unProxy

      • unProxy(x) : duck
        编译期获取引用类型 x 的的指针值

      示例

      const a = EUDArray(list(9, 8, 7));
      var b = unProxy(a);
      const c = EUDArray.cast(b + 4); // c 实际是 a[1] 的引用
      c[0] = 888888;
      println("b:{} a:{} c:{} a[1]:{} c[0]:{}", b, a, c, a[1], c[1]); // b:421156492 a:421156492 c:421156496 a[1]:888888 c[1]:7

    • UnitProperty

      • UnitProperty(...) : CUWP
        编译期在地图的中插入一个创建单位属性(Create Unit with Properties)并返回它,可以使用 GetPropertyIndex 获取它的编号,字段解释看示例

      示例

      // 所有字段都可选
      const prop = UnitProperty(
      hitpoint = 100, // 生命百分比 0~100
      shield = 100, // 护盾百分比 0~100
      energy = 100, // 能量百分比 0~100
      hanger = 0, // 0~4294967295
      resource = 0, // 0~65536 (Count)
      cloaked = False, // 是否隐形 True(启用)/False(禁用)
      burrowed = False, // 是否已钻地 True(启用)/False(禁用)
      intransit = False, // 是否正在被运输 True(启用)/False(禁用)
      hallucinated = False, // 是否是个幻影 True(启用)/False(禁用)
      invincible = False // 是否无敌 True(启用)/False(禁用)
      );
      CreateUnitWithProperties(1, "Terran Marine", $L("Location 3"), P1, prop);

    • GetPropertyIndex

      • GetPropertyIndex(创建单位属性 : CUWP) : py_int
        编译期获取一个创建单位属性在地图 CUWP 列表中的编号

      示例

      // 所有字段都可选
      const prop = UnitProperty(
      hitpoint = 100, // 生命百分比 0~100
      );
      py_print(GetPropertyIndex(prop));

    • GetPlayerInfo

      • GetPlayerInfo(player: TrgPlayer) : py_struct
        编译期获取地图信息中玩家 [player] 的信息,[player] 仅支持常量,获取的信息是地图设定的信息,并非运行时信息

      示例

      const pinfo = GetPlayerInfo(0);
      setcurpl(0);
      printAt(9, "玩家 1 类型:{}({}) 种族:{}({}) 所属势力:{}", pinfo.typestr, pinfo.type, pinfo.racestr, pinfo.race, pinfo.force);

    • EUDRegisterObjectToNamespace

      • EUDRegisterObjectToNamespace(funcname, obj) : duck
        注册一个对象到全局的名字空间,主要用于模块间反射传递参数

      示例

      const menuSel = PVariable();
      EUDRegisterObjectToNamespace("menuSel", menuSel);

    • GetEUDNamespace

      • GetEUDNamespace() : py_dict[str, duck]
        获取全局名字空间字典表,它记录着 EUDRegisterObjectToNamespace 里面注册的对象

      示例

      function afterTriggerExec() {
      setcurpl(P1);
      const menuSel2 = GetEUDNamespace().get("menuSel");
      println(9, "{}", menuSel2[0]);
      }

    • MPQAddFile

      • MPQAddFile(fname, contents, isWave = false)
        向 output 设定的输出地图中添加一个字节组内容为 [contents] 的文件 [fname],如果 [isWave] 标志设置为 true,则会在导入前使用 Wave 有损压缩的方式压缩它

      示例

      MPQAddFile("1.txt", py_open("C:/1.txt", "rb").read());

    • MPQAddWave

      • MPQAddWave(fname, content)
        它相当于是 MPQAddFile(fname, contents, true)

      示例

      MPQAddWave("1.wav", py_open("C:/1.wav", "rb").read());


  • 编译期 Python 宏

    在 epScript 中可以使用 py_ 前缀调用所有的 Python 3 内建函数,这里列举一些常用的


    • py_print

      • py_print(*args)

        编译期使用 Python 的 print 函数打印输出内容到编译日志界面,用于调试输出

      示例

      py_print("这个仅仅会输出到编译时的黑框中,跟地图一毛钱关系也没有");

    • py_list

      • py_list(iter) : py_list
        创建并返回一个编译期 Python 列表
        编译期列表可以使用 foreach 语法在编译期迭代,数组下标(索引)只能是常量

      示例

      const lst = py_list();
      lst.append(DisplayText("11111"));
      lst.append(DisplayText("22222"));
      lst.append(DisplayText("33333"));
      DoActions(lst);

    • py_open

      • py_open(filename, mode) : py_file
        编译期以 [mode] 模式打开文件 [filename],返回一个 Python 文件对象

      示例

      function onPluginStart() {
      MPQAddWave("1.wav", py_open("C:/1.wav", "rb").read()); // 导入一个外部文件到地图中
      }

    • py_eval

      • py_eval(str) : duck
        编译期简单的 Python 代码执行,返回结果

      示例

      import py_datetime;

      function is_map_expired() {
      static var expired = false;
      once {
      const expire_date = py_str('2024-01-01 00:00:00');
      const expire_date_stamp = py_eval("int(datetime.datetime.strptime(expire_date, '%Y-%m-%d %H:%M:%S').timestamp())");
      if (Memory(0x6D0F38, AtLeast, expire_date_stamp)) {
      expired = true;
      }
      }
      return expired;
      }

      function onPluginStart() {
      if (is_map_expired()) {
      DisplayTextAll("当前地图已经过期。");
      } else {
      DisplayTextAll("玩得开心。");
      }
      }

    • py_str

      • py_str(val) : py_str
        编译期字符串包装转换

      示例

      const actions = py_list();
      foreach(i : py_range(0, 3)) {
      actions.append(CreateUnit(1, "Terran Marine", py_str("Location ") + py_str(i + 1), P1)); // 往列表中添加一个动作
      }
      DoActions(actions); // 一次性执行列表所有的动作

    • py_len

      • py_len(gconstant) : py_int
        获取编译期 Python 层全局常量长度

      示例

      const lst = py_list();
      lst.append(DisplayText("11111"));
      lst.append(DisplayText("22222"));
      lst.append(DisplayText("33333"));
      foreach(i : py_range(0, py_len(lst))) {
      DoActions(lst[i]);
      }

    • py_enumerate

      • py_enumerate(vlist) : py_iter
        编译期枚举迭代器,枚举展开编译期容器中的项

      示例

      var a, b, c, d;
      const list1 = list(a, b, c, d); // list 是编译期容器,它这里只是把 a/b/c/d 的引用装起来,而非 a/b/c/d 的值
      const list2 = list(15, 4, 99, 47);
      foreach(i, v : py_enumerate(list2)) {
      list1[i] = v;
      }
      println("{}, {}, {}, {}", a, b, c, d); // 15, 4, 99, 47

    • py_range

      • py_range(start, end, step) : py_iter
        编译期计数迭代器,从 [start] 步进为 [step] 迭代到 [end] 结束展开代码块,包含 [start] 不包含 [end]

      示例

      // https://armoha.github.io/eud-book/offsets/MouseCoordinateX.html
      // https://armoha.github.io/eud-book/offsets/MouseCoordinateY.html
      // 读取内存值的原理,使用 32 个触发器判断 32 位中的每一位的值,如果第 i 位的值大于 0 则给变量附加 2 的 i-1 次方的值
      function GetMouseXY() {
      var x, y;
      RawTrigger(actions = list(
      SetDeaths(EPD(x.getValueAddr()), SetTo, 0, 0),
      SetDeaths(EPD(y.getValueAddr()), SetTo, 0, 0),
      ));
      foreach(i : py_range(32)) { /* 这里用 64 个触发器来读取鼠标的 X Y 值 */
      RawTrigger(
      conditions = DeathsX(EPD(0x6CDDC4), AtLeast, 1, 0, py_pow(2,i)),
      actions = SetDeaths(EPD(x.getValueAddr()), Add, py_pow(2,i), 0),
      );
      RawTrigger(
      conditions = DeathsX(EPD(0x6CDDC8), AtLeast, 1, 0, py_pow(2,i)),
      actions = SetDeaths(EPD(y.getValueAddr()), Add, py_pow(2,i), 0),
      );
      }
      return x, y;
      }

      // 上面的 GetMouseXY 函数实际实现等价于下面这个
      function GetMouseXY() {
      return dwread(0x6CDDC4), dwread(0x6CDDC8);
      }


  • 编译期字节转换


    • b2i

      • b2i1(content, index) : py_int
      • b2i1(content, index) : py_int
      • b2i4(content, index) : py_int
        将字面量字节组 [content] 在 [index] 位置的 byte、word、dword 使用小端序(Little Endian)方式转换转换成正整数常量

      示例

      printAt(2, "0x{:x},0x{:x},0x{:x},0x{:x}", b2i1(b"fuck"), b2i1(b"fuck",1), b2i1(b"fuck",2), b2i1(b"fuck",3)); // 0x66, 0x75, 0x63, 0x6B
      printAt(3, "0x{:x},0x{:x},0x{:x}", b2i2(b"fuck"), b2i2(b"fuck",1), b2i2(b"fuck",2)); // 0x7566, 0x6375, 0x6B63
      printAt(4, "0x{:x}", b2i4(b"fuck")); // 0x6B637566

    • i2b

      • i2b1(i) : py_byte
      • i2b2(i) : [py_byte]
      • i2b4(i) : [py_byte]
        将整数常量 [i] 使用小端序(Little Endian)方式转换成一个字节两个字节或者四个字节常量

      示例

      printAt(4, "{}", i2b4(0x6B637566));

    • u2b/b2u

      • u2b(s) : [py_byte]
      • b2u(b) : py_str
        字节字面量与字符串字面量转换

      示例

      printAt(5, "{}", u2b("fuck")); // b'fuck'
      printAt(6, "{}", b2u(b"fuck")); // fuck

    • utf8 编码/解码

      • b2utf8(str) : [py_byte]
        使用 UTF-8 解码 [str]

      • u2utf8(str) : py_str
        使用 UTF-8 编码 [str]

      示例

      printAt(5, "{}", b2utf8("fuck")); // b'fuck'
      printAt(6, "{}", u2utf8(b"fuck")); // fuck


  • 常规函数


    • EPD

      • EPD(ptr) : py_int | EUDVariable
        将指定 [ptr] 指针转换成 EPD 偏移值,内存中玩家编号占用 4 字节(32 位整数),它实际就是将 [ptr] 减去 0x58A364 得到的数再除以 4
        参数是常量的情况下可以在编译期返回结果

      示例

      // https://armoha.github.io/eud-book/offsets/GameSpeedRefreshRate.html
      const epd = EPD(0x5124D8); // (0x5124D8-0x58A364)/4 = -0x1DFA3 = -122787

    • l2v

      • l2v(条件表达式) : EUDVariable
        使用一个触发器在运行时获取 [条件表达式] 的结果逻辑值 false 或者 true

      示例

      var isP1MarineDeaths100 = l2v(Deaths(P1, AtLeast, 100, "Terran Marine"));

    • parse

      • parse(address, radix=10) : py_list[EUDVariable, EUDVariable]
        使用 [radix] 进制方式将内存地址 [address] 上的字符串转换为一个数字
        解析成功返回值为:数字, 位数
        解析失败返回值为:0, 0

      示例

      const numstr = Db("102a\r\r\r\r\r\r\r\r\r\r\r\0");
      var num, digits;
      num, digits = parse(numstr, 8);
      println("0x{:x}, {}, 8进制位数:{}", num, num, digits); // 0x00000042, 66, 8进制位数:3
      num, digits = parse(numstr, 10);
      println("0x{:x}, {}, 10进制位数:{}", num, num, digits); // 0x00000066, 102, 10进制位数:3
      num, digits = parse(numstr, 16);
      println("0x{:x}, {}, 16进制位数:{}", num, num, digits); // 0x0000102A, 4138, 16进制位数:4

    • EUDFuncPtr

      • EUDFuncPtr(argn, retn) : py_int
        声明一个 [argn] 个参数和 [retn] 个返回值的闭包函数的指针

      • EUDTypedFuncPtr(argtypes, rettypes) : py_int
        声明一个参数类型列表为 [argtypes] 返回值类型列表为 [rettypes] 的闭包函数的指针

      示例

      function GetMouseMapXY() {
      const screenX, screenY = dwread_epd(EPD(0x62848C)), dwread_epd(EPD(0x6284A8));
      const mouseX, mouseY = dwread_epd(EPD(0x6CDDC4)), dwread_epd(EPD(0x6CDDC8));
      const x, y = screenX + mouseX, screenY + mouseY;
      return x, y;
      }

      var funcptr = 0;

      function beforeTriggerExec() {
      static var x, y = 0, 0;
      once (funcptr == 0) {
      funcptr = EUDFuncPtr(0, 2)(function() {
      x, y = GetMouseMapXY();
      return x, y;
      });
      }

      setcurpl(P1);
      printAt(9, "before x = {}, y = {}", x, y);
      }

      function afterTriggerExec() {
      const x, y = EUDFuncPtr(0, 2).cast(funcptr)();
      setcurpl(P1);
      printAt(10, "after funcptr returns {}, {}", x, y);
      }

    • getgametick

      • getgametick() : EUDVariable
        获取逝去的游戏帧数,游戏速率为极快(Fastest)的情况下是 42 毫秒每帧

      示例

      var tick = getgametick();


  • 触发器构建函数

    触发器构建函数可以使用自定义构建各种属性的触发器


    • RawTrigger

      • RawTrigger(conditions = list(...), actions = list(...), preserved = true/false) : RawTrigger
        插入一个静态的传统触发器,不能传入变量,返回一个触发器指针
        conditions 字段传入条件表达式常量列表,最多支持 16 个传统触发器条件
        actions 字段传入动作表达式常量列表,最多支持 64 个传统触发器动作
        preserved 字段默认是 true,表示每次调用都会执行,如果设为 false,那么它将会在条件达成后执行一次,往后再也不会执行

      示例

      // https://armoha.github.io/eud-book/offsets/MouseCoordinateX.html
      // https://armoha.github.io/eud-book/offsets/MouseCoordinateY.html
      // 读取内存值的原理,使用 32 个触发器判断 32 位中的每一位的值,如果第 i 位的值大于 0 则给变量附加 2 的 i-1 次方的值
      function GetMouseXY() {
      var x, y;
      RawTrigger(actions = list(
      SetDeaths(EPD(x.getValueAddr()), SetTo, 0, 0),
      SetDeaths(EPD(y.getValueAddr()), SetTo, 0, 0),
      ));
      foreach(i : py_range(32)) { /* 这里用 64 个触发器来读取鼠标的 X Y 值 */
      RawTrigger(
      conditions = DeathsX(EPD(0x6CDDC4), AtLeast, 1, 0, py_pow(2,i)),
      actions = SetDeaths(EPD(x.getValueAddr()), Add, py_pow(2,i), 0),
      );
      RawTrigger(
      conditions = DeathsX(EPD(0x6CDDC8), AtLeast, 1, 0, py_pow(2,i)),
      actions = SetDeaths(EPD(y.getValueAddr()), Add, py_pow(2,i), 0),
      );
      }
      return x, y;
      }

      // 上面的 GetMouseXY 函数实际实现等价于下面这个
      function GetMouseXY() {
      return dwread(0x6CDDC4), dwread(0x6CDDC8);
      }

    • Trigger

      • Trigger(conditions = list(...), actions = list(...), preserved = true/false)
        插入一个扩展触发器,扩展触发器可能会被拆分成很多个传统触发器,它没有 16 个条件和 64 个动作的限制,并且可以传入变量,不返回值
        传入变量不会在动作完成之前动态生效,仅仅会保持变量在触发器开始执行时的状态
        为了代码明确,通常建议不使用 Trigger 而使用 if 条件来完成动态的条件判断
        conditions 字段传入条件表达式列表
        actions 字段传入动作表达式列表
        preserved 字段默认是 true,表示每次调用都会执行,如果设为 false,那么它将会在条件达成后执行一次,往后再也不会执行

      示例

      Trigger(
      conditions = list(
      Bring(P1, AtMost, 0, "Zerg Cerebrate", "Location 1")
      ),
      actions = list(
      KillUnitAt(All, "Terran Medic", "Location 1", P1)
      ),
      preserved = false,
      );

    • PTrigger

      • PTrigger(players, conditions = list(...), actions = list(...))
        插入一个匹配当前玩家的触发器,当前玩家为 [players] 中任何一个玩家时,它就会执行,没有 preserved 字段,其它字段用法和 Trigger 一样。

      示例

      setcurpl(P8);
      PTrigger(list(P3, P8),
      conditions = ElapsedTime(AtLeast, 3),
      actions = KillUnit($U("Terran Medic"), P1),
      );

    • DoActions

      • DoActions(actions, preserved = true/false)
        一个无条件执行动作的 Trigger 函数
        在 epScript 中,两个分号之间非注释代码仅为函数调用会自动使用这个函数包裹成一个触发器
        actions 为动作表达式列表
        preserved 字段默认是 true,表示每次调用都会执行,如果设为 false,那么它将会在条件达成后执行一次,往后再也不会执行

      示例

      Order("Zerg Hydralisk", P8, $L("UpArea"), Patrol, "Player1Home"); // 如果一行代码仅是一个触发器动作函数调用,那么它在编译后会在外面套上一个 DoActions

      DoActions(Order("Zerg Hydralisk", P8, $L("UpArea"), Patrol, "Player1Home"));

      DoActions(list( // 明确一点儿可以加上 list
      Order("Zerg Hydralisk", P8, $L("UpArea"), Patrol, "Player1Home"),
      ));

      Trigger(actions = list(
      Order("Zerg Hydralisk", P8, $L("UpArea"), Patrol, "Player1Home"),
      ));

      // 上面四个用法完全等价

      const a = Order("Zerg Hydralisk", P8, $L("UpArea"), Patrol, "Player1Home"); // 这一行代码与上面用法不等价,它用于声明一个名为 a 的动作表达式常量,它不会被自动用 DoActions 包裹

      DoActions(
      Order("Zerg Guardian", P8, $L("UpArea"), Patrol, "Player1Home"),
      Order("Zerg Guardian", P8, $L("MiddleArea"), Patrol, "Player1Home"),
      Order("Zerg Devourer", P8, $L("UpArea"), Patrol, "Player1Home"),
      Order("Zerg Devourer", P8, $L("MiddleArea"), Patrol, "Player1Home"),
      preserved = false, // preserved 参数必须是命名参数
      );

      // 静态特性演示
      // 传入 DoActions 序列的动作的变量会在 DoActions 开始执行的时候被替换成一个静态常量值传入,不管 DoActions 的运行过程中修改多少次变量,传入的值是在运行之前就已经确定好了的
      var c = 0;
      c.AddNumber(1);
      DoActions(
      c.AddNumber(100),
      CreateUnit(c, "Terran SCV", "Location 2", P1),
      );
      printAt(10, "你觉得应该创建了 {} 个 SCV,但实际只有 1 个", c); // 你觉得这里应该创建了 100 个 SCV,但实际只有 1 个

    • VProc

      • VProc(vars, actions) : RawTrigger | [RawTrigger]
        变量处理过程 [actions] 只能传入常量动作表达式列表,VProc 会在顺序执行完 [actions] 所有动作后,再顺序将 [vars] 中所有变量的虚拟触发器都执行一遍
        这意味着可以先在 [actions] 对部分变量的虚拟触发器进行修改,动作执行完成后再由 VProc 执行这些被修改过的变量的虚拟触发器
        它通常用于优化对变量进行串行赋值或位运算的开销,它的开销略高于 RawTrigger 但小于 DoActions 或 Trigger
        一个变量的虚拟触发器中只能有一次 SetDeathsX 动作,若设定多个动作到变量队列中取最后一个

        变量操作优化

      示例

      var 无关变量 = 0;
      var c, d = 0, 0;

      c = 1;
      RawTrigger(actions = list(
      c.AddNumber(100),
      c.QueueAssignTo(d),
      ));
      println("RawTrigger 过程后 c:{} d:{}", c, d); // c:101 d:0

      c = 1;
      VProc(c, list(
      c.AddNumber(100),
      c.QueueAssignTo(d),
      ));
      println("VProc(c) 过程后 c:{} d:{}", c, d); // c:101 d:101

      c = 1;
      VProc(list(c, d), list(
      c.AddNumber(100),
      c.QueueAssignTo(d),
      d.QueueAddTo(c),
      ));
      println("VProc(c,d) 过程后 c:{} d:{}", c, d); // c:202 d:101

      c = 1;
      VProc(list(c, d), list(
      c.AddNumber(100),
      c.QueueAssignTo(d),
      d.QueueAddTo(c),
      ));
      println("VProc(d,c) 过程后 c:{} d:{}", c, d); // c:202 d:202


  • 运行时迭代器

    运行时迭代器可以使用编译期循环 foreach 语法构建一个运行时循环代码块,运行期会执行的次数由迭代器内部的流程控制逻辑控制
    运行时迭代器所属的 foreach 代码块中可以使用 continue 或者 break,它们会编译为 EUDContinue() 和 EUDBreak()
    咱们使用等价代码的方式来解释一下运行时迭代器的原理,例如有如下代码

    foreach (cu : EUDLoopNewCUnit()) {
    py_print(cu);
    println("{}", cu);
    continue;
    }

    它大约等价于

    {
    const it = EUDLoopNewCUnit();
    const cu = py_next(it); // 首次编译期迭代获取迭代结果存储的位置并设定 EUD 运行时循环代码块开始,相当于一个 EUDWhile()(运行时it还有下一个项)
    py_print(cu); // 编译期输出,只会执行一次
    println("{}", cu);
    EUDContinue();
    py_next(it, 0); // 第二次编译期迭代设置 EUD 运行循环代码块结束位置,相当于一个 EUDEndWhile()
    }

    • EUDLoopPlayer

      • EUDLoopPlayer(ptype, force, race) : EUDIterator
        迭代类型为 [ptype] 势力为 [force] 种族为 [race] 的活跃玩家,内部使用 playerexist 检测
        活跃玩家不包括空缺或离开游戏的玩家

      示例

      // ptype 是可选参数,可以是 "Human" 或 "Computer"
      // force 是可选参数,可以是 Force1 Force2 Force3 Force4
      // race 是可选参数,可以是 "Zerg" "Terran" "Protoss"
      foreach (p: EUDLoopPlayer("Human", Force1, "Zerg")) {
      setcurpl(p);
      println("你是虫族");
      }

    • EUDLoopRange

      • EUDLoopRange(start, end=None) : EUDIterator
        迭代从 [start] 到 [end] - 1 的值

      示例

      // 打印 1 到 4 屏幕
      foreach (i : EUDLoopRange(1, 5)) {
      simpleprint(i);
      }
      // 以上代码大约等价于
      for (var i = 1; i < 5; i++) {
      simpleprint(i);
      }

    • EUDLoopUnit

      • EUDLoopUnit() : EUDIterator
        迭代单位主链上的所有单位的 ptr, epd
        不包含子单位、Scanner Sweep(雷达特效单位)、Map Revealer(地图雷达)等

      示例

      foreach (ptr, epd : EUDLoopUnit()) {
      const u = CUnit(epd);
      if (u.unitID == $U("Terran Marine")) {
      u.hp = 0x100 * 10;
      }
      }

      • EUDLoopUnit2() : EUDIterator
        迭代所有单位的 ptr, epd
        迭代的单位会包含子单位、Scanner Sweep(雷达特效单位)、Map Revealer(地图雷达)等

      示例

      // 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;
      }
      }

      • EUDLoopCUnit() : EUDIterator
        它使用 EUDLoopUnit2 遍历并将遍历出来的指针包装成 CUnit 对象
        迭代的单位会包含子单位、Scanner Sweep(雷达特效单位)、Map Revealer(地图雷达)等

      示例

      foreach (u : EUDLoopCUnit()) {
      if (u.unitID == $U("Terran Marine")) {
      u.hp = 0x100 * 10;
      }
      }

      • EUDLoopNewUnit(allowance = 2) : EUDIterator
        迭代最多 [allowance] 个从非本帧最后一次调用 EUDLoopNewUnit 或者 EUDLoopNewCUnit 以来新出现的单位的 ptr, epd
        不包含子单位、Scanner Sweep(雷达特效单位)、Map Revealer(地图雷达)等

      • EUDLoopNewCUnit(allowance = 2) : EUDIterator
        迭代最多 [allowance] 个从非本帧最后一次调用 EUDLoopNewUnit 或者 EUDLoopNewCUnit 以来新出现的单位并将遍历出来的指针包装成 CUnit 对象
        不包含子单位、Scanner Sweep(雷达特效单位)、Map Revealer(地图雷达)等

      Warning
      同一帧多次调用 EUDLoopNewUnit 或者 EUDLoopNewCUnit 将会遍历到相同的单位
      虫族的特殊性:新单位只有幼虫、取消建造气矿的农民、双生单位的第二个单位(狗、自爆蚊、第二个传送门),以及任何使用 CreateUnit 函数凭空创造的单位

      Note
      虫卵孵化的过程会使用幼虫的地址,孵化完成后的单位依然使用这个地址,如果是孵化小狗其中一只小狗继续使用幼虫的地址,另外一只是新单位,自爆蚊类似
      虫族农民孵化出来的建筑会使用农民的地址,取消孵化会变回农民,地址不变,不产生新单位
      农民孵化成气矿的一瞬间会继承气矿单位(气矿本身就是一个单位)的地址,农民算作死亡,如果取消建造,返回的是一个新的农民,这个农民可以被遍历到
      在出兵建筑里排队造的兵种:建造完毕走出来的时候才被检测到
      建筑:刚开始建造就可以检测出来(未完成建造)
      红白球:电兵合体之后白球将使用其中一个电兵的地址,另外一个电兵算作死亡,不会产生新单位,红球类似

      示例

      foreach (ptr, epd : EUDLoopNewUnit(1700)) {
      const u = CUnit(epd);
      if (u.unitID == $U("Terran Marine")) {
      u.hp = 0x100 * 10;
      }
      }

      foreach (u : EUDLoopNewCUnit(1700)) {
      if (u.unitID == $U("Terran Marine")) {
      u.hp = 0x100 * 10;
      }
      }

      // 下面代码是完整的新单位遍历,代码来自:GGRush
      const NewUnits = UnitGroup(1000);
      const ChangeableUnits = EUDDeque(1000)();

      function onPluginStart() {
      GetGlobalStringBuffer();
      }

      function beforeTriggerExec() {
      foreach(cunit: EUDLoopNewCUnit()) {
      NewUnits.add(cunit);
      }

      ChangeableUnits.append(-1);
      var uid, value;
      while(True) {
      value = ChangeableUnits.popleft();
      if(value == -1) break;
      else if(value < 228) uid = value;
      else if(value >= EPD(0x59CCA8)) {
      const epd = value;
      if(!MemoryXEPD(epd + 0x64/4, Exactly, prev, 0xFFFF)) NewUnits.add(value);
      else {
      ChangeableUnits.append(uid);
      ChangeableUnits.append(epd);
      }
      }
      }

      foreach(unit: NewUnits.cploop) {
      foreach(dead: unit.dying) {} //存在检测:存活的继续执行代码,死亡的continue
      unit.move_cp(0x64/4);
      if(DeathsX(CurrentPlayer, Exactly, $U("Zerg Larva"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Egg"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Drone"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Hydralisk"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Lurker Egg"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Mutalisk"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Mutalisk Cocoon"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Hatchery"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Lair"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Creep Colony"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Zerg Spire"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Protoss High Templar"), 0, 0xFFFF)
      || DeathsX(CurrentPlayer, Exactly, $U("Protoss Dark Templar (Unit)"), 0, 0xFFFF)
      ) {
      const uid = wread_cp(0, 0);
      ChangeableUnits.append(uid);
      ChangeableUnits.append(unit.epd);
      }
      // start
      // 你的代码
      // end
      unit.remove(); //This code is necessary!!!Don't skip it
      }
      }

      • EUDLoopPlayerUnit(player: TrgPlayer) : EUDIterator
        迭代玩家 [player] 所有的单位的 ptr, epd

      • EUDLoopPlayerCUnit(player: TrgPlayer) : EUDIterator
        迭代玩家 [player] 所有的单位并将遍历出来的指针包装成 CUnit 对象

      示例

      foreach (ptr, epd : EUDLoopPlayerUnit(P1)) {
      const u = CUnit(epd);
      if (u.unitID == $U("Terran Marine")) {
      u.hp = 0x100 * 10;
      }
      }

      foreach (u : EUDLoopPlayerCUnit(P1)) {
      if (u.unitID == $U("Terran Marine")) {
      u.hp = 0x100 * 10;
      }
      }

      // 将 玩家 Owner 的机枪兵全改成 玩家 NewOwner 的
      // 使用 cunit.give 会影响 EUDLoopPlayerCUnit 迭代,所以要先将所有 cunit 加入到一个队列容器中
      // 然后再从容器中对每一个 cunit 进行更改所有者
      const givequeue = EUDQueue(100);
      foreach(cunit: EUDLoopPlayerCUnit(Owner)) {
      if(cunit.unitType != $U("Terran Marine")) continue;
      givequeue.append(cunit);
      }
      while (!givequeue.empty()) {
      const cunit = CUnit(givequeue.pop());
      cunit.cgive(NewOwner);
      }


  • 屏幕输出文字函数


    • DisplayTextAt

      • DisplayTextAt(行, 文本 : TrgString)
        本机玩家 == 当前玩家屏幕上第 [行] 行显示 [文本]
        它和 DisplayText 不同,不返回触发器动作表达式

      • DisplayTextAllAt(行, 文本 : TrgString)
        为所有的玩家(包括观察者)在第 [行] 行显示 [文本]
        它和 DisplayTextAll 不同,不返回触发器动作表达式

      示例

      var text_10 = $T("_10");
      var text_09 = $T("_09");
      var line = 10;
      DisplayTextAllAt(line, text_10);
      line -= 1;
      DisplayTextAllAt(line, text_09);
      line -= 1;
      setcurpl(P1);
      DisplayTextAt(line, "只显示给 P1");

    • print

      • simpleprint(*args, spaced=true)
        将多个参数 [*args] 按顺序打印到本机玩家 == 当前玩家屏幕滚动信息的下一行,命名参数 spaced 表示是否将打印出来的每个参数用空格隔开,默认 true

      • println(format_string : py_str, *args)
        以格式 [format_string] 格式化打印多个参数 [*args] 到本机玩家 == 当前玩家屏幕滚动信息的下一行

      • printAt(line, format_string : py_str, *args)
        以格式 [format_string] 格式化打印多个参数 [*args] 到本机玩家 == 当前玩家屏幕滚动信息的从上往下的第 [line] 行(取值范围 0~10)

      • printAll(format_string : py_str, *args)
        以格式 [format_string] 格式化打印多个参数 [*args] 到所有玩家屏幕滚动信息的下一行

      • printAllAt(line, format_string : py_str, *args)
        以格式 [format_string] 格式化打印多个参数 [*args] 到所有玩家屏幕滚动信息的从上往下的第 [line] 行(取值范围 0~10)

      格式化占位符
      • {}:通用占位符,用于输出一个变量的值或常量的指针
      • {{}}:输出{}本身
      • {:c}:将位置上的值作为玩家编号并输出为对应的玩家颜色代码,就像 PColor(位置上的值) 那样
      • {:n}:将位置上的值作为玩家编号并输出为对应的玩家名字,就像 PName(位置上的值) 那样
      • {:s}:将位置上的值作为字符串指针输出为其指向的字符串,就像 ptr2s(位置上的值) 那样
      • {:t}:将位置上的值作为字符串 EPD 指针输出为其指向的字符串,就像 epd2s(位置上的值) 那样
      • {:x}:将位置上的数值输出为左补 0 的 8 位 16 进制数字,就像 hptr(将位置上的数值) 那样

      示例

      // simpleprint(*args, spaced=true)
      simpleprint("你好", "星际争霸"); // 在当前玩家屏幕滚动信息下一行输出 “你好 星际争霸”
      simpleprint("你好", "星际争霸", spaced = false); // 在当前玩家屏幕滚动信息下一行输出 “你好星际争霸”

      // println(format_string, *args)
      println("{} {}", "你好", "星际争霸"); // 在当前玩家屏幕滚动信息下一行输出 “你好 星际争霸”

      // printAt(line, format_string, *args)
      printAt( 0, "{} {}", "你好", "星际争霸"); // 在当前玩家屏幕滚动信息最上面一行输出 “你好 星际争霸”
      printAt(10, "{} {}", "你好", "星际争霸"); // 在当前玩家屏幕滚动信息最下面一行输出 “你好 星际争霸”

      // printAll(format_string, *args)
      printAll("{} {}", "你们好", "星际争霸"); // 在所有玩家的屏幕滚动信息下一行输出 “你们好 星际争霸”

      // printAllAt(line, format_string, *args)
      printAllAt( 0, "{} {}", "你们好", "星际争霸"); // 在所有玩家的屏幕最上面一行输出 “你们好 星际争霸”
      printAllAt(10, "{} {}", "你们好", "星际争霸"); // 在所有玩家的屏幕最下面一行输出 “你们好 星际争霸”

    • GetGlobalStringBuffer

      • GetGlobalStringBuffer() : StringBuffer
        获取本机玩家 == 当前玩家 print 系列函数中内部使用的 StringBuffer,它的容量为 1023 字节

      示例

      // 以下两行代码等价
      printAt( 0, "{} {}", "你好", "星际争霸");
      GetGlobalStringBuffer().printfAt(0, "{} {}", "你好", "星际争霸");

    • eprint

      • eprintln(*args)
        将多个参数 [*args] 按顺序打印到当前玩家屏幕中心偏下的错误提示信息,打印超过 218 个字节的内容(包含颜色代码字符串结尾等)会发生错误

      • eprintf(format_string, *args)
        以字面字符串 [format_string] 作为格式格式化打印多个参数 [*args] 到当前玩家屏幕中心偏下的错误提示信息,打印超过 218 个字节的内容(包含颜色代码字符串结尾等)会发生错误

      • eprintAll(format_string, *args)
        以字面字符串 [format_string] 作为格式格式化打印多个参数 [*args] 到所有玩家屏幕中心偏下的错误提示信息,打印超过 218 个字节的内容(包含颜色代码字符串结尾等)会发生错误

      • eprintln2(*args)
        将多个参数 [*args] 按顺序打印到当前玩家屏幕中心偏下的错误提示信息
        替换 stat_txt.tbl[871]: "Unit's waypoint list is full." 这个错误提示的信息,再自行配合 QueueGameCommand_QueuedRightClick(xy) 可在错误信息提示那里输出超过 218 字节内容

      格式化占位符
      • {}:通用占位符,用于输出一个变量的值或常量的指针
      • {{}}:输出{}本身
      • {:c}:将位置上的值作为玩家编号并输出为对应的玩家颜色代码,就像 PColor(位置上的值) 那样
      • {:n}:将位置上的值作为玩家编号并输出为对应的玩家名字,就像 PName(位置上的值) 那样
      • {:s}:将位置上的值作为字符串指针输出为其指向的字符串,就像 ptr2s(位置上的值) 那样
      • {:t}:将位置上的值作为字符串 EPD 指针输出为其指向的字符串,就像 epd2s(位置上的值) 那样
      • {:x}:将位置上的数值输出为左补 0 的 8 位 16 进制数字,就像 hptr(将位置上的数值) 那样

      示例

      eprintln("你好", "星际争霸");             // 输出 “你好星际争霸” 到当前玩家屏幕中心偏下的错误提示信息
      eprintf("{}-{}", "你好", "星际争霸"); // 输出 “你好-星际争霸” 到当前玩家屏幕中心偏下的错误提示信息
      eprintAll("{}-{}", "你们好", "星际争霸"); // 输出 “你们好-星际争霸” 到所有玩家的屏幕中心偏下的错误提示信息

    • TextFX

      • TextFX_FadeIn(*args, color=None, wait=1, reset=True, tag=None, encoding="UTF-8")
        渐显特效文字

      • TextFX_FadeOut(*args, color=None, wait=1, reset=True, tag=None, encoding="UTF-8")
        渐隐特效文字

      • TextFX_Remove(标签)
        移除本机标签为 [标签] 的特效文字

      • TextFX_SetTimer(标签, 设为/增加/减少 : TrgModifier, 数值)
        将本机标签为 [标签] 的特效文字的计时器 [设为/增加/减少] [数值]



  • 玩家相关函数


    • getuserplayerid

      • getuserplayerid() : EUDVariable
        获取本机玩家编号,本机玩家不是当前玩家,每个玩家电脑上获取到的本机玩家是不同的,它返回非同步数据

      示例

      setcurpl(getuserplayerid());
      println("当前玩家的编号:{}", getuserplayerid());

    • playerexist

      • playerexist(player) : EUDVariable
        检测玩家 [player] 是否仍在游戏中,电脑玩家也是玩家

      示例

      if (playerexist(P1)) {
      // 玩家1 存在
      }

    • 当前玩家

      • getcurpl() : EUDVariable
        获取 cpcache 的值,如果 cpcache 没有值或 cpcache 的值和当前玩家不一样,则将当前玩家的值缓存到 cpcache 并返回

      • setcurpl(cp)
        设置当前玩家的值为 [cp],并缓存到 cpcache

      • addcurpl(n)
        当前玩家的值自增 [n],并缓存到 cpcache

      • setcurpl2cpcache()
        当前玩家的值恢复到 cpcache 缓存的值

      可以把当前玩家看作是一个全局变量,在需要指定玩家编号的触发器条件或动作中指定玩家编号为 CurrentPlayer(13)会使用它,并且一些触发器动作内部会使用它,当前玩家甚至可能不是指一个玩家,可能存储任何数值

      只在 当前玩家 == 本机玩家 的机器上生效的动作(允许非同步使用,可单独在部分玩家机器上使用)
      • DisplayText
      • CenterView
      • PlayWAV
      • MinimapPing
      • TalkingPortrait
      • Transmission
      • SetMissionObjectives
      会使用 当前玩家 作为参数的动作(必须同步在所有玩家机器上使用,否则掉线)
      • SetAllianceStatus
      • RunAIScript
      • RunAIScriptAt
      • Draw
      • Defeat
      • Victory

      示例

      const cp = getcurpl();
      setcurpl(P1);
      println("你好 玩家 1");
      setcurpl(cp);

      // CurrentPlayer 是常量数字 13,它可以使一些与玩家相关的条件或动作去访问 当前玩家 的值
      // CurrentPlayer != getcurpl()

      // https://armoha.github.io/eud-book/offsets/GameSpeedRefreshRate.html
      setcurpl(-122787); // 第 1 档游戏速度 PlayerID 偏移
      addcurpl(6); // 第 7 挡游戏速度
      SetDeaths(CurrentPlayer, SetTo, 21, 0); // 游戏速度 x2

      // https://armoha.github.io/eud-book/offsets/TriggerCurrentPlayerakaCPTrick.html
      // https://armoha.github.io/eud-book/offsets/GameBrightness.html
      SetMemory(0x6509B0, SetTo, 210382); // 将 当前玩家 改为 210382 (游戏亮度等级 0~31)
      SetDeaths(CurrentPlayer, SetTo, 15, 0); // 设置亮度为 15
      setcurpl2cpcache(); // 将 当前玩家 恢复到 cpcache,防止干扰 getcurpl 等函数的使用

    • PColor

      • PColor(player: TrgPlayer) : Db*
        返回玩家 [player] 在游戏中的颜色代码,在格式化文本中使用 {:c} 占位符等效

    • PName

      • PName(player: TrgPlayer) : Db*
        返回玩家 [player] 在游戏中的名字,在格式化文本中使用 {:n} 占位符等效

      示例

      println("玩家1: {}{}", PColor(P1), PName(P1)); // 如果 玩家1 叫 Soze,颜色是红色,那么这个输出就是会打印出红色的 Soze
      println("玩家1: {:c}{:n}", P1, P1); // 与上面那条等效

    • SetPName

      • SetPName(player : TrgPlayer, *args)
        设置玩家 [player] 的名字为多个参数 [*args] 组合起来的文本

      • SetPNamef(player: TrgPlayer, format_string, *args)
        设置玩家 [player] 的名字为多个参数 [*args] 使用 [format_string] 格式化输出的文本

      这俩函数均不能影响 PName 函数获取到的那个名字,只影响玩家聊天显示的那个名字,并且只对当前帧有效,每一帧都需要重复运行

      示例

      // 以下两个用法等效
      SetPName(cp, epd2s(title), " \x07等级: \x04", level, " ", PColor(cp), PName(cp));
      SetPNamef(cp, "{:t} \x07等级: \x04{} {:c}{:n}", title, level, cp, cp);

    • EUDPlayerLoop

      • EUDPlayerLoop()()
      • EUDEndPlayerLoop()
        这俩是一对儿,它会把当前玩家依次设为每一个活动玩家(包括电脑玩家),运行完毕后当前玩家值就是 Loop 开始前的当前玩家值

      示例

      // 给所有玩家都加 1000 水晶,也包括电脑玩家
      EUDPlayerLoop()();
      SetResources(CurrentPlayer, Add, 1000, Ore);
      EUDEndPlayerLoop();


  • 区域位置相关函数


    • setloc

      • setloc(loc : TrgLocation, x, y)
        设置区域 [loc] 左上右下坐标分别为 [x], [y], [x], [y] (也就是将区域设置为一个点)

      • setloc(loc : TrgLocation, left, top, right, bottom)
        设置区域 [loc] 左上右下坐标分别为 [left], [top], [right], [bottom]

      示例

      setloc($L("Location 1"), 1234, 2345);
      setloc($L("Location 1"), 1234, 1234, 2345, 2345);

    • addloc

      • addloc(loc : TrgLocation, x, y)
        设置区域 [loc] 的左右坐标加 [x] 上下坐标加 [y] (实际大小不变,中心平移到另外一个位置)

      • addloc(loc : TrgLocation, left, top, right, bottom)
        设置区域 [loc] 的左上右下分别加上 [left], [top], [right], [bottom]

      示例

      addloc($L("Location 1"), 123, 234);
      addloc($L("Location 1"), 123, 234, 345, 456);
      // 配合 lengthdir 的用法
      addloc($L("Location 1"), lengthdir_256(888, 73)); // 将 Location 1 区域往 73 度(256 度制)方向平移 888 个坐标的距离
      addloc($L("Location 1"), lengthdir(888, 102)); // 将 Location 1 区域往 102 度方向平移 888 个坐标的距离

    • dilateloc

      • dilateloc(loc : TrgLocation, x, y)
        设置区域 [loc] 的左上右下分别加上 -[x], -[y], [x], [y] (中心不变,区域扩张)

      • dilateloc(loc : TrgLocation, left, top, right, bottom)
        设置区域 [loc] 的左上右下分别加上 -[left], -[top], [right], [bottom]

      示例

      dilateloc($L("Location 1"), 5, 5);
      dilateloc($L("Location 1"), 1, 2, 3, 4);

    • getlocTL

      • getlocTL(loc : TrgLocation) : py_tuple[EUDVariable, EUDVariable]
        获取一个区域的左上坐标

      示例

      const left, top = getlocTL($L("Location 1"));

    • setloc_epd

      • setloc_epd(loc : TrgLocation, epd)
        设置区域 [loc] 的坐标为本机内存地址 0x58A364 + [epd] * 4 上存储的值

      示例

      // 它的内部实现大概与下面这个函数等效
      function setloc_epd(loc : TrgLocation, epd) {
      const x, y = posread_epd(epd);
      setloc(loc, x, y);
      }


  • 内存读写相关函数


    • dwbreak

      • dwbreak(number) : py_tuple[EUDVariable, EUDVariable, EUDVariable, EUDVariable, EUDVariable, EUDVariable]
        将一个 dword 值 [number] 切分成 word 及 byte 形式

      示例

      const w1, w2, b1, b2, b3, b4 = dwbreak(1234 + 0x10000 * 5678)[[0,1,2,3,4,5]];
      println("w1:{} w2:{} b1:{} b2:{} b3:{} b4:{}", w1, w2, b1, b2, b3, b4);

    • read/write

      • dwread(ptr) : EUDVariable

      • wread(ptr) : EUDVariable

      • bread(ptr) : EUDVariable

        读取本机指定内存地址 [ptr] 上的 dword/word/byte 值

      • dwwrite(ptr, dw)

      • wwrite(ptr, w)

      • bwrite(ptr, b)

        将 dword/word/byte 值写入到本机内存地址 [ptr] 上

      示例

      // 0x582144 参考 https://armoha.github.io/eud-book/offsets/ZergControlAvailable.html
      const SUP_RACE_ZERG = 0;
      const SUP_RACE_TERRAN = 1;
      const SUP_RACE_PROTOSS = 2;
      const SUP_TYPE_AVAILABLE = 0;
      const SUP_TYPE_USED = 1;
      const SUP_TYPE_MAX = 2;

      function SetPlayerSupply(player: TrgPlayer, race, type, amount) {
      dwwrite(0x582144 + (race) * 36 * 4 + (type) * 12 * 4 + (player) * 4, amount);
      }
      function GetPlayerSupply(player: TrgPlayer, race, type) {
      return dwread(0x582144 + (race) * 36 * 4 + (type) * 12 * 4 + (player) * 4);
      }

      SetPlayerSupply(P1, SUP_RACE_ZERG, SUP_TYPE_MAX, 800); // 将 玩家1 的虫族人口最大值设置为 400

    • read_epd/write_epd

      • dwread_epd(epd) : EUDVariable
        读取本机内存地址 0x58A364 + [epd] * 4 上的 dword 值

      • dwwrite_epd(epd, value)
        将 dword 值写入到本机内存地址 0x58A364 + [epd] * 4

      • wread_epd(epd, subp) : EUDVariable

      • bread_epd(epd, subp) : EUDVariable

        读取本机内存地址 0x58A364 + [epd] * 4 + [subp] 上的 word/byte 值,[subp] < 4

      • wwrite_epd(epd, subp, value)

      • bwrite_epd(epd, subp, value)

        将 dword 值写入到本机内存地址 0x58A364 + [epd] * 4 + [subp] 上,[subp] < 4

      • maskread_epd(epd, mask) : EUDVariable
        使用 [mask] 做掩码读取本机内存地址 0x58A364 + [epd] * 4 上的 dword 值

      Example

      // 与 read/write 的例子很相似,不过 _epd 系列函数基准的内存偏移不同,使用 EPD 函数可以将内存地址转换为 epd 偏移值  
      function SetPlayerSupply(player: TrgPlayer, race, type, amount) {
      dwwrite_epd(EPD(0x582144) + (race) * 36 + (type) * 12 + (player), amount);
      }
      function GetPlayerSupply(player: TrgPlayer, race, type) {
      return dwread_epd(EPD(0x582144) + (race) * 36 + (type) * 12 + (player));
      }
      const oe, os = div(EncodeWeapon("C-10 Concussion Rifle"), 4);
      println("bread_epd 鬼兵的武器间隔 {}", bread_epd(EPD(0x656FB8) + oe, os));
      println("maskread_epd 鬼兵的武器间隔 {}", bitrshift(maskread_epd(EPD(0x656FB8) + 1 + oe, 0xFF000000), 24));
      bwrite_epd(EPD(0x656FB8) + oe, os, 1); // 修改鬼兵的武器攻击间隔为 1

    • add_epd/subtract_epd

      • dwadd_epd(epd, value)
        使本机内存地址 0x58A364 + [epd] * 4 上的 dword 值自增 [value]

      • dwsubtract_epd(epd, value)
        使本机内存地址 0x58A364 + [epd] * 4 上的 dword 值自减 [value]

      • wadd_epd(epd, subp, value)

      • badd_epd(epd, subp, value)

        使本机内存地址 0x58A364 + [epd] * 4 + [subp] 上的 word/byte 值自增 [value],[subp] < 4

      • wsubtract_epd(epd, subp, value)

      • bsubtract_epd(epd, subp, value)

        使本机内存地址 0x58A364 + [epd] * 4 + [subp] 上的 word/byte 值自减 [value],[subp] < 4


    • repmovsd_epd

      • repmovsd_epd(dstepdp, srcepdp, copydwn)
        从本机内存地址 0x58A364 + [srcepdp] * 4 拷贝 [copydwn] * 4 字节内容到内存地址 0x58A364 + [dstepdp] * 4

      示例

      const src = Db(b"___1___2___3___4___5");
      const dst = Db(20);
      repmovsd_epd(EPD(src), EPD(dst), 5);
      // dst 将会是 Db(b"___1___2___3___4___5")

    • dwepdread_epd

      • dwepdread_epd(epd) : py_tuple[EUDVariable, EUDVariable]
        从本机内存地址 0x58A364 + [epd] * 4 上读取一个指针,它同时返回指针和该指针的 EPD 值

      • epdread_epd(epd) : EUDVariable
        从本机内存地址 0x58A364 + [epd] * 4 上读取一个指针,它返回该指针的 EPD 值

      示例

      // 创建一个机枪兵并获取它的指针和 EPD 值
      CreateUnit(1, "Terran Marine", $L("Location 1"), P1);
      const lastUnitEPD = EPD(0x628438);
      const ptr1, epd1 = dwepdread_epd(lastUnitEPD);
      var epd2 = epdread_epd(lastUnitEPD);

    • cunitread_epd

      • cunitread_epd(epd) : EUDVariable
        从本机内存地址 0x58A364 + [epd] * 4 上读取一个 cunit 指针,该函数为读取 cunit 指针优化过,它返回一个指针

      • cunitepdread_epd(epd) : py_tuple[EUDVariable, EUDVariable]
        从本机内存地址 0x58A364 + [epd] * 4 上读取一个 cunit 指针,该函数为读取 cunit 指针优化过,它返回该指针和该指针的 EPD 值

      示例

      // 创建一个机枪兵并获取它的指针和 EPD 值
      CreateUnit(1, "Terran Marine", $L("Location 1"), P1);
      const lastUnitEPD = EPD(0x628438);
      const ptr1, epd1 = cunitepdread_epd(lastUnitEPD);
      var ptr2 = cunitread_epd(lastUnitEPD);

    • posread_epd

      • posread_epd(epd) : py_tuple[EUDVariable, EUDVariable]
        从本机内存地址 0x58A364 + [epd] * 4 上读取一个 pos(位置)

      示例

      const screenTilePosEPD = EPD(0x57F1D0);
      const x, y = posread_epd(screenTilePosEPD);
      println("当前屏幕在地图上的坐标: ({}, {})", x, y);

    • _cp 系列

      • dwread_cp(cpoffset) : EUDVariable
      • dwwrite_cp(cpoffset, value)
      • dwadd_cp(cpoffset, value)
      • dwsubtract_cp(cpoffset, value)
      • wread_cp(cpoffset, subp) : EUDVariable
      • bread_cp(cpoffset, subp) : EUDVariable
      • wwrite_cp(cpoffset, subp, w)
      • bwrite_cp(cpoffset, subp, b)
      • maskread_cp(cpoffset, mask) : EUDVariable
      • maskwrite_cp(cpoffset, mask, value)
      • dwepdread_cp(cpoffset) : py_tuple[EUDVariable, EUDVariable]
      • epdread_cp(cpoffset) : EUDVariable
      • cunitread_cp(cpoffset) : EUDVariable
      • cunitepdread_cp(cpoffset) : py_tuple[EUDVariable, EUDVariable]
      • posread_cp(cpoffset) : py_tuple[EUDVariable, EUDVariable]

      _cp 系列所有函数用法都可以参考 _epd 系列,_cp 系列会把 当前玩家 + [cpoffset] 当作 epd
      它们通常用于提升代码运行效率,减少最终生成的触发器数量

      示例

      // 以下代码仅仅为了演示 _cp 用法,并不是提升效率的做法,要提升效率可能需要自己思考
      const screenTilePosEPD = EPD(0x57F1D0);

      setcurpl(screenTilePosEPD); // 把 当前玩家 设置为 screenTilePosEPD
      const x, y = posread_cp(0); // 读取相对 当前玩家 位置偏移 0 字节的值,实际就是读取 screenTilePosEPD 位置上的值

      setcurpl(P1); // 把 当前玩家 设置为 P1 以便输出信息给 玩家1
      println("当前屏幕在地图上的坐标: ({}, {})", x, y);

    • readgen

      • readgen_epd(mask, args) : duck

      • readgen_cp(mask, args) : duck

        可用于创建自定义规则本机内存读取函数

      示例

      // 256 grids = 8192 pixels = x and y are within 0 ~ 8191 (0x1FFF)
      // 编译期函数只能用 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);

    • memcpy

      • memcpy(dst, src, copylen)
        从本机内存地址 [src] 拷贝 [copylen] 字节内容到内存地址 [dst]

    • memcmp

      • memcmp(buf1, buf2, count) : EUDVariable
        对比本机 [buf1] 和 [buf2] 两个内存块 [count] 字节内容
        如果两块内存完全一致,则返回 0
        否则,将对比第一个不同的字节并返回大于或者小于 0 的结果

    • strcpy

      • strcpy(dst, src)
        从本机内存地址 [src] 拷贝字符串(以 \x00 终止的字节块内容)到内存地址 [dst]

    • strcmp

      • strcmp(s1, s2) : EUDVariable
        对比本机 [s1] 和 [s2] 两个内存块上的字符串(以 \x00 终止的字节块内容)
        如果两块内存完全一致,则返回 0
        否则,将对比第一个不同的字节并返回大于或者小于 0 的结果

    • strlen

      • strlen(ptr) : EUDVariable
        获取本机内存地址 [ptr] 上的字符串(以 \x00 终止的字节块内容)的 ASCII 字符个数

      • strlen_epd(epd) : EUDVariable
        获取本机内存地址 0x58A364 + [epd] * 4 上的字符串(以 \x00 终止的字节块内容)的 ASCII 字符个数


    • strnstr

      • strnstr(ptr, substr, count) : EUDVariable
        在本机内存地址 [ptr] 上的字符串的前 [count] 个 ASCII 字符中搜索另外一个字符串 [substr]
        找到了就返回找到的位置的指针,没找到返回 0

    • dbstr

      • dbstr_addstr(dst, src) : EUDVariable
        将本机字符串 [src] 拷贝到内存地址 [dst] 上,返回地址 [dst] + strlen([src])

      • dbstr_addstr_epd(dst, srcepd) : EUDVariable
        将本机内存地址 0x58A364 + [srcepd] * 4 上的字符串拷贝到内存地址 [dst] 上,返回地址 [dst] + strlen_epd([srcepd])

      • dbstr_adddw(dst, number) : EUDVariable
        将一个数字值转换成文本输出到本机内存地址 [dst] 上,返回地址 [dst] + strlen(itoa([number]))

      • dbstr_addptr(dst, ptr) : EUDVariable
        将一个数字值转换成 16 进制数字文本输出到本机内存地址 [dst] 上,返回地址 [dst] + strlen(itox([number]))

      • dbstr_print(dst, *args, EOS=true, encoding="UTF-8")
        将多个参数 [*args] 组合成字符串输出到本机内存地址 [dst] 上
        命名参数 [EOS] 指定是否在字符串结尾附加字符串结尾符号,默认 true
        命名参数 [encoding] 指定编码,默认 UTF-8

      • sprintf(dst, format_string : py_str, *args, EOS=true, encoding="UTF-8")
        以 [format_string] 作为格式将多个参数 [*args] 格式化后输出到本机内存地址 [dst] 上
        命名参数 [EOS] 指定是否在字符串结尾附加字符串结尾符号,默认 true
        命名参数 [encoding] 指定编码,默认 UTF-8

      示例

      const s = Db(100);
      var addr = unProxy(s);
      addr = dbstr_addstr(addr, Db("0123"));
      addr = dbstr_adddw(addr, 4567);
      addr = dbstr_addptr(addr, 0x89ABCDEF);
      simpleprint(s); // 0123456789ABCDEF

      dbstr_print(s, "0123", 4567, hptr(0x89ABCDEF));
      simpleprint(s); // 0123456789ABCDEF

      sprintf(s, "{}{}{:x}", "0123", 4567, 0x89ABCDEF);
      simpleprint(s); // 0123456789ABCDEF

    • ptr2s/epd2s

      • ptr2s(ptr) : Db*
        读取本机内存地址 [ptr] 上的字符串,在格式化文本中使用 {:s} 占位符等效

      • epd2s(epd) : Db*
        读取本机内存地址 0x58A364 + [srcepd] * 4 上的字符串,在格式化文本中使用 {:t} 占位符等效


    • hptr

      • hptr(value) : Db*
        将 [value] 转成 16 进制形式输出,在格式化文本中使用 {:x} 占位符等效

      示例

      println("{}, {}", 0xAABBCC, hptr(0xAABBCC)); // 11189196, 00AABBCC
      println("{}, {:x}", 0xAABBCC, 0xAABBCC); // 11189196, 00AABBCC

    • gettextptr

      • gettextptr() : EUDVariable
        获取本机显示到屏幕上的下一行文本的行

    • dwpatch_epd

      • dwpatch_epd(dstepd, value)
        用 [value] 给本机内存地址 0x58A364 + [dstepd] * 4 打内存补丁

    • GetMapStringAddr

      • GetMapStringAddr(strID : TrgString) : EUDVariable
        获取本机一个地图字符串或字符串编号 [strID] 的内存地址

      示例

      // 它支持使用字符串及 ID 获取,以下这几种用法等效
      const addr = GetMapStringAddr(6);
      const addr = GetMapStringAddr("Force 3");

    • GetTBLAddr

      • GetTBLAddr(TBLKey : StatText) : EUDVariable
        获取本机一个 TBL 表中的Key/编号 [TBLKey] 的内存地址
        这个不是 $B(或称 EncodeTBL) 获取到的那个,EncodeTBL 获取 TBL 表中保存的各种信息字符串在 tbl 文件中对应的编号

        值得一提的是,TBLKey 字符串本身并不一定在内存中实际存在。
        比如,内存中并不存在 "Terran Siege Tank (Tank Mode)" 这个字符串。
        "Terran Siege Tank (Tank Mode)" 这个 TBLKey 所对应的地址内的字符串 (位于 stat_txt.tbl string section 内)打印出来是 "Terran Siege Tank"

      示例

      // 它支持使用 TBLKey 或是 TBL 编号获取,以下这几种用法等效
      const addr = GetTBLAddr(4);
      const addr = GetTBLAddr("Terran Goliath");
      const addr = wread(dwread_epd(EPD(0x6D1238)) + $B("Terran Goliath"));

    • settbl

      • settbl(tblID : StatText, offset, *args, encoding="cp949")

      • settbl2(tblID : StatText, offset, *args, encoding="cp949")

        设置本机指定 [tblID] 在 TBL 表中的偏移 [offset] 内存字符串的值为 *args,settbl 和 settbl2 的区别在于,settbl 会在设置的字符串最后加上 EOS 字符,而 settbl2 不会

      • settblf(tblID : StatText, offset, format_string, *args, encoding="cp949")

      • settblf2(tblID : StatText, offset, format_string, *args, encoding="cp949")

        设置本机指定 [tblID] 在 TBL 表中的偏移 [offset] 内存字符串的值为一个格式化字符串,settblf 和 settblf2 的区别在于,settblf 会在设置的字符串最后加上 EOS 字符,而 settblf2 不会

        // 以下代码等价
      settbl("Terran Goliath", 1, "1234");
      settbl2("Terran Goliath", 1, "1234\0");
      dbstr_print(GetTBLAddr("Terran Goliath") + 1, "1234\0", EOS = false); // 将 Terran Goliath 改名成 T1234


  • 数学函数


    • atan2

      • atan2(y, x) : EUDVariable
        两个参数的反正切函数,返回的是原点至点 (x,y) 的方位角,即与 x 轴的夹角

      • atan2_256(x, y) : EUDVariable
        与 atan2 区别在于,在对角度对处理上,它将一个圆周分成 256 等份,角度是 256 度制,不是 360 度制

      星际争霸中的单位使用的角度用一个字节存储,所以是 256 度制
      0 度表示面朝上,0 到 256 递增向右旋转
      64 度表示面朝右,128 度表示面朝下,192 度表示面朝左

      Warning
      euddraft 0.9.9.7 及以前的版本中 atan2_256 使用数学坐标系
      euddraft 0.9.9.8 及以上版本中 atan2_256 更改为使用星际争霸坐标系

      示例

      function _0998_above() {
      static var is0998above = false;
      once is0998above = l2v(atan2_256(10, 10) >= 90);
      return is0998above;
      }

      function angleBetween_256(x1, y1, x2, y2) {
      if (_0998_above()) {
      return atan2_256(y2 - y1, x2 - x1);
      }
      return atan2_256(x2 - x1, y1 - y2);
      }

      println("从 (131, 33) 到 (765, 546) 的角度是 {}", angleBetween_256(131, 33, 765, 546));

    • sqrt

      • sqrt(x) : py_int | EUDVariable
        计算 [x] 的平方根(取整)

      示例

      function distanceBetween(x1, y1, x2, y2) {
      const x = x2 - x1;
      const y = y2 - y1;
      return sqrt(x*x + y*y);
      }

      println("从 (131, 33) 到 (765, 546) 的距离是 {}", distanceBetween(131, 33, 765, 546));

    • lengthdir

      • lengthdir(length, angle) : tuple[EUDVariable, EUDVariable]
        计算从坐标 (0, 0) 出发向着 [angle] 角度出发走 [length] 距离的另外一个坐标

      • lengthdir_256(length, angle) : tuple[EUDVariable, EUDVariable]
        与 lengthdir 的区别在于,在对角度对处理上,它将一个圆周分成 256 等份,角度是 256 度制,不是 360 度制

      星际争霸中的单位使用的角度用一个字节存储,所以是 256 度制
      0 度表示面朝上,0 到 256 递增向右旋转
      64 度表示面朝右,128 度表示面朝下,192 度表示面朝左

      Warning
      euddraft 0.9.9.7 及以前的版本中 lengthdir_256 使用数学坐标系
      euddraft 0.9.9.8 及以上版本中 lengthdir_256 更改为使用星际争霸坐标系

      示例

      function _0998_above() {
      static var is0998above = false;
      once is0998above = l2v(atan2_256(10, 10) >= 90);
      return is0998above;
      }

      function polarProjection_256(x0, y0, length, angle) {
      var dx, dy;
      if (_0998_above()) {
      dx, dy = lengthdir_256(length, angle);
      return x0 + dx, y0 + dy;
      } else {
      dx, dy = lengthdir_256(length, 320 - angle);
      return x0 + dx, y0 - dy;
      }
      }

      const x, y = polarProjection_256(1264, 880, 888, 73);

      println("从 (1264, 880) 出发朝 73 度(256 度制)走 888 的距离到达 ({}, {})", x, y);

      示例:位置函数的使用/地图主脚本.eps


    • pow

      • pow(x, y) : py_int | EUDVariable
        计算 [x] 的 [y] 次幂,如果两个参数都是编译期常量,那么它可以在编译期返回常量

      示例

      println("2^10 = {}", pow(2, 10));

    • div

      • div(a, b) : py_tuple[EUDVariable, EUDVariable]
        无符号整数除法 [a] 除以 [b],仅支持正整数,返回商和余数

      • div_towards_zero(a, b) : py_tuple[EUDVariable, EUDVariable]
        euddraft 0.9.9.8 版新增,向下取整有符号除法 [a] 除以 [b],返回商和余数,当商是负数时,商向上取整(向 0 的方向)

      • div_floor(a, b) : py_tuple[EUDVariable, EUDVariable]
        euddraft 0.9.9.8 版新增,向下取整有符号除法 [a] 除以 [b],返回商和余数,商是负数时依然会向下取整(向远离 0 的方向)

      • div_euclid(a, b) : py_tuple[EUDVariable, EUDVariable]
        euddraft 0.9.9.8 版新增,计算 [a] 除以 [b] 的欧几里得商和余数。
        这是计算有符号(负数)除法,它计算商使得 a = 商 * b + 余数,并且 0 <= 余数 < 绝对值(b)
        换句话说,结果是将 a ÷ b 四舍五入到商的值,使得 a >= 商 * b。如果 a > 0,则等于向零舍入;如果 a < 0,则等于朝着正负无穷大方向(远离零)四舍五入。

      示例

      var a, b, quotient, remainder;
      a, b = 17, 3;
      quotient, remainder = div(a, b);
      println("div(17, 3) returns {}, {}", quotient, remainder); // div(17, 3) returns 5, 2
      a, b = 17, -3;
      quotient, remainder = div_towards_zero(a, b);
      println("div_towards_zero(17, -3) returns -{}, {}", -quotient, remainder); // div_towards_zero(17, -3) returns -5, 2
      quotient, remainder = div_floor(a, b);
      println("div_floor(17, -3) returns -{}, -{}", -quotient, -remainder); // div_floor(17, -3) returns -6, -1
      quotient, remainder = div_euclid(a, b);
      println("div_euclid(17, -3) returns -{}, {}", -quotient, remainder); // div_euclid(17, -3) returns -5, 2
      a, b = -17, -3;
      quotient, remainder = div_towards_zero(a, b);
      println("div_towards_zero(-17, -3) returns {}, -{}", quotient, -remainder); // div_towards_zero(-17, -3) returns 5, -2
      quotient, remainder = div_floor(a, b);
      println("div_floor(-17, -3) returns {}, -{}", quotient, -remainder); // div_floor(-17, -3) returns 6, -1
      quotient, remainder = div_euclid(a, b);
      println("div_euclid(-17, -3) returns {}, {}", quotient, remainder); // div_euclid(-17, -3) returns 6, 1

    • rand

      • rand() : EUDVariable
        生成一个范围在 0~0xFFFF 的随机整数

      • dwrand() : EUDVariable
        生成一个范围在 0~0xFFFFFFFF 的随机整数

      注意不要在非同步条件下使用随机数函数

      示例

      const r = rand();

    • seed

      • srand(seed)
        设置随机因子为 [seed]

      • getseed() : EUDVariable
        获取设置好的随机因子

      注意不要在非同步条件下使用随机数函数

      示例

      var seed = getseed();
      srand(seed + 1);

    • randomize

      • randomize() : EUDVariable
        初始化随机因子

      注意不要在非同步条件下使用随机数函数

      示例

      function onPluginStart() {
      randomize();
      }


  • 位运算函数


    • bitand

      • bitand(a, b) : py_int | EUDVariable
        按位与运算 [a] & [b]

      示例

      var a = 0b0011; // 3
      var b = 0b1100; // 12
      println("{}", bitand(a, b)); // 0 (二进制的 0b0000)

    • bitor

      • bitor(a, b) : py_int | EUDVariable
        按位或运算 [a] | [b]

      示例

      var a = 0b0011; // 3
      var b = 0b1100; // 12
      println("{}", bitor(a, b)); // 15 (二进制的 0b1111)

    • bitnot

      • bitnot(a) : py_int | EUDVariable
        按位取反运算 ~[a]

      示例

      var a = 0b0011; // 3
      var b = 0b1100; // 12
      println("{}, {}", bitnot(a), b); // 12, 12 (二进制的 0b1100, 0b1100)

    • bitxor

      • bitxor(a, b) : py_int | EUDVariable
        按位异或运算 [a] ^ [b]

      示例

      var a = 0b0111; // 7
      var b = 0b1110; // 14
      println("{}", bitxor(a, b)); // 9 (二进制的 0b1001)

    • bitnand

      • bitnand(a, b) : py_int | EUDVariable
        按位与反运算 ~([a] & [b])

      示例

      var a = 0b0011; // 3
      var b = 0b1100; // 12
      println("{}", bitnand(a, b)); // 15 (二进制的 0b1111)

    • bitnor

      • bitnor(a, b) : py_int | EUDVariable
        按位或反运算 ~([a] | [b])

      示例

      var a = 0b0011; // 3
      var b = 0b1100; // 12
      println("{}", bitnor(a, b)); // 0 (二进制的 0b0000)

    • bitnxor

      • bitnxor(a, b) : py_int | EUDVariable
        按位异或反运算 ~([a] ^ [b])

      示例

      var a = 0b0111;
      var b = 0b1110;
      println("{}", bitnxor(a, b)); // 6 (二进制的 0b0110)

    • bitlshift

      • bitlshift(a, b) : py_int | EUDVariable
        左位移运算 [a] << [b]

      示例

      var a = 0b0111; // 7
      var b = 1;
      println("{}", bitlshift(a, b)); // 14 (二进制的 0b1110)

    • bitrshift

      • bitrshift(a, b) : py_int | EUDVariable
        右位移运算 [a] >> [b]

      示例

      var a = 0b0111; // 7
      var b = 1;
      println("{}", bitrshift(a, b)); // 3 (二进制的 0b0011)


  • QueueGameCommand 函数

    星际争霸定期向其它玩家广播数据包,此系列函数用于将数据包添加到本机广播队列中,等待星际争霸广播它
    QueueGameCommand 系列的函数都是作用于本机玩家而非当前玩家,没法向不在游戏内的玩家或电脑的玩家使用,可用 getuserplayerid() 获取本机玩家的编号判断

    Note 如果本机数据包队列已满,这些函数调用会失效,并且是静默的,它不会有提示也不会有错误的返回值,所以不要过于频繁的调用这些函数。


    • QueueGameCommand

      • QueueGameCommand(data, size)
        向本机广播队列添加尺寸为 [size] 数据包 [data],本节所有的函数都是对这个函数发送特定的数据包的封装

    • QueueGameCommand_MinimapPing

      • QueueGameCommand_MinimapPing(xy)
        向本机广播队列添加小地图 [xy] 坐标上发 Ping 的数据包,xy 的计算方法是 x + y * 65536

      示例

      // 在 1234, 2345 这个坐标上发起 Ping
      QueueGameCommand_MinimapPing(1234 + 2345 * 65536);

    • QueueGameCommand_QueuedRightClick

      • QueueGameCommand_QueuedRightClick(xy)
        向本机广播队列添加右键点击 [xy] 的数据包,xy 的计算方法是 x + y * 65536

      示例

      // 在 1234, 2345 这个坐标上点右键,如果有选中单位,那么它就会往这跑
      QueueGameCommand_QueuedRightClick(1234 + 2345 * 65536);

    • QueueGameCommand_Select

      • QueueGameCommand_Select(n, ptrList: EUDArray)
        向本机广播队列添加选中一些单位的数据包,[n] 是单位个数,[ptrList] 是 cunit 指针列表,不是 epd 列表

        这个只是会发出本机 “已经选中单位的数据包”,并不会在本机画面上选中单位,它只是告诉其它在线玩家,本机选中了这些单位,如果紧接着发 RightClick 数据包,则这些单位就会往目标位置移动

      示例

      const uar = EUDArray(12);
      if (playerexist(P1) && GetPlayerInfo(P1).type == 0x06) { // 检测玩家1是否为在线的人类玩家
      foreach(i : py_range(3)) {
      uar[i] = dwread_epd(EPD(0x628438));
      CreateUnitWithProperties(1, "Zerg Overlord", "Location 1", P1, UnitProperty(invincible = true));
      }
      }
      if (getuserplayerid() == $P1) { // 检测本机是否玩家1
      QueueGameCommand_Select(3, uar);
      QueueGameCommand_QueuedRightClick(1234 + 2345 * 65536);
      }

    • QueueGameCommand_PauseGame

      • QueueGameCommand_PauseGame()
        向本机广播队列添加暂停游戏的数据包

    • QueueGameCommand_ResumeGame

      • QueueGameCommand_ResumeGame()
        向本机广播队列添加继续游戏的数据包

    • QueueGameCommand_RestartGame

      • QueueGameCommand_RestartGame()
        向本机广播队列添加重新开始游戏的数据包

    • QueueGameCommand_UseCheat

      • QueueGameCommand_UseCheat(cheats)
        设置本机作弊码使用情况为 [cheats],多人游戏无效
      作弊码列表
      0x00000001 Black Sheep Wall
      0x00000002 Operation CWAL
      0x00000004 Power Overwhelming
      0x00000008 Something For Nothing
      0x00000010 Show me the Money
      0x00000020
      0x00000040 Game Over Man
      0x00000080 There is no Cow Level
      0x00000100 Staying Alive
      0x00000200 Ophelia
      0x00000400
      0x00000800 The Gathering
      0x00001000 Medieval Man
      0x00002000 Modify the Phase Variance
      0x00004000 War Aint What It Used To Be
      0x00008000
      0x00010000
      0x00020000 Food For Thought
      0x00040000 Whats Mine Is Mine
      0x00080000 Breathe Deep
      0x20000000 Noglues

      示例

      QueueGameCommand_UseCheat(0x00000001 | 0x00000002 | 0x00000010);  // 启用 Black Sheep Wall + Operation CWAL + Show me the Money
      QueueGameCommand_UseCheat(0x00000002); // 关闭 Operation CWAL
      QueueGameCommand_UseCheat(0); // 关闭所有作弊

    • QueueGameCommand_TrainUnit

      • QueueGameCommand_TrainUnit(unit: TrgUnit)
        向本机广播队列添加训练指定单位类型的数据包,配合 QueueGameCommand_Select 选中单位后使用

      示例

      once {
      const uar = EUDArray(12);
      if (playerexist(P1) && GetPlayerInfo(P1).type == 0x06) { // 检测玩家1是否为在线的人类玩家
      SetResources(P1, Add, 10000, OreAndGas);
      uar[0] = dwread_epd(EPD(0x628438));
      CreateUnitWithProperties(1, "Terran Command Center", "Location 1", P1, UnitProperty(invincible = true));
      }
      if (getuserplayerid() == $P1) { // 检测本机是否玩家1
      QueueGameCommand_Select(1, uar); /* 选中它 */
      QueueGameCommand_QueuedRightClick(1234 + 2345 * 65536); /* 设置集结点到 1234, 2345 */
      QueueGameCommand_TrainUnit("Terran SCV"); /* 训练一个 SCV */
      }
      }

    • QueueGameCommand_MergeDarkArchon

      • QueueGameCommand_MergeDarkArchon()
        向本机广播队列添加合成红球(黑暗执政官)的数据包,配合 QueueGameCommand_Select 选中单位后使用

      示例

      once {
      const uar = EUDArray(12);
      if (playerexist(P1) && GetPlayerInfo(P1).type == 0x06) { // 检测玩家1是否为在线的人类玩家
      foreach(i : py_range(6)) {
      uar[i] = dwread_epd(EPD(0x628438));
      CreateUnitWithProperties(1, "Protoss Dark Templar", "Location 1", P1, UnitProperty(invincible = true));
      }
      }
      if (getuserplayerid() == $P1) { // 检测本机是否玩家1
      QueueGameCommand_Select(6, uar);
      QueueGameCommand_MergeDarkArchon();
      }
      }

    • QueueGameCommand_MergeArchon

      • QueueGameCommand_MergeArchon()
        向本机广播队列添加合成白球(光明执政官)的数据包,配合 QueueGameCommand_Select 选中单位后使用

      示例

      once {
      const uar = EUDArray(12);
      if (playerexist(P1) && GetPlayerInfo(P1).type == 0x06) { // 检测玩家1是否为在线的人类玩家
      foreach(i : py_range(6)) {
      uar[i] = dwread_epd(EPD(0x628438));
      CreateUnitWithProperties(1, "Protoss High Templar", "Location 1", P1, UnitProperty(invincible = true));
      }
      }
      if (getuserplayerid() == $P1) { // 检测本机是否玩家1
      QueueGameCommand_Select(6, uar);
      QueueGameCommand_MergeArchon();
      }
      }