帝国时代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 | (defrule | 
一个defrule可以看做地图编辑器里面的一个触发项。下面的fact/action可以包含若干个条件/动作。
rule一旦被定义,系统就会不断轮询检查是否符合facts所描述的条件,如果符合,就执行action语句。脚本不会像C++一样只采用最匹配的规则,如下面两个规则都是适用的,所以我们细化战略时,必须同时写真条件和假条件。
| 1 | (defrule | 
对于一些只想执行一次的语句就可以通过最末尾的(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 | (defrule | 
需要再次强调的是在这个规则之外,本次匹配得到的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-detected和acknowledge-taunt这两个可以实现由用户输入某个数字taunt,然后输出相应值的功能。
补人口
默认1.5倍时间下,TC造一个农民大概在13秒左右,一个农民造房子大概在15秒左右,所以当人口差两个的时候就需要造房子了
| 1 | (defrule | 
can-build会检查该building是否符合当前的文明、科技树以及资源量(减去escrow预留的资源)。不过即使不加can-build,电脑也不会在文明、科技、资源量不满足的情况下造,而是作为一次失败的build尝试。一旦一次build失败了,此趟执行的所有build x都不会被运行了,即使后来条件全部满足。
类似的还有can-train和can-research,不过这两个还要注意建筑物在研究科技/造兵时被占用的问题。
如果不幸卡农民了,可以升级织布机
| 1 | (defrule | 
城镇规模(TSA)进攻
一般的进攻有(attack-now)语句,该语句会造成一队士兵以编队形式过来进攻,不过有些问题,例如在编队行进过程中受到进攻也不还手。由于attack-now是一队一队地出发的,所以可以通过改小sn-percent-attack-soldiers然后多次attack-now实现多波的、相互呼应的进攻。
TSA即Town Size Attack,相对于(attack-now)的直来直去,TSA进攻更分散、更操作性一点。它通过增加城镇的面积,这样己方就会对城镇面积内的敌人进行攻击。
注意要区分TSA和日常随着经济发展的城镇增长,城镇增长相对TSA还要额外扩大sn-camp-max-distance和sn-mill-max-distance用以获得更多资源。
为了测试,我们可以做一个场景地图。
| 1 | (defrule | 
sn-attack-intelligence表示智能进攻系统。该系统尝试在攻击时躲避敌人单位,尝试从多角度进攻。配合sn-attack-coordination设为2能够实现多线作战效果。sn-disable-attack-groups会禁用自动进攻编组,TSA必须不禁用。类似的选项还有sn-disable-defend-groups。
结果调试发现,首先所有的兵力沿着城镇方向散开,直到有一个军事单位发现敌人。当战斗单位还存在的时候TSA农民是不怎么参与战斗的,即使设置了
| 1 | (set-strategic-number sn-allow-civilian-defense 3) | 
但如果放到一个纯农民的地图里面,农民就都会上去搏。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-type2和sn-special-attack-type3
与之对应的是sn-gold-defend-priority等一系列防守重要性等级策略,从0到7防守力度越来越大。
sn-number-attack-groups、sn-minimum-attack-group-size、sn-maximum-attack-group-size及sn-attack-group-size-randomness是一套指令。
修建围墙
围家是帝国时代做经济的一个军事支撑,如果在前方给敌人足够压力可以借助TC和军事建筑,如果想直城或直帝则需要木头墙+石墙围一道。
围墙建造第一步是enable-wall-placement这个命令,这个命令会通知将来的建筑离未来的围墙至少距离1格(tile),因此最好在初始化中就做好。enable-wall-placement带一个参数perimeter,设为1就是小围,2是大围。build-wall是建造围墙命令
| 1 | (defrule | 
小围技术
小围技术包含两个方面,一是如何让房子等技术修成圈,另一个是如何让农民在第一个建筑没修完时赶快桥第二个建筑
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-count和 building-type-count-total,前一个只计算现存的建筑数,后一个计算包括正在队列(正在建造)中的建筑数。注意摧毁了的建筑不会增加到这个里面。unit-type-count和 unit-type-count-total的区别也在此,不过unit-不仅对建筑用,也可以对军队等单位用。
| 1 | (defrule | 
第二步是发现塔爆
| 1 | (defrule | 
首先需要经过第一步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 | (defrule | 
下面讨论ai防御塔爆的行为,这在Defend Truch部分有定义。这些防御行为的产生条件是封建时代且GOAL-DEF-TRUCH为1,即防御塔爆状态
- 首先取消 - GOAL-FAST-ATTACK计划
- 如果发现有石矿,造采石场 
- 如果能造塔,并且已造塔数小于4,那么就造塔 
 注意下面的造塔命令,能够粗略地控制造塔范围- 1 
 2
 3- (set-strategic-number sn-maximum-town-size 12) 
 (build watch-tower-line)
 (set-strategic-number sn-maximum-town-size 20)
- 如果每个敌人都没到城堡,如果能造塔,并且已造塔数小于8,那么反塔爆别人 
 这里用了- build-forward命令,即前置造建筑物。
- 当自己到了城堡之后,如果塔还没清掉(估计是石墙搞了一波农民搏不掉),那么就造BK(siege-workshop)。下面不用多说,造攻城车( - battering-ram-line)。然后就等着攻城车自己清理吧。
最后是防御塔爆的结束条件:
- 城堡时代、农民数大于50
- 时间超过1020,塔全部被清理,农民数大于28
前置塔攻就不详细讨论了,下面的是在成功防御前置塔攻之后的行为。
| 1 | (defrule | 
防守TC暴
TC暴(Douche)是种比较恶心的塔爆,玩家在黑暗自爆自己的TC,然后农民跑到对家的基地附近建TC,然后TC对射和对方耗。BOOM II对TC暴有着绝妙的应对方法,那就是不打TC暴、、、
| 1 | (defrule | 
cc-players-unit-type-count是一个作弊命令,相对于不加cc-的命令,它获取玩家某一种类建筑的数目,不管自己能不能看到。这里判断如果在黑暗时代TC的数量变成0,那么对方很可能就是TC爆了。
将GOAL-DEF-TRUCH改成3表示当前是TC爆。
| 1 | (defrule | 
城快进攻
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 | (defconst gl-slot0 100) | 
以上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 | (defconst gl-data 101) | 
但是UP还可以为我们做更多,我们可以获得某些玩家(由every/any通配符指定)事实集合中的最大/小值以及和,如
| 1 | (defconst gl-my-civ-sum 101) | 
这个可以输出敌人的农民人口与自己的农民人口的比值,可惜是整数,这里因为我们没有探测到敌人的家,所以gl-ene-civ-sum值是0或-1,结果是0。
成本函数
up-reset-cost-data将四种资源成本全部置零,例如
| 1 | (defconst gl-military-cost-food 111) | 
当第一个参数为1的时候,将所有的成本设置为0,真是够奇葩的,为啥不搞个memset一样的呢?
为什么第二个参数是gl-military-cost-food呢?因为可以把gl-military-cost-food到gl-military-cost-gold看成一个四个元素的数组,而gl-military-cost-food相当于传入了一个头指针。
在使用up-reset-cost-data,其他的成本函数都是对当前选定的“数组”进行操作了。
下面的代码往总成本上加了两份军事成本(每份成本包含一个骑士)
| 1 | (defrule | 
up-add-object-cost第一个参数表示单位id,第二个参数表示数量,相对up-setup-cost-data还有科技研发成本up-add-research-costup-setup-cost-data第一个参数表示一个goal,第二个参数表示份数up-get-cost-delta计算当前资源与建造成本的差值,负的表示不够。这个值与escrow无关。
| 1 | (defrule | 
坐标代数
UP提供的坐标代数能实现高级AI的精细控制,如此篇帖子。
为了表示坐标,首先定义一个点对。类似于上面up-reset-cost-data操纵资源的方式,点对的地址应当是连续的(形成一个二维数组),如下面的100和101,这样gl-point-x可以看做指向该点对的指针。
| 1 | (defconst gl-point-x 100) | 
我们可以将gl-point-x点对设为目标点,供如up-build等命令使用
| 1 | (up-set-target-point gl-point-x) | 
- up-lerp-percent和- up-lerp-tiles
 这个用来计算坐标偏移,将第一个点对作为基点,第二个点对作为位移值进行偏移。- up-lerp-tiles会偏移固定的格数,- up-lerp-precent则按百分比。
- up-cross-tiles
 我写了段代码测试了一下,并没有看出来有啥用,应该结果保存在第一个点
流程控制
在同一个文件里面rules的执行可以看成是从上至下的,因此可以利用up-jump-rule来实现选择或者循环结构,但注意#load块可能影响位置。
直接寻的系统
find
过滤器
过滤器由up-filter-distance、up-filter-exclude、up-filter-garrison、up-filter-include、up-filter-range构成
注意以下命令中-1表示忽略此过滤条件
- 选择目标点周围10个内单位:(up-filter-distance c: -1 c: 10),其中两个参数分别为最小距离和最大距离
- 派出具有某个编号的单位:(up-filter-exclude cmdid-trade -1 -1 -1),其中四个参数分别表示命令编号、行动编号、执行编号和类别编号。例如(up-filter-exclude -1 actionid-explore orderid-relic warship-class)
- 选择驻扎了至少5个单位的建筑:(up-filter-garrison c: 5 c: -1),两个参数同样是最大值和最小值
- up-filter-include这个和exclude是相似的,不过第四个参数改为了是否在主大陆上
一般重置需要同时重置search结果和过滤器,即
| 1 | (up-reset-search 1 1 1 1) | 
使用直接寻的系统找到的对象可以利用up-set-target-object设为目标,然后通过up-object-data等语句对目标进行操作,下面的语句摘自拉猪部分
| 1 | (up-set-target-object search-local c: 0) | 
人员控制
包括驻扎、巡逻、删除冗余人员
- 驻扎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 | (defconst sn-castle-age-strategy 182) | 
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 | #load-if-defined MONGOL-CIV | 
UP-POCKET-POSITION指的是玩家是否坐中,那1v1的时候玩家始终坐中,于是就用马爆策略。
造房子
| 1 | #load-if-not-defined CHINESE-CIV | 
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-normal、place-forward、place-control、place-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-size在place-forward、place-control这两个选项时。
下面是另外一个造房子的条件,在开局的时候并没有被触发。由于这两个行为是全部一样的,所以其实我感觉做简单一点,这两个可以合为一个。
| 1 | (defrule | 
如果房子超过一个了,开局就不会卡农民了,这样将造房子的农民减少到一个。
| 1 | (defrule | 
注意开局的时候我们一般是造两个房子,一个分配2农民,一个分配1农民,这样会导致超过sn-cap-civilian-builders的默认值2,所以最好在一开始扩大一下,例如设为25。最重要的是sn-enable-new-building-system必须设为1,否则村民只能同时建造一个建筑。
农民调配
| 1 | (defrule | 
以上部分是早期探索阶段,阿拉伯地图的civilian-exploration-time为60。根据调试,实际上更多运行的是下面这个rule,因为通常农民很快就能找到一个羊群。这时候设置农民探索者最大数量为0,采集者最大数量为100,并且所有的农民都去采集。探索者的队伍容量为1人。
这里注意与sn-number-explore-groups和sn-total-number-explorers区别,前者指的是陆地上的探索者数量,后者指的是村民探索者和军队探索者的总和。但是当有军队进行探索的时候似乎不会派村民进行较多的侦查活动。
| 1 | (defrule | 
再往后,根据战术的不同,资源调配的方案有很大不同。
sn设置
下面是strategy number设置,这里分了几部分是因为rule里面action的条数限制
| 1 | (defrule | 
sn-camp-max-distance表示伐木场和矿场和城镇中心最远的距离,不可能说我们的一个伐木放到敌人家门口的,对应有sn-mill-max-distance。BF将这两个值分别设为16和32sn-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 | (defrule | 
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 | (defrule | 
sn-preferred-trade-distance偏好贸易距离,由于贸易越长越好,所以设为255.
| 1 | (defrule | 
其中LAND-NOMAD为陆游,NOMAD为游牧。
下面这段造农民的代码来自Krush部分。
我们实际上看到在主per文件里面也有相应的造农民代码,但这个是互相不冲突的,因为条件写得很严格。
| 1 | (defrule | 
牵羊
小马斥候有时候可能看到羊,但不会多走几步取得这头羊控制权,并将其派回TC,因此需要进行牵羊。
放伐木场、磨坊
伐木一般要晚于磨坊。虽然农民一般先狩猎,但当羊比较难找时草料丛就起作用了,还有一点是为了种田的方便,最好让农民先把TC旁边的树木全部伐倒。resource-found语句中food参数特指草料丛,wood参数特指树林(而不是单棵的树)。但有时候(如存档aitest_farwood)即使找到了森林,伐木场依然可能建在单棵树的旁边,解决方案可以是延缓伐木场的建造时间,例如在第一个房子建成后。注意这条命令是一次性的,即使后面树木全部被挖完了,也依然是ture。如何检测是否还有树呢?可以使用下面的命令,如dropsite-min-distance wood < 255
在修建完伐木场后,可以检查dropsite-min-distance,dropsite-min-distance是个fact,用来检测从资源点到资源放置点的最少距离。如果说这个值比较大,那么我们的伐木场的效率就不算很高。
为什么农民建完伐木场不呆在伐木场旁边采木头,而是回去牧羊了呢?
这时候可以利用up-target-objects命令,强制农民走到伐木场处
| 1 | ; Task villagers to lumber-camp | 
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 | (defrule | 
sn-enable-boar-hunting设为1是会杀鹿和猪,2则只杀猪。
首先当农民数大于等于11的时候,织布机升完了,此时如果猪距小于sn-maximum-hunt-drop-distance就设置sn-enable-boar-hunting为允许。注意特别判断值为-1,即未定义的情况,不然-1恒小于任何数会出错。
下面就开始拉第一头猪。
| 1 | (defrule | 
up-retask-gatherers重新指派给定数量的村民收集某种资源。up-request-hunters尝试请求给定数量的猎人加入采集猪肉的队伍,不能保证到达给定的全部数量。villager-hunter是一个在UserPatch之外的常数,表示猎人的数目。经过测试,牧羊人不能称作猎人。
下面两个命令容易混淆,sn-minimum-boar-hunt-group-size指的是达到多少个农民就可以开始猎杀野猪(此时猪可能已经被拉到TC下)。而sn-minimum-boar-lure-group-size指的是使用多少个农民拉猪,由于猪只同时对一个农民仇恨,所以一般设为1sn-minimum-number-hunters用来强制至少有多少猎人,一般在杀猪时结合sn-minimum-boar-hunt-group-size和up-request-hunters使用。enable-timer 7 5这个是干什么用的呢?这是为了防止之前杀猪的农民挂了,可以重新拉猪
拉猪是拉猪,拉到TC下还要杀猪,下面的rule用来处理杀猪。
| 1 | (defrule | 
这里122指男猎人 ,216指女猎人,当猪离TC很近了(小于等于5),这时候就用猎人来杀猪。
下面的规则用来拉第二头猪。
经测试“Attempting to lure another boar”、“Injured villager found – search again.”、“Begin luring boar (2)”三条规则依次触发。
| 1 | (defrule | 
up-remaining-boar-amount检查当前猪所剩食物量。只有在另一猪可捕猎时,本数据才有效。否则数据会是65535(似乎65535等于-1,不知道为啥不设为0或者-1),以显示这是最后一头猪。Barbarian野蛮人ai似乎都没有用过这个fact。
因为第二头猪通常比较远,而且拉完第一个猪农民会有残血,所以这里检查当前农民的血量是否足够,不够的话会(up-jump-rule -1)回到上面的“Attempting to lure another boar”,重新搜索一个农民。
| 1 | (defrule | 
up-object-data检查选定目标物件的特定信息,object-data-hitpoints常数表示生命值。
| 1 | (defrule | 
sn-maximum-hunt-drop-distance设置电脑玩家捕猎时资源距离资源放置点的最大距离。
这里wild-boar和javelina都表示野猪。
下面的这个规则就很有意思了,为啥会有这个条件呢?
首先触发条件,主要包括3部分:
- (goal gl-boar-lurer-search 2)
 当”Begin luring boar (2)”对应规则被触发后即满足,也就是说它只发生在拉第二头猪
- dropsite-min-distance live-boar < 5
 表示当猪足够近的时候
- up-timer-status 7 != timer-running
 这个条件似乎正常情况下并不会满足
| 1 | (defrule | 
我们看看(up-set-target-point gl-position-self-x),通过检查前面的代码gl-position-self-x指的是TC的坐标。
所以说它的作用是当第二只猪很近的时候,隔一段时间选择6个农民走向它,这是在杀猪么?然而同样触发的是“Request support hunters”,并且可能会触发好几次。
赶鹿
下面这条规则是当没有猪时开始杀鹿
| 1 | (defrule | 
种田
空闲的农田
初期农田会空闲,这是因为农民要去杀猪和杀羊,如果不去杀动物,那么它们的尸体会慢慢腐烂,食物就浪费了,但是田一旦建好了就不会坏,所以可以先杀动物,再种田。
当城镇被攻击后农民会空闲。
升级时代
| 1 | (defrule | 
这里up-drop-resources指的是让至少携带若干资源的村民上交资源,例如当初期要断农民或者差一点点封建的时候,就可以使用这个命令争取时间。
在黑暗时代农民只能携带10单位的资源,在后面升级了手推车等科技之后农民携带的资源会增多。此外如果让一个携带某种资源的农民去采集另外的资源,那么已采集到的资源会被丢弃,但是如果是去修建筑资源不会被丢弃。也可以通过up-garrison指令让农民进入TC交资源再出来,节省走路时间。
放兵营
为啥兵营会放在不前不后的地方?
侦察敌情
一般小马探路只会在家里转,这叫explorer。那么如何让它探对手家里呢?up-send-scout加上position-位置常数可以做到这一点
强行建造
征服者的默认AI建造工事的时候,如果被攻击就会立刻取消建筑,事实上这是不完善的,比如有时候我们就想强行肛一个城堡。sn-percent-building-cancellation可以提供这样的功能
建造第二个TC
| 1 | (defrule | 
 
          