帝国时代AI开发

帝国时代2的AI是比较弱智的,因此网上出现了一些比较厉害的AI,例如Barbarian(野蛮人)、The Horde等,甚至出现了一个AI Ladder对市面上的AI进行排行。恰逢国庆,打算首先来研究一下帝国时代AI的原始DSL以及UserPatch补丁修改后增加的功能,并且结合一些著名AI探讨一些常用的手法。

Native DSL简介

帝国时代2 AOK(即红帽子)版本的DSL文档可以从这里下载,翔鹰帝国提供了有点奇怪的中文翻译。此外外国一款针对1.0c版本的UserPatch加强了DSL,有了一些新的特性,目前已更新到v1.4,它的官网是userpatch.aiscripters.net。
一套ai包含两个文件,.per即personality文件,包含着所有的ai部分,ai文件一般只做占位符类似的作用。
如果per文件是空的,那么就会弹出一个错误,并且农民会四处跑,进行牧羊。

def语法

总的来说,这份DSL类似于lisp语言,主要由下面的defrule语句构成

1
2
3
4
5
(defrule  
(some facts)
=>
(some action takes place)
)

一个defrule可以看做地图编辑器里面的一个触发项。下面的fact/action可以包含若干个条件/动作。
rule一旦被定义,系统就会不断轮询检查是否符合facts所描述的条件,如果符合,就执行action语句。脚本不会像C++一样只采用最匹配的规则,如下面两个规则都是适用的,所以我们细化战略时,必须同时写真条件和假条件。

1
2
3
4
5
6
7
8
9
10
11
(defrule
(cc-players-unit-type-count any-human-enemy town-center >= 1)
=>
(chat-to-all "aaa")
)
(defrule
(current-age == dark-age)
(cc-players-unit-type-count any-human-enemy town-center >= 1)
=>
(chat-to-all "bbb")
)

对于一些只想执行一次的语句就可以通过最末尾的(disable-self)来禁用这条规则。
除了defrule,还有defconst,用来定义常数,defconst可以重复定义(如果值相同)。

load语法

这个有点类似于C++中的include了。
常常针对各个民族的特点包括陆/海/游/移民等不同形式各写一套ai,然后在一个总的per文件里面针对不同情况来load。

随机加载

可以通过load-random指令按照一定比率随机加载per文件,这样可以做到一定程度的随机ai。

条件加载

通过#load-if-defined#load-if-not-defined可以实现条件加载。这两个命令接受System-defined symbols,来自于开始游戏时的设定,例如初始资源选项就有LOW-RESOURCES-START/MEDIUM-RESOURCES-START/HIGH-RESOURCES-START三种,分别对应了游戏下拉列表里面的三个选项。
条件加载有点类似于C++中的预处理指令,但实际上帝国时代引擎中没有预处理阶段,条件加载所包裹的指令只是不被load,但会被parse的。
不加载一些rule能够有效提高性能,这样轮询的对象就会少一点。

通配符(wildcard)

any-every-可以引导通配符,其中any-引导的需要一个为真,every-引导的则需要全部为真。特别地this-any-里面有any,但实际上它是variable。
通配符包括 any-ally、any-computer、any-computer-ally、any-computer-enemy、any-computer-neutral、any-enemy、any-human、any-human-ally、any-human-enemy、any-human-neutral、any-neutral、every-ally、every-computer、every-enemy、every-human、every-neutral
根据科技的研究骑士可以升级为重装骑士和圣殿骑士,这时候就需要使用-line是这个通配符,knight-line表示骑士,这样我们就不需要去判断是否研究过cavalier或者paladin了。

facts、actions、parameters、variables和goal

事实(facts)一般用作rule的条件。例如military-population这个fact表示自己此时的军事人口。不过我们不能把这个值取出来,只能用一个比较运算符和某个立即量进行比较,形成一个predicate。例如military-population > 10
fact或action的参数称为parameter。
this-引导的为变量(variable),variable的生存空间只存在于当前规则中,并且variable都是由系统隐式定义的,例如this-any-enemy表示选定的任一敌人。
那么这个this-any-enemy具体是是怎么决定的呢?这由facts中的通配符来匹配得到,例如在facts中匹配了哥特文明的任意敌人,那么下面的this-any-enemy就指的上面的敌人。

1
2
3
4
5
6
(defrule
(players-civ any-enemy gothic)
=>
(chat-to-player this-any-enemy "I know you are a Goth")
(disable-self)
)

需要再次强调的是在这个规则之外,本次匹配得到的this-any-enemy就不能适用了。
有没有用户能控制的全局的变量呢?最接近的应该是目标(goal)。goal可以由(set-goal goal-id value)来定义,goal-id范围一般是从1-70(HD版本),为了方便阅读,常使用defconst常数。可惜的是goal的读取很不方便,因为它只能通过(goal goal-id value)来判断是否相等,甚至连比较运算符都不支持。

定时器

注意enable之后必须要disable掉当前语句,不然会一直enable导致无法trigger

关系运算符

游戏难度的关系运算符是和想象相反的,即easiest>easy>moderate>hard>hardest。

策略

sn-引导的为策略。
城镇规模town-size指的是一个城镇建筑所覆盖的面积。

escrow预留资源

escrow-amount命令可以将某一类的资源预留一部分作为储备,这样所有的资源会被分成两块,便于管理。
release-escrow可以取消预留资源

AI基本操作

调试

我们可以看到大多数时候,变量都是不能直接获取值的,而是通过比较是否相等(goal)或者比较运算符比较(如building-type-count)实现,对这些数值的调试只能写一条defrule然后在里面判断数值输出。
但是还有一类sn-开头的strategic-number,这个可以通过chat-trace这条指令输出。

使用taunt调试

taunt-detectedacknowledge-taunt这两个可以实现由用户输入某个数字taunt,然后输出相应值的功能。

补人口

默认1.5倍时间下,TC造一个农民大概在13秒左右,一个农民造房子大概在15秒左右,所以当人口差两个的时候就需要造房子了

1
2
3
4
5
6
7
(defrule
(can-build house)
(housing-headroom < 2)
(population-headroom > 3)
=>
(build house)
)

can-build会检查该building是否符合当前的文明、科技树以及资源量(减去escrow预留的资源)。不过即使不加can-build,电脑也不会在文明、科技、资源量不满足的情况下造,而是作为一次失败的build尝试。一旦一次build失败了,此趟执行的所有build x都不会被运行了,即使后来条件全部满足。
类似的还有can-traincan-research,不过这两个还要注意建筑物在研究科技/造兵时被占用的问题。
如果不幸卡农民了,可以升级织布机

1
2
3
4
5
6
7
8
(defrule
(current-age == dark-age)
(not (can-train villager))
(can-research ri-loom)
=>
(research ri-loom)
(disable-self)
)

城镇规模(TSA)进攻

一般的进攻有(attack-now)语句,该语句会造成一队士兵以编队形式过来进攻,不过有些问题,例如在编队行进过程中受到进攻也不还手。由于attack-now是一队一队地出发的,所以可以通过改小sn-percent-attack-soldiers然后多次attack-now实现多波的、相互呼应的进攻。
TSA即Town Size Attack,相对于(attack-now)的直来直去,TSA进攻更分散、更操作性一点。它通过增加城镇的面积,这样己方就会对城镇面积内的敌人进行攻击。
注意要区分TSA和日常随着经济发展的城镇增长,城镇增长相对TSA还要额外扩大sn-camp-max-distancesn-mill-max-distance用以获得更多资源。
为了测试,我们可以做一个场景地图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defrule
(true)
=>
(set-strategic-number sn-home-exploration-time 0)

(set-strategic-number sn-maximum-town-size 255)
(set-strategic-number sn-number-attack-groups 200)
(set-strategic-number sn-maximum-attack-group-size 200)
(set-strategic-number sn-minimum-attack-group-size 1)
(set-strategic-number sn-attack-intelligence 1)
(set-strategic-number sn-enemy-sighted-response-distance 200)
(set-strategic-number sn-disable-attack-groups 0)
(set-strategic-number sn-percent-attack-soldiers 100)
)

sn-attack-intelligence表示智能进攻系统。该系统尝试在攻击时躲避敌人单位,尝试从多角度进攻。配合sn-attack-coordination设为2能够实现多线作战效果。
sn-disable-attack-groups会禁用自动进攻编组,TSA必须不禁用。类似的选项还有sn-disable-defend-groups
结果调试发现,首先所有的兵力沿着城镇方向散开,直到有一个军事单位发现敌人。当战斗单位还存在的时候TSA农民是不怎么参与战斗的,即使设置了

1
2
3
(set-strategic-number sn-allow-civilian-defense 3)
(set-strategic-number sn-allow-civilian-offense 2)
(set-strategic-number sn-number-civilian-militia 200)

但如果放到一个纯农民的地图里面,农民就都会上去搏。
sn-percent-attack-soldiers似乎并不对TSA有作用,它仅针对attack-now命令,指的是进攻士兵占防守士兵的比例。官方说明中提到用它时最好不用sn-number-defend-groups(注意是defend)。
sn-enemy-sighted-response-distance指敌人攻击自己时,什么范围内的己方单位会作出反应,为了调试效果,我们把这个值设得非常大。

sn-special-attack-type1(2/3)是个很有用的命令,它设置了首要的攻击目标(单位、建筑等),在UP的注释中却有不同的说明,当它为1时会攻击僧侣。我在论坛上确认了使用UP时UP的注释是正确的,此外原版DSL中更本就没有sn-special-attack-type2sn-special-attack-type3
与之对应的是sn-gold-defend-priority等一系列防守重要性等级策略,从0到7防守力度越来越大。

sn-number-attack-groupssn-minimum-attack-group-sizesn-maximum-attack-group-sizesn-attack-group-size-randomness是一套指令。

修建围墙

围家是帝国时代做经济的一个军事支撑,如果在前方给敌人足够压力可以借助TC和军事建筑,如果想直城或直帝则需要木头墙+石墙围一道。
围墙建造第一步是enable-wall-placement这个命令,这个命令会通知将来的建筑离未来的围墙至少距离1格(tile),因此最好在初始化中就做好。enable-wall-placement带一个参数perimeter,设为1就是小围,2是大围。
build-wall是建造围墙命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(defrule
(true)
=>
(enable-wall-placement 1)
(disable-self)
)
(defrule
(current-age >= castle-age)
(stone-amount > 300)
(can-build-wall 1 stone-wall-line)
=>
(build-wall 1 stone-wall-line)
)
(defrule
(or
(wall-completed-percentage 2 >= 20)
(wall-completed-percentage 1 >= 20)
)
(or
(can-build-gate 2)
(can-build-gate 1)
)
=>
(build-gate 2)
(build-gate 1)
)

小围技术

小围技术包含两个方面,一是如何让房子等技术修成圈,另一个是如何让农民在第一个建筑没修完时赶快桥第二个建筑

Native AI赏析之BOOM II

BOOM II ai是一个比较厉害的ai,特点是疯狂爆兵。和他打了几把,它不喜欢杀敌人的农民,后期也不会清理空闲农民(倒是UserPatch里面有个相关的清理命令),多人的时候不太喜欢在后面圈贸易,所以经济有点吃亏,黑暗时代不喜欢拉猪赶鹿可能是AI的一个通病,可能做不到这么细致。

防守塔爆

GOAL-DEF-TRUCH指的是防御塔爆(defend tower rush)

塔爆状态流

第一步是发现修塔,如果在封建时代前的前期(前1200秒)内探查到敌人修塔,就切换GOAL-DEF-TRUCH到7。
(goal GOAL-ADDRESOURCE 1)这个条件我不太明白是怎么回事
值得注意的是building-type-countbuilding-type-count-total,前一个只计算现存的建筑数,后一个计算包括正在队列(正在建造)中的建筑数。注意摧毁了的建筑不会增加到这个里面。
unit-type-countunit-type-count-total的区别也在此,不过unit-不仅对建筑用,也可以对军队等单位用。

1
2
3
4
5
6
7
8
9
10
11
12
(defrule
(goal GOAL-ADDRESOURCE 1)
(game-time < 1200)
(current-age <= feudal-age)
(goal GOAL-DEF-TRUCH 0)
(players-building-type-count any-human-enemy watch-tower >= 1)
=>
(set-goal GOAL-DEF-TRUCH 7)
(chat-to-player this-any-human-enemy "修塔...")
(set-strategic-number sn-maximum-town-size 35)
(disable-self)
)

第二步是发现塔爆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defrule
(goal GOAL-ADDRESOURCE 1)
(game-time < 1000)
(current-age <= feudal-age)
(goal GOAL-DEF-TRUCH 7)
(players-military-population any-human-enemy <= 1)
(enemy-buildings-in-town)
=>
(chat-to-player this-any-human-enemy "塔暴?!")
(set-goal GOAL-DEF-TRUCH 1)
(set-strategic-number sn-maximum-town-size 20)
(set-strategic-number sn-camp-max-distance 25)
(set-goal GOAL-SPECIAL-AID 1)
(disable-timer TIMER-SPECIAL-AID)
(enable-timer TIMER-SPECIAL-AID 300)
(disable-self)
)

首先需要经过第一步GOAL-DEF-TRUCH已经到7,然后敌人的军事人口应该小于等于1(相对于黑快的大于等于4),并且有建筑在城镇范围内部了。这时候切换GOAL-DEF-TRUCH到1,并且调整城镇规模,此外切换GOAL-SPECIAL-AID到1,并启动定时器TIMER-SPECIAL-AID
此外脚本对前置塔攻的情况进行了特殊处理。前置塔攻相比塔爆多了军事人口,更厉害。在处理时切换GOAL-DEF-TRUCH到2,启动SPECIAL-AID一套流程,但不调整城镇规模。
第三步,检查敌人是否使用木墙或者石墙围,并根据具体情况不同重置TIMER-SPECIAL-AID为400(前置塔攻490)、900、1600

1
2
3
4
5
6
7
8
9
10
11
(defrule
(goal GOAL-DEF-TRUCH 1)
(players-building-type-count any-human-enemy watch-tower >= 1)
(players-building-type-count any-human-enemy stone-wall >= 2)
=>
(chat-to-player this-any-human-enemy "24你死定了!!")
(set-goal GOAL-SPECIAL-AID 1)
(disable-timer TIMER-SPECIAL-AID)
(enable-timer TIMER-SPECIAL-AID 900)
(disable-self)
)

下面讨论ai防御塔爆的行为,这在Defend Truch部分有定义。这些防御行为的产生条件是封建时代且GOAL-DEF-TRUCH为1,即防御塔爆状态

  1. 首先取消GOAL-FAST-ATTACK计划

  2. 如果发现有石矿,造采石场

  3. 如果能造塔,并且已造塔数小于4,那么就造塔
    注意下面的造塔命令,能够粗略地控制造塔范围

    1
    2
    3
    (set-strategic-number sn-maximum-town-size 12)
    (build watch-tower-line)
    (set-strategic-number sn-maximum-town-size 20)
  4. 如果每个敌人都没到城堡,如果能造塔,并且已造塔数小于8,那么反塔爆别人
    这里用了build-forward命令,即前置造建筑物。

  5. 当自己到了城堡之后,如果塔还没清掉(估计是石墙搞了一波农民搏不掉),那么就造BK(siege-workshop)。下面不用多说,造攻城车(battering-ram-line)。然后就等着攻城车自己清理吧。

最后是防御塔爆的结束条件:

  1. 城堡时代、农民数大于50
  2. 时间超过1020,塔全部被清理,农民数大于28

前置塔攻就不详细讨论了,下面的是在成功防御前置塔攻之后的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defrule
(game-time > 900)
(goal GOAL-DEF-TRUCH 2)
(or (goal GOAL-FORCE-COMPARE 3)
(goal GOAL-FORCE-COMPARE 4))
(players-building-type-count any-human-enemy watch-tower <= 0)
=>
(chat-to-player this-any-human-enemy "11")
(set-goal GOAL-DEF-TRUCH 0)
(set-goal GOAL-FAST-ATTACK 1)
(set-goal GOAL-SPECIAL-AID 2)
(disable-timer TIMER-SPECIAL-AID)
(disable-self)
)

防守TC暴

TC暴(Douche)是种比较恶心的塔爆,玩家在黑暗自爆自己的TC,然后农民跑到对家的基地附近建TC,然后TC对射和对方耗。BOOM II对TC暴有着绝妙的应对方法,那就是不打TC暴、、、

1
2
3
4
5
6
7
8
9
10
(defrule
(game-time < 900)
(goal GOAL-PVP 1)
(current-age <= feudal-age)
(cc-players-unit-type-count any-human-enemy town-center <= 0)
=>
(chat-to-player this-any-human-enemy "不会吧?TC暴?")
(set-goal GOAL-DEF-TRUCH 3)
(disable-self)
)

cc-players-unit-type-count是一个作弊命令,相对于不加cc-的命令,它获取玩家某一种类建筑的数目,不管自己能不能看到。这里判断如果在黑暗时代TC的数量变成0,那么对方很可能就是TC爆了。
GOAL-DEF-TRUCH改成3表示当前是TC爆。

1
2
3
4
5
6
7
8
9
10
11
(defrule
(goal GOAL-DEF-TRUCH 3)
(goal GOAL-TOWN-SIZE-ATTACK 0)
(enemy-buildings-in-town)
(town-under-attack)
(players-building-type-count any-human-enemy town-center >= 1)
=>
(chat-to-all "不打TC暴!")
(set-goal GOAL-RESIGN 1)
(disable-self)
)

城快进攻

UserPatch v1.4简介

我们发现帝国时代内置的ai命令还是比较弱的,我们很难实现一些逻辑。简单地说,删除所有闲置的农民就难以实现。为此使用UserPatch提供的增强版Scripting是个不错的选择。

UP的安装

在网站userpatch.aiscripters.net上下载,里面包含一个Reference文件夹和一个exe程序。
Reference里面Scripting文件夹里面的per文件是需要load进去的常量,另一个Reference.html与网上在线的Guide相同。
此外,网站的论坛也能提供更详细的资料。

UP基础语法

变量

在Native DSL中goal的值既不能直接拿出来,也不能直接比较,UP改善了这个特性。
首先引入了三个类型运算符c:g:s:c:表示作为常数值,g:表示作为goal值,s:表示作为sn值。
举一个例子

1
2
3
4
5
6
7
8
(defconst gl-slot0 100)
(defrule
(true)
=>
(set-goal gl-slot0 77)
(up-chat-data-to-all "gl-slot0 key is: %d" c: gl-slot0)
(up-chat-data-to-all "gl-slot0 value is: %d" g: gl-slot0)
)

以上rule的结果是

gl-slot0 key is: 100
gl-slot0 value is: 77

可以发现现在goal可以真正地作为变量使用了,在定义变量的时候,我们通常以gl-为前缀,这样的gl-类似于一个整型指针(只不过由我们自己规定地址),g:可以将它解引用。
但是注意不能在set-goal里面用g:

1
(set-goal gl-slot1 g: sl-slot0)

替代方案是使用up-modify-goal

get系列函数

既然goal可以作为变量使用,那么我们肯定希望用它来保存一下有用数据,up-get-系列函数能够将我们需要的一些数据取到变量中。

get fact系列

up-get-fact是最基础的get fact函数。
在先前,我们不能获取military-population的值,我们只能拿它进行比较,如(military-population > 10)
但现在我们可以将它保存在gl-data中了。遗憾的是似乎这个函数只能获取UserPatchConst中定义的fact,另外的一些,例如villager-hunter之类的就没办法获取。

1
2
3
4
5
6
7
(defconst gl-data 101)
(defconst military-population 31)
(defrule
(true)
=>
(up-get-fact military-population 0 gl-data)
)

但是UP还可以为我们做更多,我们可以获得某些玩家(由every/any通配符指定)事实集合中的最大/小值以及和,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(defconst gl-my-civ-sum 101)
(defconst gl-ene-civ-sum 102)
(defconst gl-cmp 103)

(defconst civilian-population 32) ;any
(defrule
(true)
=>
(up-get-fact-sum any-enemy civilian-population 0 gl-ene-civ-sum)
(up-get-fact civilian-population 0 gl-my-civ-sum)
(up-modify-goal gl-cmp g:= gl-my-civ-sum)
(up-modify-goal gl-cmp g:/ gl-ene-civ-sum)
(up-chat-data-to-all "me is: %d" g: gl-my-civ-sum)
(up-chat-data-to-all "ene is: %d" g: gl-ene-civ-sum)
(up-chat-data-to-all "comparation of ene and me is: %d" g: gl-cmp)
)

这个可以输出敌人的农民人口与自己的农民人口的比值,可惜是整数,这里因为我们没有探测到敌人的家,所以gl-ene-civ-sum值是0或-1,结果是0。

成本函数

up-reset-cost-data将四种资源成本全部置零,例如

1
2
3
4
5
6
7
8
9
10
(defconst gl-military-cost-food 111)
(defconst gl-military-cost-wood 112)
(defconst gl-military-cost-stone 113)
(defconst gl-military-cost-gold 114)
(defrule
(true)
=>
(up-setup-cost-data 1 gl-military-cost-food)
(disable-self)
)

当第一个参数为1的时候,将所有的成本设置为0,真是够奇葩的,为啥不搞个memset一样的呢?
为什么第二个参数是gl-military-cost-food呢?因为可以把gl-military-cost-foodgl-military-cost-gold看成一个四个元素的数组,而gl-military-cost-food相当于传入了一个头指针。
在使用up-reset-cost-data,其他的成本函数都是对当前选定的“数组”进行操作了。
下面的代码往总成本上加了两份军事成本(每份成本包含一个骑士)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(defrule
(true)
=>
(up-setup-cost-data 1 gl-military-cost-food)
(up-add-object-cost c: knight-line c: 1)
(up-setup-cost-data 1 gl-cost-food)
(up-add-cost-data gl-military-cost-food c: 2)

(up-chat-data-to-all "wood is: %d" g: gl-cost-wood)
(up-chat-data-to-all "stone is: %d" g: gl-cost-stone)
(up-chat-data-to-all "food is: %d" g: gl-cost-food)
(up-chat-data-to-all "gold is: %d" g: gl-cost-gold)
(up-chat-data-to-all "mit wood is: %d" g: gl-military-cost-wood)
(up-chat-data-to-all "mit stone is: %d" g: gl-military-cost-stone)
(up-chat-data-to-all "mit food is: %d" g: gl-military-cost-food)
(up-chat-data-to-all "mit gold is: %d" g: gl-military-cost-gold)
(disable-self)
)

up-add-object-cost第一个参数表示单位id,第二个参数表示数量,相对up-setup-cost-data还有科技研发成本up-add-research-cost
up-setup-cost-data第一个参数表示一个goal,第二个参数表示份数
up-get-cost-delta计算当前资源与建造成本的差值,负的表示不够。这个值与escrow无关。

1
2
3
4
5
6
7
8
9
10
(defrule
(true)
=>
(up-setup-cost-data 1 gl-cost-food)
;(up-modify-escrow wood c:= 100)
(up-add-object-cost c: archer-line c: 200)
(up-get-cost-delta gl-delta-food)
(up-chat-data-to-all "delta is: %d" g: gl-delta-wood)
(disable-self)
)

坐标代数

UP提供的坐标代数能实现高级AI的精细控制,如此篇帖子
为了表示坐标,首先定义一个点对。类似于上面up-reset-cost-data操纵资源的方式,点对的地址应当是连续的(形成一个二维数组),如下面的100和101,这样gl-point-x可以看做指向该点对的指针。

1
2
(defconst gl-point-x 100)
(defconst gl-point-y 101)

我们可以将gl-point-x点对设为目标点,供如up-build等命令使用

1
(up-set-target-point gl-point-x)
  1. up-lerp-percentup-lerp-tiles
    这个用来计算坐标偏移,将第一个点对作为基点,第二个点对作为位移值进行偏移。
    up-lerp-tiles会偏移固定的格数,up-lerp-precent则按百分比。

  2. up-cross-tiles
    我写了段代码测试了一下,并没有看出来有啥用,应该结果保存在第一个点

流程控制

在同一个文件里面rules的执行可以看成是从上至下的,因此可以利用up-jump-rule来实现选择或者循环结构,但注意#load块可能影响位置。

直接寻的系统

find

过滤器

过滤器由up-filter-distanceup-filter-excludeup-filter-garrisonup-filter-includeup-filter-range构成
注意以下命令中-1表示忽略此过滤条件

  1. 选择目标点周围10个内单位:(up-filter-distance c: -1 c: 10),其中两个参数分别为最小距离和最大距离
  2. 派出具有某个编号的单位:(up-filter-exclude cmdid-trade -1 -1 -1),其中四个参数分别表示命令编号、行动编号、执行编号和类别编号。例如(up-filter-exclude -1 actionid-explore orderid-relic warship-class)
  3. 选择驻扎了至少5个单位的建筑:(up-filter-garrison c: 5 c: -1),两个参数同样是最大值和最小值
  4. up-filter-include这个和exclude是相似的,不过第四个参数改为了是否在主大陆上

一般重置需要同时重置search结果和过滤器,即

1
2
(up-reset-search 1 1 1 1)
(up-reset-filters)

使用直接寻的系统找到的对象可以利用up-set-target-object设为目标,然后通过up-object-data等语句对目标进行操作,下面的语句摘自拉猪部分

1
2
(up-set-target-object search-local c: 0)
(up-object-data object-data-hitpoints < 40)

人员控制

包括驻扎、巡逻、删除冗余人员

  1. 驻扎
    up-gather-inside可以指定建筑生产集合点是自己本身。这在操作的时是很有用的一个特性,一方面敌人不容易观察你到底生产了什么,第二个单位不至于瞎跑,或者被泼粪车泼到。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (defrule
    (true)
    =>
    (up-gather-inside c: dock c: 1)
    (disable-self)
    )
    (defrule
    (unit-type-count warship-class >= 10) ; warship-class = 922
    =>
    (up-gather-inside c: dock c: 0)
    )

直接驻扎的命令是up-garrison,如

1
(up-garrison battering-ram c: infantry-class) ; infantry-class = 906

将所有步兵驻扎到工程车中,注意指定建筑不能使用battering-ram-line这种带wildcard的。
2. up-guard-unit

UserPatch AI赏析之Bruteforce

Bruteforce是一个适合新手练习的AI。

AI概览

AI预定义了定义了一些城堡时代的打法,有些英文属于可以查询网站,对应着各个per文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defconst sn-castle-age-strategy 182)

(defconst xbow 1)
(defconst end-game 2)
(defconst krush 3)
(defconst EAGLE-RUSH 4)
(defconst fast-castle 5)
(defconst eagle-rush 6)
(defconst conquistadors 7)
(defconst naval-fun 8)
(defconst klew 9)
(defconst castled 10)
(defconst booming 11)
(defconst lsr 12)
(defconst PIKEMAN 13)
(defconst DRUSH 14)
(defconst RUN 15) ; https://youtu.be/mw2kKyJu9gY?t=2m11s

end-game应该表示此战术已失效
xbow是打弩手。
Krush指的是城快马爆,28p升封建。
Scrush是打肉马(配合弩手和散兵),24p到封建。
Skirm是打散兵(掷矛战士),24p到封建。
GenericAra是通用阿拉伯,以打弓箭为主,23-24p升封建。
KLEW(Kidd’s Lightning Eagle Warrior Rush)是美洲民族的打法,主张直城,然后雄鹰快攻,24p到封建,卖石头点城堡。
Booming是暴经济直帝,30p到封建
PIKEMAN是长枪兵。
TRASH是垃圾兵(长戟和掷矛)。
MAA是打装甲步兵。
HCA是打骑射手。
Sling是打进贡。
CASTLED(Castle Drop)是打前置城堡。

开局

现在选择我最喜欢的蒙古民族,在阿拉伯地图上进行开局。
我们看到蒙古民族会随机加载一些策略文件。

1
2
3
4
5
6
7
8
9
#load-if-defined MONGOL-CIV
#load-if-defined UP-POCKET-POSITION
(load "Brutal2\Krush")
#else
(load-random 45 "Brutal2\GenericAra"
52 "Brutal2\Scrush"
3 "Brutal2\Krush")
#end-if
#end-if

UP-POCKET-POSITION指的是玩家是否坐中,那1v1的时候玩家始终坐中,于是就用马爆策略。

造房子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#load-if-not-defined CHINESE-CIV
(defrule
(up-gaia-type-count c: livestock-class == 0)
(building-type-count-total town-center >= 1)
(population-headroom > 0)
(housing-headroom < 3)
(up-pending-objects c: house < 1)
(building-type-count-total house < 1)
(up-can-build 0 c: house)
=>
(up-assign-builders c: house c: 2)
(set-strategic-number sn-placement-zone-size 1)
(up-set-placement-data my-player-number villager c: 1)
(up-build place-control 0 c: house)
)

up-gaia-type-count用来表示尚存的的自然资源的数目,例如(up-gaia-type-count c: livestock-class > 6)表示是否还存在超过6只绵羊或火鸡。与之对应的是up-gaia-type-count-total,表示所有曾经发现过的自然资源的数目,但是对鹿和羊来说没有相关数据,所以其实返回的是up-gaia-type-count的结果。注意,看到的羊可能还是灰色,并不等于自己拥有的羊。看自己的羊应该用up-object-type-count,并且羊被杀了之后up-object-type-count会减少,而不是吃完会减少。
population-headroom指的是游戏设置最大人口和目前住房提供人口的差额。
housing-headroom指的是目前住房提供人口和实际人口的差额。
上面这段是造第一个房子,由于中国城镇中心提供10个人口,所以中国不需要造第一个房子。
up-assign-builders为指定类型建筑分配农民数,由于一开始就只有一个人口富余,容易卡房子,所以分配两个农民造。
有意思的是up-build命令,它的第二个参数可以取place-normalplace-forwardplace-controlplace-point
这里的place-control是与up-set-placement-data配合使用的。例如

1
(up-set-placement-data my-player-number -1 c: -25) ; home town center = -1

表示在主TC后面25格。本段代码的意思即在村民旁边建造房子。
place-point则是利用up-set-target-point储存的地点。
sn-placement-zone-sizeplace-forwardplace-control这两个选项时。
下面是另外一个造房子的条件,在开局的时候并没有被触发。由于这两个行为是全部一样的,所以其实我感觉做简单一点,这两个可以合为一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defrule
(game-time < 7)
(up-gaia-type-count c: livestock-class > 0)
(building-type-count house < 1)
(building-type-count-total town-center >= 1)
(population-headroom > 0)
(housing-headroom < 3)
(up-pending-objects c: house < 2)
(up-can-build 0 c: house)
=>
(up-assign-builders c: house c: 2)
(set-strategic-number sn-placement-zone-size 1)
(up-set-placement-data my-player-number villager c: 1)
(up-build place-control 0 c: house)
)

如果房子超过一个了,开局就不会卡农民了,这样将造房子的农民减少到一个。

1
2
3
4
5
6
7
(defrule
(building-type-count house > 0)
=>
(up-assign-builders c: house c: 1)
(disable-self)
)
#end-if

注意开局的时候我们一般是造两个房子,一个分配2农民,一个分配1农民,这样会导致超过sn-cap-civilian-builders的默认值2,所以最好在一开始扩大一下,例如设为25。最重要的是sn-enable-new-building-system必须设为1,否则村民只能同时建造一个建筑。

农民调配

可以结合这篇帖子这篇帖子来理解AI的一些思路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defrule
(up-compare-goal gl-map-style != LAND-NOMAD)
(up-compare-goal gl-map-style != NOMAD)
(current-age == dark-age)
(game-time < civilian-exploration-time)
(unit-type-count livestock-class < 1)
(strategic-number sn-number-explore-groups != 4)
=>
(set-strategic-number sn-cap-civilian-explorers civ-explorers)
(set-strategic-number sn-cap-civilian-gatherers 100)
(set-strategic-number sn-percent-civilian-gatherers 100)
(set-strategic-number sn-percent-civilian-explorers 100)
(set-strategic-number sn-number-explore-groups civ-explorers)
(set-strategic-number sn-total-number-explorers civ-explorers)
)

以上部分是早期探索阶段,阿拉伯地图的civilian-exploration-time为60。根据调试,实际上更多运行的是下面这个rule,因为通常农民很快就能找到一个羊群。这时候设置农民探索者最大数量为0,采集者最大数量为100,并且所有的农民都去采集。探索者的队伍容量为1人。
这里注意与sn-number-explore-groupssn-total-number-explorers区别,前者指的是陆地上的探索者数量,后者指的是村民探索者和军队探索者的总和。但是当有军队进行探索的时候似乎不会派村民进行较多的侦查活动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defrule
(up-compare-goal gl-map-style != LAND-NOMAD)
(up-compare-goal gl-map-style != NOMAD)
(building-type-count town-center > 0)
(current-age == dark-age)
(or(game-time > civilian-exploration-time)
(or(building-type-count-total mill > 0)
(unit-type-count livestock-class >= 2)))
(strategic-number sn-number-explore-groups != 4)
=>
(set-strategic-number sn-cap-civilian-explorers 0)
(set-strategic-number sn-cap-civilian-gatherers 100)
(set-strategic-number sn-percent-civilian-explorers 0)
(set-strategic-number sn-percent-civilian-gatherers 100)
(set-strategic-number sn-number-explore-groups 1)
(set-strategic-number sn-total-number-explorers 1)
)

再往后,根据战术的不同,资源调配的方案有很大不同。

sn设置

下面是strategy number设置,这里分了几部分是因为rule里面action的条数限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defrule
(true)
=>
(set-strategic-number sn-cap-civilian-builders 25)
(set-strategic-number sn-livestock-to-town-center 1)
(set-strategic-number sn-enable-new-building-system 1)
(set-strategic-number sn-enable-training-queue 1)
(set-strategic-number sn-allow-adjacent-dropsites 1)
;(set-strategic-number sn-dropsite-separation-distance 5)
(set-strategic-number sn-disable-builder-assistance 1)
(set-strategic-number sn-camp-max-distance 16)
(set-strategic-number sn-mill-max-distance 32)
;(set-strategic-number sn-defer-dropsite-update 1)
;(set-strategic-number sn-task-ungrouped-soldiers 0)
(set-strategic-number sn-enable-patrol-attack 1)
(set-strategic-number sn-maximum-hunt-drop-distance 12)
(set-strategic-number sn-maximum-town-size 10)
(set-goal gl-new-town-size 10)
(set-strategic-number sn-max-retask-gather-amount 10)
(set-strategic-number sn-retask-gather-amount 0)
(disable-self)
)

sn-camp-max-distance表示伐木场和矿场和城镇中心最远的距离,不可能说我们的一个伐木放到敌人家门口的,对应有sn-mill-max-distance。BF将这两个值分别设为16和32
sn-defer-dropsite-update策略,设为1的时候当新资源放置点建好时才更新dropsite-min-distance,否则刚建造就更新。
sn-task-ungrouped-soldiers策略在TSA攻击时比较有用,它设定未编组部队是否分散开并保卫城镇地区。如果说是1,那么非编组军队(可以认为是空闲的军队)就会在城镇范围内散开并“游荡”。
sn-enable-patrol-attack为巡逻式攻击命令,可参见heavengames上的说明
sn-allow-adjacent-dropsites指的是否将资源放置点建到紧贴资源,这里选择的是1,但是我觉得0更好,这样资源放置点与资源之间会有一格距离,农民和资源放置点之间的接触面会更大
sn-enable-training-queue允许在训练队列中pending一个单位。我们实际操作的时候可能喜欢按shift,然后一下子让电脑造5个甚至填满训练队列,这样会预先一次性支出所有单位的训练资源,但好处是防止我们操作不过来延误暴兵。但是电脑没有这方面的烦恼,rule的触发条件满足了,就会造单位,所以没必要把训练队列里面塞满。由于计算机对所有rule的一趟遍历是周期性的,所以这个命令使得,例如前期一个农民造好后能够立刻开始造下一个农民,而不是可能会等1-2秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(defrule
(true)
=>
(set-strategic-number sn-intelligent-gathering 1)
(set-strategic-number sn-use-by-type-max-gathering 0)
(set-strategic-number sn-gather-defense-units 1)
(set-strategic-number sn-military-level 0)
(set-strategic-number sn-enemy-sighted-response-distance 0)
(set-strategic-number sn-percent-enemy-sighted-response 0)
(set-strategic-number sn-percent-attack-soldiers 0)
(set-strategic-number sn-enemy-current-age dark)
(set-strategic-number sn-dropsite-separation-distance 1)
(set-strategic-number sn-local-targeting-mode 1)
;(set-strategic-number sn-ttkfactor-scalar 200)
(set-strategic-number sn-percent-building-cancellation 10)
(set-strategic-number sn-zero-priority-distance 250)
(set-strategic-number sn-initial-exploration-required 0)
(set-strategic-number sn-enemy-sling-target-player 0)
(disable-self)
)

sn-intelligent-gathering是智能采集系统,有什么用呢?从论坛上有

If you do up-retask-gatherers without sn-intelligent-gathering on, then the villagers will lose their load. If you do it with sn-intelligent-gathering then you don’t lose the load.

sn-military-level并没有在UP或者原生DSL中出现,从gist上的这份源码,应该是自己的军事力量越强,sn-military-level就被设得越高
sn-initial-exploration-required是修建建筑前最少的探索地图比例,由于农民直接在身后拍房子,所以应当为0。注意它的默认值不是0!所以一定要显式地修改回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(defrule
(true)
=>
(set-strategic-number sn-percent-attack-soldiers 100)
(set-strategic-number sn-minimum-attack-group-size 1)
(set-strategic-number sn-preferred-trade-distance 255)
(set-strategic-number sn-consecutive-idle-unit-limit 0)
(set-strategic-number sn-attack-winning-player 0)
(set-strategic-number sn-attack-winning-player-factor 0)
;(set-strategic-number sn-placement-fail-delta 1)
;(set-strategic-number sn-placement-to-center 1)
(set-strategic-number sn-blot-exploration-map 0)
(set-strategic-number sn-blot-size blot-size)
(set-strategic-number sn-escrow-level 0)
(set-strategic-number sn-allow-direct-unit-control 0)
(set-strategic-number sn-maximum-fish-boat-drop-distance 5)
(set-goal gl-slain-deer 0)
(up-setup-cost-data 1 gl-cost-food)
(disable-self)
)

sn-preferred-trade-distance偏好贸易距离,由于贸易越长越好,所以设为255.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defrule
(true)
=>
(set-strategic-number sn-archer-threat 0)
(set-strategic-number sn-infantry-threat 0)
(set-strategic-number sn-cavalry-threat 0)
;(set-strategic-number sn-disable-trade-evasion 1)
;(set-strategic-number sn-disable-villager-garrison 1) ; 2 affects towers too
;(set-strategic-number sn-target-point-adjustment 3) ; right
(set-strategic-number sn-allow-drush-defense 0)
(set-goal temporary-goal12 1) ; player 1 as default.
(up-change-name "BruteForce") ; Make it easier for 1.5
(disable-self)
)

其中LAND-NOMAD为陆游,NOMAD为游牧。
下面这段造农民的代码来自Krush部分。
我们实际上看到在主per文件里面也有相应的造农民代码,但这个是互相不冲突的,因为条件写得很严格。

1
2
3
4
5
6
7
8
9
10
11
(defrule
(up-compare-goal gl-map-style != WATER)
(strategic-number sn-castle-age-strategy == krush)
(unit-type-count-total villager < max-civ)
(up-research-status c: ri-loom < research-pending)
(unit-type-count villager < 10)
(can-train villager)
=>
(train villager)
(enable-timer 46 21)
)

牵羊

小马斥候有时候可能看到羊,但不会多走几步取得这头羊控制权,并将其派回TC,因此需要进行牵羊。

放伐木场、磨坊

伐木一般要晚于磨坊。虽然农民一般先狩猎,但当羊比较难找时草料丛就起作用了,还有一点是为了种田的方便,最好让农民先把TC旁边的树木全部伐倒。
resource-found语句中food参数特指草料丛,wood参数特指树林(而不是单棵的树)。但有时候(如存档aitest_farwood)即使找到了森林,伐木场依然可能建在单棵树的旁边,解决方案可以是延缓伐木场的建造时间,例如在第一个房子建成后。注意这条命令是一次性的,即使后面树木全部被挖完了,也依然是ture。如何检测是否还有树呢?可以使用下面的命令,如dropsite-min-distance wood < 255
在修建完伐木场后,可以检查dropsite-min-distancedropsite-min-distance是个fact,用来检测从资源点到资源放置点的最少距离。如果说这个值比较大,那么我们的伐木场的效率就不算很高。

为什么农民建完伐木场不呆在伐木场旁边采木头,而是回去牧羊了呢?
这时候可以利用up-target-objects命令,强制农民走到伐木场处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; Task villagers to lumber-camp
(defrule
(current-age == dark-age)
(building-type-count lumber-camp > 0)
=>
(up-reset-search 1 1 1 1)
(up-reset-filters)
(up-find-local c: villager-m-lumberjack c: 240)
(up-find-local c: villager-f-lumberjack c: 240)
(up-modify-goal temporary-goal s:= sn-focus-player-number)
(up-modify-sn sn-focus-player-number c:= my-player-number)
(up-find-remote c: lumber-camp c: 1)
(up-target-objects 0 action-default -1 -1)
(up-modify-sn sn-focus-player-number g:= temporary-goal)
(chat-local-to-self "task villagers to lumbercamp")
(disable-self)
)

up-target-objects将被选择的up-find-local以普通模式action-normal(还可以选择action-patrol巡逻模式)指引到(direct against)up-find-remote
除此之外,第三四个参数分别为阵型(formation-line, formation-box, formation-stagger, formation-flank)和动作(stance-aggressive, stance-defensive, stance-stand-ground, stance-no-attack)

BTW,我在搜sn-intelligent-gathering时意外的发现了相反的问题

拉猪

这段代码来自于Bruteforce
电脑拉猪一般是用农民拉猪,升级织布机农民能够减少长距离拉猪农民死亡的可能性。

1
2
3
4
5
6
7
8
9
10
11
(defrule
(up-research-status c: ri-loom >= research-pending)
(or(unit-type-count-total villager >= 11)
(game-time > 275))
(strategic-number sn-enable-boar-hunting != 2)
(dropsite-min-distance live-boar != -1)
(dropsite-min-distance live-boar s:< sn-maximum-hunt-drop-distance)
=>
(set-strategic-number sn-enable-boar-hunting 2)
(set-strategic-number sn-maximum-hunt-drop-distance 32)
)

sn-enable-boar-hunting设为1是会杀鹿和猪,2则只杀猪。
首先当农民数大于等于11的时候,织布机升完了,此时如果猪距小于sn-maximum-hunt-drop-distance就设置sn-enable-boar-hunting为允许。注意特别判断值为-1,即未定义的情况,不然-1恒小于任何数会出错。
下面就开始拉第一头猪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(defrule
(strategic-number sn-enable-boar-hunting == 2)
;(dropsite-min-distance boar-hunting > 10)
(dropsite-min-distance live-boar != -1)
(dropsite-min-distance live-boar != 255)
;(dropsite-min-distance live-boar s:< sn-maximum-hunt-drop-distance)
(dropsite-min-distance live-boar < 32)
(unit-type-count villager-hunter == 0)
(up-timer-status 7 != timer-running)
=>
(set-strategic-number sn-minimum-number-hunters 1)
(set-strategic-number sn-minimum-boar-hunt-group-size 1)
(set-strategic-number sn-minimum-boar-lure-group-size 1)
;(up-chat-data-to-self "sn-maximum-hunt-drop-distance: %d" s: sn-maximum-hunt-drop-distance)
(chat-local-to-self "Begin luring boar")
(up-retask-gatherers food c: 1)
(up-request-hunters c: 1)
(enable-timer 7 5)
)

up-retask-gatherers重新指派给定数量的村民收集某种资源。
up-request-hunters尝试请求给定数量的猎人加入采集猪肉的队伍,不能保证到达给定的全部数量。
villager-hunter是一个在UserPatch之外的常数,表示猎人的数目。经过测试,牧羊人不能称作猎人。
下面两个命令容易混淆,sn-minimum-boar-hunt-group-size指的是达到多少个农民就可以开始猎杀野猪(此时猪可能已经被拉到TC下)。而sn-minimum-boar-lure-group-size指的是使用多少个农民拉猪,由于猪只同时对一个农民仇恨,所以一般设为1
sn-minimum-number-hunters用来强制至少有多少猎人,一般在杀猪时结合sn-minimum-boar-hunt-group-sizeup-request-hunters使用。
enable-timer 7 5这个是干什么用的呢?这是为了防止之前杀猪的农民挂了,可以重新拉猪
拉猪是拉猪,拉到TC下还要杀猪,下面的rule用来处理杀猪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defrule
(strategic-number sn-enable-boar-hunting == 2)
(dropsite-min-distance live-boar != -1)
(dropsite-min-distance live-boar < 5)
(or(unit-type-count 122 >= 1)
(unit-type-count 216 >= 1))
(unit-type-count villager-hunter < 6) ; 8
=>
(set-strategic-number sn-minimum-number-hunters 8)
(set-strategic-number sn-minimum-boar-hunt-group-size 8)
(set-strategic-number sn-minimum-boar-lure-group-size 8)
(chat-local-to-self "Request support hunters")
;(up-retask-gatherers food c: 8)
(up-request-hunters c: 8)
)

这里122指男猎人 ,216指女猎人,当猪离TC很近了(小于等于5),这时候就用猎人来杀猪。
下面的规则用来拉第二头猪。
经测试“Attempting to lure another boar”、“Injured villager found – search again.”、“Begin luring boar (2)”三条规则依次触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(defrule
(strategic-number sn-enable-boar-hunting == 2)
(dropsite-min-distance live-boar s:< sn-maximum-hunt-drop-distance)
(dropsite-min-distance boar-hunting < 10)
(up-remaining-boar-amount < 195)
(strategic-number sn-minimum-number-hunters > 1)
=>
(set-strategic-number sn-minimum-number-hunters 1)
(set-strategic-number sn-minimum-boar-hunt-group-size 1)
(set-strategic-number sn-minimum-boar-lure-group-size 1)
(chat-local-to-self "Attempting to lure another boar")
(up-reset-search 1 1 1 1)
(up-reset-filters)
(up-set-target-point gl-position-self-x)
(up-filter-distance c: -1 c: 10)
(up-find-local c: villager-class c: 1)
(set-goal gl-boar-lurer-search 1)
)

up-remaining-boar-amount检查当前猪所剩食物量。只有在另一猪可捕猎时,本数据才有效。否则数据会是65535(似乎65535等于-1,不知道为啥不设为0或者-1),以显示这是最后一头猪。Barbarian野蛮人ai似乎都没有用过这个fact。
因为第二头猪通常比较远,而且拉完第一个猪农民会有残血,所以这里检查当前农民的血量是否足够,不够的话会(up-jump-rule -1)回到上面的“Attempting to lure another boar”,重新搜索一个农民。

1
2
3
4
5
6
7
8
9
10
(defrule
(goal gl-boar-lurer-search 1)
(up-set-target-object search-local c: 0)
(up-object-data object-data-hitpoints < 40)
=>
(up-reset-search 0 1 0 0)
(up-find-local c: villager-class c: 1)
(up-jump-rule -1)
(chat-local-to-self "Injured villager found -- search again.")
)

up-object-data检查选定目标物件的特定信息,object-data-hitpoints常数表示生命值。

1
2
3
4
5
6
7
8
9
10
11
12
13
(defrule
(goal gl-boar-lurer-search 1)
(up-set-target-object search-local c: 0)
=>
(up-filter-distance c: -1 s: sn-maximum-hunt-drop-distance)
(set-strategic-number sn-focus-player-number 0)
(up-find-remote c: wild-boar c: 1)
(up-find-remote c: javelina c: 1)
(up-set-target-object search-remote c: 0)
(up-target-objects 0 action-default -1 -1)
(chat-local-to-self "Begin luring boar (2)")
(set-goal gl-boar-lurer-search 2)
)

sn-maximum-hunt-drop-distance设置电脑玩家捕猎时资源距离资源放置点的最大距离。
这里wild-boarjavelina都表示野猪。
下面的这个规则就很有意思了,为啥会有这个条件呢?
首先触发条件,主要包括3部分:

  1. (goal gl-boar-lurer-search 2)
    当”Begin luring boar (2)”对应规则被触发后即满足,也就是说它只发生在拉第二头猪
  2. dropsite-min-distance live-boar < 5
    表示当猪足够近的时候
  3. up-timer-status 7 != timer-running
    这个条件似乎正常情况下并不会满足
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(defrule
(strategic-number sn-enable-boar-hunting == 2)
(dropsite-min-distance live-boar != -1)
(dropsite-min-distance live-boar < 5)
(goal gl-boar-lurer-search 2)
(unit-type-count villager-hunter > 0)
(up-timer-status 7 != timer-running)
;(unit-type-count villager-hunter >= 8)
=>
(up-reset-search 1 1 1 1)
(up-reset-filters)
(up-set-target-point gl-position-self-x)
(up-filter-distance c: -1 c: 10)
(up-find-local c: villager-class c: 6)
(set-strategic-number sn-focus-player-number 0)
(up-find-remote c: wild-boar c: 1)
(up-find-remote c: javelina c: 1)
(up-target-objects 0 action-default -1 -1)
(enable-timer 7 10)
)

我们看看(up-set-target-point gl-position-self-x),通过检查前面的代码gl-position-self-x指的是TC的坐标。
所以说它的作用是当第二只猪很近的时候,隔一段时间选择6个农民走向它,这是在杀猪么?然而同样触发的是“Request support hunters”,并且可能会触发好几次。

赶鹿

下面这条规则是当没有猪时开始杀鹿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(defrule
(up-research-status c: ri-loom >= research-pending)
(unit-type-count-total villager >= 11)
(strategic-number sn-enable-boar-hunting == 2)
(unit-type-count-total villager >= 20)
(up-compare-goal gl-my-boars < 1)
(or(dropsite-min-distance live-boar == -1)
(dropsite-min-distance live-boar s:> sn-maximum-hunt-drop-distance))
=>
(set-strategic-number sn-enable-boar-hunting 1)
(set-strategic-number sn-minimum-number-hunters 0) ; 4
(set-strategic-number sn-minimum-boar-hunt-group-size 1)
(set-strategic-number sn-minimum-boar-lure-group-size 1)
(set-goal gl-boar-lurer-search 0)
;(up-retask-gatherers food c: 4)
(up-request-hunters c: 4)
(chat-local-to-self "No boar in range, allow deer hunting")
)

种田

空闲的农田

初期农田会空闲,这是因为农民要去杀猪和杀羊,如果不去杀动物,那么它们的尸体会慢慢腐烂,食物就浪费了,但是田一旦建好了就不会坏,所以可以先杀动物,再种田。
当城镇被攻击后农民会空闲。

升级时代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(defrule	
(game-time > 132)
(food-amount < 50)
(up-research-status c: feudal-age < research-pending) ; current-age < imp
(up-pending-objects c: villager < 1)
=>
(up-drop-resources sheep-food c: 5)
(up-drop-resources farm-food c: 5)
(up-drop-resources forage-food c: 5)
(up-drop-resources deer-food c: 20)
(up-drop-resources boar-food c: 7) ; 10
)
(defrule
(game-time > 132)
(food-amount < 50)
(food-amount >= 44)
(up-research-status c: feudal-age < research-pending)
(up-pending-objects c: villager < 1)
=>
(up-drop-resources sheep-food c: 2)
(up-drop-resources farm-food c: 2)
(up-drop-resources forage-food c: 2)
(up-drop-resources deer-food c: 20)
(up-drop-resources boar-food c: 2) ; 10
)
(defrule
(current-age == dark-age)
(strategic-number sn-enable-training-queue == 1)
(up-pending-objects c: villager < 2)
(food-amount < 50)
(timer-triggered 46)
=>
(up-drop-resources sheep-food c: 5)
(up-drop-resources farm-food c: 5)
(up-drop-resources forage-food c: 5)
(up-drop-resources deer-food c: 20)
(up-drop-resources boar-food c: 10)
)

这里up-drop-resources指的是让至少携带若干资源的村民上交资源,例如当初期要断农民或者差一点点封建的时候,就可以使用这个命令争取时间。
在黑暗时代农民只能携带10单位的资源,在后面升级了手推车等科技之后农民携带的资源会增多。此外如果让一个携带某种资源的农民去采集另外的资源,那么已采集到的资源会被丢弃,但是如果是去修建筑资源不会被丢弃。也可以通过up-garrison指令让农民进入TC交资源再出来,节省走路时间。

放兵营

为啥兵营会放在不前不后的地方?

侦察敌情

一般小马探路只会在家里转,这叫explorer。那么如何让它探对手家里呢?
up-send-scout加上position-位置常数可以做到这一点

强行建造

征服者的默认AI建造工事的时候,如果被攻击就会立刻取消建筑,事实上这是不完善的,比如有时候我们就想强行肛一个城堡。sn-percent-building-cancellation可以提供这样的功能

建造第二个TC

1
2
3
4
5
6
(defrule
(building-type-count-total town-center >= 1)
(building-type-count-total town-center < 2)
=>
(set-strategic-number sn-town-center-placement mining-camp)
)