用Python实现FGO自动战斗脚本

我家黑贞!

1. 背景

Fate/Grand Order(非的肝不过欧的)作为索尼为了拯救自己不倒闭而开发的面向月厨的骗氪养成抽卡爆肝游戏,居然没有像隔壁《阴阳师》的自动战斗系统(看看别人现在都自带脚本了)。毕竟是懒得肝,就不妨写一个脚本来肝算了,省时省力。

懒得写新的文章了orz新的版本在这个REPO里,主要实现了完全的自动刷,包括磕苹果OWO,文中的版本在文末。

2. 思路:界面识别

本来以为搞这个的难度不会比《阴阳师》的难太多QAQ,我真的是too young too simple啊QAQ

注意:由于我个人比较懒,所以之前文章提到的就懒得再提了QWQ
所以关于通过ADB对手机进行操作和OpenCV这个库的使用也不再赘述,不过可以参考我之前写的两篇文章:
《用Python实现阴阳师自动结界突破》
《用Python实现阴阳师自动抽卡》
(抽卡那篇文章基本概括了ADB的用法,和要用到的OpenCV的函数的用法了)

2.1 开始

我们这次要做的可不是什么抽卡脚本,而是一个战斗脚本,其实可以算是AI的初步了。虽然只是暴力算出造成最大伤害的方案orz。

我们在这里不考虑释放技能宝具暴击星这三样非常重要的东西。。。只单纯考虑克制抵抗和不同种类卡打出的伤害,目标就就是算出伤害最高的组合。

2.2 指令卡

战斗界面

要开始,我们首先要分析界面的组成。首先下面是一排指令卡,每张指令卡都有卡的种类(黄色框)和“克制”和“抵抗”的标记(黄色圈)之类的东西。那我们可以把每张指令卡视为一个对象,然后把它的特点抽象出来。我们可以知道每张卡都有一个坐标,一个类型(绿蓝红),一种状态(无/克制/抵抗),还有在点按是的顺序(1/2/3)和伤害系数(这个具体有一张表)。
所以我们可以这样做:

class Card:
    def __init__(self):
        self.crd = []  # the coordinate of the card (x, y)
        self.status = 0  # the status of the card "normal(0)" "restraint(1)" "resistance(2)"
        self.type = []  # type of the card "Quick(0)" "Arts(1)" "Buster(2)"
        self.priority = 0  # the priority of the card
        self.atk = 1  # set atk of the card

2.3 识别与匹配

2.3.1 坐标匹配

这个其实就没啥难度的了,无非就是调用OpenCV的库(之前的文章都提到过)用matchTemplate()识别图像然后返回坐标。不过我们倒是要写一个 “过滤系统”来把相近的坐标过滤掉,最后得到5张指令卡的坐标。这个简单来说,也可以用穷举法,设定一个范围,使这个范围里的坐标只保留一个。

2.3.2 标记匹配

另外一个重点就是把“克制”和“抵抗”的标记和其所在的卡匹配在一起。通过多组数据我们就可以观察到指令卡的坐标和标记的坐标的差值总在一个范围里面,简单的话就是设置一个范围如果标记的坐标在这个范围里则标记这张指令卡。

def mark_crd(card, mark): 
    note = []
    for i in range(len(card)):
        for j in range(len(mark)):
            for p in range_of_x:  # 两坐标x差值范围
                for q in range_of_y:  # 两坐标y差值范围
                    if (card[i][0] + p == mark[j][0]) and (card[i][1] - q == mark[j][1]):  # 如果在范围内
                        note.append(card[I])
    return note

2.3.3 种类匹配

然后我们还有给每张指令卡标记上卡的种类这个和前面匹配标记也是差不多的,就不再赘述了。

总的来说,这样就把每张卡(对象)的属性给匹配了起来,这样子就可以后面的程序调用了。

3. 思路:出卡顺序

整个算法的核心就是这一部分,计算出造成伤害最大的组合。

3.1 计算法则

就是这张图!

分析这张图,可以看到红卡放第一张时后面的卡都有伤害加成(废话),而其他颜色的卡则为原来的伤害,只是后面的卡伤害会略高而已。。。

最简单的办法就是暴力的把它写成一堆if-else语句QWQ

3.2 实现

我们在前面已经初始化了伤害(atk=1),我们只要给克制的伤害乘2,抵抗的乘0.5就好了,然后再加上我们那一大坨if-else语句

def rank_card():  # main algorithm
    # 设置 atk total rank这三个数组
    for i in range(5):
        cards[i].priority = 1
        for j in range(5):
            if j == I:
                continue
            else:
                cards[j].priority = 2
                for k in range(5):
                    if k == i or k == j:
                        continue
                    else:
                        if cards[i].type == 2:  # 第一张是buster的话
                            # 第二张的伤害 balabala
                           # 第三张的伤害 balabala
                        else:
                            if cards[i].type == 1:  # 第一张是arts的话
                                atk[0] = 1 * cards[i].atk
                            elif cards[i].type == 0:  # 第一张quick的话
                                atk[0] = 0.8 * cards[i].atk
                           # 第二张的伤害 balabala
                           # 第三张的伤害 balabala
                    if (sum(atk)) > total:
                        total = sum(atk)
                        rank[0] = I
                        rank[1] = j
                        rank[2] = k
    # 返回伤害最高的卡的号码
    return rank

反正人懒,给机器做多点事也没关系XD,接下来只要把坐标返回给主程序点按的可以了。

4. 思路:防封

听说FGO会封脚本,所以就特地加入了防封的机制。

其实方法很简单,加入随机的点按,和不同的间隔(等待时间)就可以了,点按每张卡有位置的变化,点每张卡之间有变化的间隔,和一些故意的“误触”应该就没问题,其实还可以加上一些长度不同的滑动也是可以的,简单来说就是一堆随机函数而已233

5. 思路:整合

简单来说就是把上面的一堆代码整合到一起就可以了

开始界面

识别到这个界面然后点按“Attack”

结束界面

识别到“与从者的羁绊”终止脚本

中间就是上面所提到的了。也即是一个不停的循环,直到“结束”界面才终止。有什么其他的就到时候再补充吧OWO

6. 总结

这应该是我搞过最大最复杂的一个项目了,也是第一次接触到一点OOP。然而这个项目还是偏实用性,毕竟没有什么高端的,或者更高效率的算法,这也应该是以后要改进的地方。

然后是惯例,代码在Github上面QWQ
还有Bilibili上的演示视频XD

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容