# DDTank
A package used for writing ddtank game scripts.
Contact the author: jugking6688@gmail.com
Video tutorial in bilibili: https://space.bilibili.com/3493127383943735
Examples are as follows:
```python
from ddtank import Status, get_game
class ScriptStatus(Status):
def task(self):
while True:
self.press('B')
if __name__ == '__main__':
handle_list = get_game()
handle = handle_list[0]
my_status = ScriptStatus(handle)
my_status.start()
my_status.stop()
```
---
# DDTank 非正式文档
#### Document for ddtank package
## 写在前面
> 本软件包的更新、规范全凭作者个人喜好而变动,请慎重使用!
>
> 作者邮箱: jugking6688@gmail.com
>
> B站视频系列: https://space.bilibili.com/3493127383943735
>
> QQ交流群:285669805
---
## 版本更新说明
* **version 1.1**
引入了图形化界面,即脚本执行器**ddtanker**。
```python
from ddtank import DDTanker
ddtanker = DDTanker()
```
ddtanker有找色、截图、地图识别等功能,大大提高脚本编写的效率。
注意,如果你想编写一个ddtanker能够识别、执行的ddtank脚本,你需要将子类命名为ScriptStatus,
然后将.py文件放在./script文件夹中。
```python
from ddtank import Status
class ScriptStatus(Status):
def task(self):
self.click('rfind', pos=(520, 57), rgb=(6, 104, 210))
self.click('rfind', pos=(743, 141), rgb=(247, 75, 61))
self.click('rfind', pos=(430, 278), rgb=(149, 86, 92))
self.sleep(100)
self.click('rfind', pos=(241, 373), rgb=(83, 244, 52))
```
一个能供ddtanker识别、执行的ddtank脚本,它的作用是:
假设人物位于大厅,他将会进入农场,然后扭蛋一次。
---
## 1. 获取游戏窗口信息 get_game()
```python
from ddtank import get_game
hwnds = get_game()
```
上面的代码演示了如何获取当前已经运行了的,所有的**弹弹堂**游戏窗口的句柄,并保存到了列表hwnds中。
get_game()的本质就是遍历所有1000*600的,类名为'MacromediaFlashPlayerActiveX'的子窗口。
此方法绝对适用于中国弹弹堂官服,其他弹弹堂版本,如弹弹堂经典服、中国台湾淘米弹弹堂、越南、
马来西亚、巴西服务器弹弹堂不保证能够识别,请根据实际情况修改代码参数。
如果你想要同时获取游戏窗口的标题文本,那么设置get_title参数为True
```python
from ddtank import get_game
hwnds, titles = get_game(get_title=True)
```
## 2.实例化角色对象 Status()
在弹弹堂中,每一个游戏窗口对应一个游戏角色。因此,每一个实例化的Status对象也对应每一个角色。
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
```
如果你之前也获取了标题文本列表,那么可以在实例化Status时指定第二个参数title。
## 3.找图与找点 find()
存在以下场景:
* 获取某点的像素值(RGB)
* 判断某个(多个)点像素值是否符合条件
* 重复判断直到某个点像素符合了条件
* 获取某图片的位置
### 3.1 获取某点的像素值(RGB)
在下面的例子中,我们获取了游戏图像中(200, 200)处的像素值并储存在了变量pixel中。
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
pixel = my_status.find('find', pos=(200, 200))
# 或者,你可以直接 find(pos=(200, 200)
```
获取像素值使用了win32gui的GetPixel方法,而不是先截图然后访问图片数组。
### 3.2 判断某个(多个)点像素值是否符合条件
在下面的例子中,我们判断了三个点是否符合条件,如果其中某个符合了条件,
那么就会返回它的**参数名称**(例如,cdt2)。
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
rst = my_status.find('mfind',
cdt1=((100, 100), (12, 23, 34)),
cdt2=((200, 200), (34, 23, 12)),
cdt3=((300, 300), (21, 32, 43)), )
```
'mfind'也可以简写为'mf'或者'm',取决于你的心情。
每一种method都有许多简写方式。
### 3.3 重复判断直到某个点像素符合了条件
在下面的例子中,我们重复判断游戏图像中(200, 200)处的像素值是否为(1, 2, 3),
在实际应用中,这种情况经常适用于进入某场景前的等待。
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
my_status.find('rfind', pos=(200, 200), rgb=(1, 2, 3))
```
指定method为'rfind'时,还可以设置两个参数: 'el'和'period'。
'el'用于指定在找某点像素值不符合条件时,执行的方法。
'period'用于指定每次循环之间的等待时间,默认为100毫秒,这是为了避免资源占用过大。
### 3.4 获取某图片的位置
用于寻找的图片储存在self.image_path内(默认值为./image),其格式必须为png。
在下面的例子中,我们有图片路径为./image/temp.png,我们在游戏图像中查找它的位置:
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
center_point_pos = my_status.find('find_image', img='temp')
```
返回值center_point_pos为图片的中心点位置。
指定method为'rfind'时,还可以设置一个参数: 'part'。
'part'用于指定寻找图片的范围。
'part'需要传递一个元组(x, y, w, h):
分别为左上角x坐标、左上角y坐标、宽度、高度,默认为(0, 0, 1000, 600)即整个窗口
在有的时候,整个游戏图像中有多个相同(或高度相似)的图片,
你可能希望仅仅在某一小片的区域内寻找图片。
## 4.模拟点击 click()
存在以下场景:
* 点击某处
* 某个点像素值符合条件才点击此处
* 重复判断直到某个点像素符合了条件,然后点击此处
* 获取某图片的位置,然后点击图片位置
* 重复寻找某图片,直到图片出现,然后点击图片位置
一般来说,click方法和find方法的method及参数都是对应的。
下面的代码基本完整展示了click的使用方法。如果你学会了find的使用,那么理解click将会是顺其自然的。
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
my_status.click(pos=(200, 200))
my_status.click('find', pos=(200, 200), rgb=(255, 255, 255), el=lambda: print(my_status.find(pos=(200, 200))), period=1000)
my_status.click('rfind', pos=(200, 200), rgb=(255, 255, 255))
my_status.click('find_image', img='temp', part=(0, 300, 500, 300))
```
## 5.模拟按键 press()
下面的一个例子展示了press方法的使用:
```python
from ddtank import get_game, Status
hwnds = get_game()
hwnd = hwnds[0]
my_status = Status(hwnd)
key_series = ('B', 1000, 'ESC', ('M', 1000))
my_status.press(key_series)
```
在上面的例子里,我们先模拟按下了B键,等待1秒(1000毫秒)后按下ESC键,然后持续按下M键1秒。
## 6.读取地图 read()
识别小地图,来获取各种信息。
```
风力: {self.wind}
角度: {self.angle}
小地图界限: {self.map_pos}
白框位置: {self.box_pos}
白框宽度: {self.box_width}
蓝点: {self.blues}
三角: {self.cur_pos}
红点: {self.reds}
光圈: {self.circle}
```
read()方法有一个参数为is_circle,默认为False,代表是否进行光圈识别。
在副本中,我们识别当前出手的角色一般依赖三角,而在竞技中,我们依靠闪烁的光圈来定位。
对于风力和角度的识别,我们使用了深度学习的方法来识别数字,如果你玩的弹弹堂版本不是官服,
那么其识别的准确率不理想。如果你想要为你所在的服务器来训练一个独特的识别模型,你可以为我们提供数据集。
## 7.发射炮弹 shot()
不好用,以后改进。
综合模拟发射炮弹操作
注意,使用此方法前需要先执行self.read()方法,否则信息得不到及时更新
:param shot_angle: 发射的角度,若传递一个元组则执行变角操作
:param shot_strength: 发射的力度
:param shot_item: 使用的道具按键,多个道具之间用','分隔
:return: 无返回值
## 8.角色移动 move()
---
## 关于脚本的编写
我们建议您继承父类Status,然后重写task()方法,将脚本内容写在此方法内,
通过YourStatus.start()方法执行脚本,此方法会开辟一个子线程来执行脚本。并且,
你可以通过YourStatus.stop()方法来强制停止脚本的运行。