帝国时代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-cost
up-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 |