Evonyのレイドに自動参加する(Windows)
はじめに
タイトルの通り、スマホゲームのEvonyをエミュレータ環境を使用して自動的にレイドに参加するプログラムを作りました。
一応ソースを載せますが、細かいことを考えずに作っているため苦情などはご遠慮いただけると助かります。自作なので環境やその他要因によってうまく動かないケースも考えられますのでご注意ください。ついでにソースも汚いので、なんとなく参考になれば・・・程度で。
必要なもの
・BlueStack5 (Androidエミュレータ)
・Visual Studio 2022
・Tesseract-OCR (各種値を画面スナップショットから文字認識で取得するために必要です)
・OpenCvSharp4(画像関係ライブラリ)
・AdvancedSharpAdbClient (C#でadbを操作するライブラリ)
エミュレーター環境
解像度は540×960 画素密度は160DPI での動作を想定しています。
解像度を変えるとそれ用にボタンなどの画像を用意する必要があるので複数対応していません。
HD-Adb.exeをコピーしてAdb.exeとして保存する
一応コマンドがadb.exeでないと不安なので別名コピーで用意します。
copy "C:\Program Files\BlueStacks_nxt\HD-Adb.exe" "C:\Program Files\BlueStacks_nxt\Adb.exe"
プログラム
Form1.cs
画面です。フォームとか使ったことないので適当にもほどがありますが表示できているのでご勘弁を。
public partial class Form1 : Form
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
List<UserProcessInfo> processList = new List<UserProcessInfo>();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
List<string[]> dataList = ReadCsv();
// カラム数を指定
dataGridView1.ColumnCount = 0;
DataGridViewTextBoxColumn column1 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column2 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column3 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column4 = new DataGridViewTextBoxColumn();
DataGridViewCheckBoxColumn column5 = new DataGridViewCheckBoxColumn();
DataGridViewCheckBoxColumn column6 = new DataGridViewCheckBoxColumn();
DataGridViewCheckBoxColumn column7 = new DataGridViewCheckBoxColumn();
DataGridViewCheckBoxColumn column8 = new DataGridViewCheckBoxColumn();
DataGridViewTextBoxColumn column9 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column10 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column11 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column12 = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn column13 = new DataGridViewTextBoxColumn();
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
column1.ReadOnly = true;
column2.ReadOnly = true;
column9.ReadOnly = true;
column10.ReadOnly = true;
column11.ReadOnly = true;
column12.ReadOnly = true;
column13.ReadOnly = true;
dataGridView1.Columns.Add(column1);
dataGridView1.Columns.Add(column2);
dataGridView1.Columns.Add(column3);
dataGridView1.Columns.Add(column4);
dataGridView1.Columns.Add(column5);
dataGridView1.Columns.Add(column6);
dataGridView1.Columns.Add(column7);
dataGridView1.Columns.Add(column8);
dataGridView1.Columns.Add(column9);
dataGridView1.Columns.Add(column10);
dataGridView1.Columns.Add(column11);
dataGridView1.Columns.Add(column12);
dataGridView1.Columns.Add(column13);
// カラム名を指定
dataGridView1.Columns[0].HeaderText = "名前";
dataGridView1.Columns[1].HeaderText = "PORT";
dataGridView1.Columns[2].HeaderText = "部隊数";
dataGridView1.Columns[3].HeaderText = "レイドパワー";
dataGridView1.Columns[4].HeaderText = "RSS-石材";
dataGridView1.Columns[5].HeaderText = "RSS-木材";
dataGridView1.Columns[6].HeaderText = "RSS-鉄鉱石";
dataGridView1.Columns[7].HeaderText = "RSS-食料";
dataGridView1.Columns[8].HeaderText = "レイド";
dataGridView1.Columns[9].HeaderText = "リソース";
dataGridView1.Columns[10].HeaderText = "石板";
dataGridView1.Columns[11].HeaderText = "アイテム";
dataGridView1.Columns[12].HeaderText = "バイキング";
dataGridView1.Columns[8].DefaultCellStyle.BackColor = Color.LightGray;
dataGridView1.Columns[9].DefaultCellStyle.BackColor = Color.LightGray;
dataGridView1.Columns[10].DefaultCellStyle.BackColor = Color.LightGray;
dataGridView1.Columns[11].DefaultCellStyle.BackColor = Color.LightGray;
dataGridView1.Columns[12].DefaultCellStyle.BackColor = Color.LightGray;
// データを追加
foreach (string[] line in dataList)
{
dataGridView1.Rows.Add(line[0], line[1], line[2], "70.0", true, true, true, false, "停止中", "停止中", "停止中", "停止中", "停止中");
}
// タイマーの間隔(ミリ秒)
System.Timers.Timer timer = new System.Timers.Timer(1000);
// タイマーの処理
timer.Elapsed += (sender, e) =>
{
List<UserProcessInfo> work = new List<UserProcessInfo>();
work.AddRange(processList);
foreach (UserProcessInfo info in work)
{
if (info.task.IsCompleted || info.task.IsCanceled)
{
dataGridView1[info.col, info.row].Style.BackColor = Color.LightGray;
if (info.task.IsCanceled)
{
dataGridView1[info.col, info.row].Value = "取り消し";
}
else if (info.task.IsCompleted)
{
dataGridView1[info.col, info.row].Value = "終了";
}
processList.Remove(info);
}
}
};
// タイマーを開始する
timer.Start();
}
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
System.Diagnostics.Debug.Print($"Col, Row = '{e.ColumnIndex}', '{ e.RowIndex}'");
DataGridView dgv = (DataGridView)sender;
if (dgv.CurrentCell.Value.Equals("停止中") || dgv.CurrentCell.Value.Equals("取り消し") || dgv.CurrentCell.Value.Equals("終了"))
{
dgv.CurrentCell.Style.BackColor = Color.LightBlue;
dgv.CurrentCell.Value = "実行中";
var temp = dgv[1, e.RowIndex].Value;
string port = temp != null ? temp.ToString() : "5555";
executeProcess(port, e.ColumnIndex, e.RowIndex, dgv[2, e.RowIndex].Value.ToString());
}
else
{
killProcess(e.ColumnIndex, e.RowIndex);
}
}
private void killProcess(int col, int row)
{
foreach (UserProcessInfo info in processList)
{
if (info.col == col && info.row == row)
{
logger.Info($"停止処理開始しました。PORT='{info.port}', RowIndex='{info.row}', ColumnIndex='{info.col}'");
if (info.tokenSource != null)
{
info.tokenSource.Cancel();
}
dataGridView1[info.col, info.row].Style.BackColor = Color.LightGray;
dataGridView1[info.col, info.row].Value = "停止中";
logger.Info($"停止処理終了しました。PORT='{info.port}', RowIndex='{info.row}', ColumnIndex='{info.col}'");
processList.Remove(info);
break;
}
}
}
private void executeProcess(string port, int col, int row, string? max)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task task = null;
// レイド
if (col == 8)
{
logger.Info("レイド開始しました。。PORT={0}", port);
string[] raidParam = new string[2] { port, dataGridView1[3, row].Value.ToString() };
ProgramAutoJoinRaid autoPlay = new ProgramAutoJoinRaid();
task = Task.Run(() => autoPlay.execute(token, raidParam));
}
// リソース
else if (col == 9)
{
logger.Info("リソース開始しました。。PORT={0}", port);
string[] raidParam = new string[2] { port, max };
ProgramAutoResource autoPlay = new ProgramAutoResource();
bool[] param = new bool[] { (bool)dataGridView1[4, row].Value, (bool)dataGridView1[5, row].Value, (bool)dataGridView1[6, row].Value, (bool)dataGridView1[7, row].Value };
task = Task.Run(() => autoPlay.execute(token, raidParam, param));
}
// 石板
else if (col == 10)
{
logger.Info("石板開始しました。。PORT={0}", port);
string[] raidParam = new string[1] { port };
ProgramSekiban autoPlay = new ProgramSekiban();
task = Task.Run(() => autoPlay.execute(token, raidParam));
}
// アイテム
else if (col == 11)
{
logger.Info("アイテム開始しました。。PORT={0}", port);
string[] raidParam = new string[1] { port };
ProgramAutoOpenItem autoPlay = new ProgramAutoOpenItem();
task = Task.Run(() => autoPlay.execute(token, raidParam));
}
// ヴァイキング
else if (col == 12)
{
logger.Info("ヴァイキング開始しました。。PORT={0}", port);
string[] raidParam = new string[2] { port, dataGridView1[3, row].Value.ToString() };
ProgramAutoViking autoPlay = new ProgramAutoViking();
task = Task.Run(() => autoPlay.execute(token, raidParam));
}
else
{
return;
}
UserProcessInfo info = new UserProcessInfo();
info.tokenSource = tokenSource;
info.task = task;
info.col = col;
info.row = row;
info.port = port;
processList.Add(info);
}
private List<string[]> ReadCsv()
{
List<string[]> lists = new List<string[]>();
using (StreamReader sr = new StreamReader(@".\targetlist.csv"))
{
int i = 0;
//1行ずつ処理
while (!sr.EndOfStream)
{
string? line = sr.ReadLine();
if (line == null) continue;
if (i == 0)
{
//1行目はヘッダーとして別途取得
string[] cols = line.Split(',');
i += 1;
continue;
}
//カンマで配列の要素として分ける
string[] values = line.Split(',');
// 配列からリストに格納する
lists.Add(values);
}
}
return lists;
}
Setting.cs
環境設定系だけですね。BlueStacksのadb.exeのパスを書くのが主な用途。
internal class Setting
{
// 環境設定
public static string ADB_PATH = @"C:\Program Files\BlueStacks_nxt\adb.exe";
public static string DEFAULT_PORT = "5555";
public static int SLEEP_TIME = 100;
public static int PANEL_TOUCH_INTERVAL = 500;
public static int touch_y_diff = 0;
}
AutoPlayLib.cs
画像認識、画像チェック、画像タップ、文字認識などの諸々が書かれています。
internal class AutoPlayLib
{
// 環境設定
string ADB_PATH = AndroidAutoPlay.Setting.ADB_PATH;
string? PORT = AndroidAutoPlay.Setting.DEFAULT_PORT;
int SLEEP_TIME = AndroidAutoPlay.Setting.SLEEP_TIME;
System.Drawing.Image? screenShotImage = null;
AdbClient client;
DeviceData? device;
public int touch_y_diff = 0;
public AutoPlayLib(string[] args)
{
InitialAdbServer();
if (args.Length > 0)
{
PORT = args.FirstOrDefault();
}
client = new AdbClient();
client.Connect("127.0.0.1:" + PORT);
//device = client.GetDevices().FirstOrDefault();
foreach (DeviceData deviceData in client.GetDevices())
{
if (deviceData.Serial.Equals("127.0.0.1:" + PORT))
{
device = deviceData;
break;
}
}
if (device == null)
{
//device = client.GetDevices().FirstOrDefault();
}
}
/**
* 石板クリック
*/
int TouchItem(string imagePath, int count)
{
if (count >= 3) return 0;
touch_y_diff = 50;
if (TouchImg(imagePath))
{
touch_y_diff = 0;
return 1;
}
touch_y_diff = 0;
return 0;
}
/**
* 画像チェック
*/
public Boolean CheckImg(string imagePath)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
if (screenShotImage == null) return false;
try
{
// 画像の読み込み
Bitmap bmp = new Bitmap(screenShotImage);
Mat temp = new Mat(imagePath);
Mat tempGray = temp.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat sc = OpenCvSharp.Extensions.BitmapConverter.ToMat(bmp);
Mat scGray = sc.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat result = new Mat();
try
{
// テンプレートマッチ
Cv2.MatchTemplate(scGray, tempGray, result, TemplateMatchModes.CCoeffNormed);
// 類似度が最大/最小となる画素の位置を調べる
OpenCvSharp.Point minloc, maxloc;
double minval, maxval;
Cv2.MinMaxLoc(result, out minval, out maxval, out minloc, out maxloc);
Rect rect = new Rect(maxloc.X, maxloc.Y, temp.Width, temp.Height);
Cv2.Rectangle(sc, rect, new OpenCvSharp.Scalar(0, 0, 255), 2);
//sc.SaveImage(@".\screenshot\sc.bmp");
// しきい値で判断
var threshold = 0.8;
if (maxval >= threshold)
{
//// 最も見つかった場所に赤枠を表示
//Rect rect = new Rect(maxloc.X, maxloc.Y, temp.Width, temp.Height);
//Cv2.Rectangle(sc, rect, new OpenCvSharp.Scalar(0, 0, 255), 2);
//Cv2.ImWrite(@".\template_match_result.jpg", sc);
return true;
}
else
{
// 見つからない
return false;
}
}
finally
{
bmp.Dispose();
result.Dispose();
scGray.Dispose();
sc.Dispose();
tempGray.Dispose();
temp.Dispose();
}
}
finally
{
System.GC.Collect();
}
}
/**
* 画像チェック2
*/
public Boolean CheckImg2(string imagePath, List<System.Drawing.Point> respos)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
// if (GetScreenShot() == null) throw new Exception();
if (screenShotImage == null) return false;
try
{
// 画像の読み込み
Bitmap bmp = new Bitmap(screenShotImage);
Mat temp = new Mat(imagePath);
Mat tempGray = temp.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat sc = OpenCvSharp.Extensions.BitmapConverter.ToMat(bmp);
Mat scGray = sc.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat result = new Mat();
try
{
while (true)
{
// テンプレートマッチ
Cv2.MatchTemplate(scGray, tempGray, result, TemplateMatchModes.CCoeffNormed);
//Cv2.Threshold(result, result, 0.8, 1.0, ThresholdTypes.Tozero);
// 類似度が最大/最小となる画素の位置を調べる
OpenCvSharp.Point minloc, maxloc;
double minval, maxval;
Cv2.MinMaxLoc(result, out minval, out maxval, out minloc, out maxloc);
var threshold = 0.8;
if (maxval >= threshold)
{
// 見つかった場所に赤枠を表示
Rect rect = new Rect(maxloc.X, maxloc.Y, temp.Width, temp.Height);
Cv2.Rectangle(sc, rect, new OpenCvSharp.Scalar(0, 0, 255), 2);
// 見つかった箇所は塗りつぶす
Cv2.Rectangle(scGray, rect, new OpenCvSharp.Scalar(0, 0, 0), -1);
respos.Add(new System.Drawing.Point(maxloc.X + temp.Width / 2, maxloc.Y + temp.Height / 2));
//Console.WriteLine($"maxval='{maxval}' x='{maxloc.X + temp.Width / 2}' y= x='{maxloc.Y + temp.Height / 2}'");
}
else
{
break;
}
}
//Console.WriteLine(respos);
}
finally
{
bmp.Dispose();
result.Dispose();
scGray.Dispose();
sc.Dispose();
tempGray.Dispose();
temp.Dispose();
}
}
finally
{
System.GC.Collect();
}
if (respos.Count > 0)
{
return true;
}
return false;
}
/**
* 画像タップ
*/
public Boolean TouchImg(string imagePath)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
// if (GetScreenShot() == null) throw new Exception();
if (screenShotImage == null) return false;
try
{
// 画像の読み込み
Bitmap bmp = new Bitmap(screenShotImage);
Mat temp = new Mat(imagePath);
Mat tempGray = temp.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat sc = OpenCvSharp.Extensions.BitmapConverter.ToMat(bmp);
Mat scGray = sc.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat result = new Mat();
try
{
// テンプレートマッチ
Cv2.MatchTemplate(scGray, tempGray, result, TemplateMatchModes.CCoeffNormed);
// 類似度が最大/最小となる画素の位置を調べる
OpenCvSharp.Point minloc, maxloc;
double minval, maxval;
Cv2.MinMaxLoc(result, out minval, out maxval, out minloc, out maxloc);
// しきい値で判断
var threshold = 0.8;
if (maxval >= threshold)
{
//// 最も見つかった場所に赤枠を表示
Rect rect = new Rect(maxloc.X, maxloc.Y, temp.Width, temp.Height);
//Cv2.Rectangle(sc, rect, new OpenCvSharp.Scalar(0, 0, 255), 2);
//Cv2.ImWrite(@".\template_match_result.jpg", sc);
TouchPos((rect.X + (rect.Width / 2)), (rect.Y + (rect.Height / 2)));
}
else
{
// 見つからない
return false;
}
}
finally
{
bmp.Dispose();
result.Dispose();
scGray.Dispose();
sc.Dispose();
tempGray.Dispose();
temp.Dispose();
}
}
finally
{
System.GC.Collect();
}
Thread.Sleep(100);
return true;
}
/**
* 画面タップ
*/
public void TouchPos(int x, int y)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
if (device == null) return;
if (client == null) return;
client.Click(device, x, y - touch_y_diff);
}
/**
* 画面スワイプ
*/
public void Swipe(int from_x, int from_y, int to_x, int to_y)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
if (device == null) return;
if (client == null) return;
client.Swipe(device, from_x, from_y, to_x, to_y, 1000);
}
/**
* スマホの「戻る」ボタン
*/
public void SendBackKeyEvent()
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
client.BackBtn(device);
}
/**
* Adb初期化
*/
public void InitialAdbServer()
{
if (!AdbServer.Instance.GetStatus().IsRunning)
{
AdbServer server = new AdbServer();
StartServerResult result = server.StartServer(ADB_PATH, false);
if (result != StartServerResult.Started)
{
Console.WriteLine("Can't start adb server");
}
}
}
/**
* スクリーンショット
*/
public System.Drawing.Image? GetScreenShot()
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
using (Framebuffer fb = client.GetFrameBufferAsync(device, CancellationToken.None).GetAwaiter().GetResult())
{
if (screenShotImage != null) screenShotImage = null;
screenShotImage = fb.Header.ToImage(fb.Data);
//screenShotImage.Save(@".\screenshot\screenshot.jpeg", ImageFormat.Jpeg);
return screenShotImage;
}
}
/**
* 画像認識 文字取得
*/
public string GetText(int fromX, int fromY, int toX, int toY)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
GetScreenShot();
if (screenShotImage == null) return "";
//tessdataのパスを指定
var rootDir = Directory.GetCurrentDirectory();
var langPath = rootDir + @"\tessdata";
langPath = @"C:\Program Files\Tesseract-OCR\tessdata";
//tesseractでの使用する言語を指定
var langStr = "eng";
string readTexts = "";
using (var tesseract = new Engine(langPath, langStr))
{
//使用する文字を指定する(今回は数字と.のみを検出)
tesseract.SetVariable("tessedit_char_whitelist", "1234567890.");
tesseract.DefaultPageSegMode = PageSegMode.SingleLine;
Bitmap rectBitMap = ImageRoi(new Bitmap(screenShotImage), new Rectangle(fromX, fromY, toX - fromX, toY - fromY));
//rectBitMap.Save(@".\screenshot\temp.bmp");
var tempMat = new Mat();
var grayImage = BitmapConverter.ToMat(rectBitMap).CvtColor(ColorConversionCodes.BGR2GRAY);
//var thresholdImege = grayImage.Threshold(100, 255, ThresholdTypes.Binary);
Cv2.Threshold(grayImage, tempMat, 150, 255, ThresholdTypes.BinaryInv);
//tempMat.SaveImage(@".\screenshot\gray.bmp");
//画像データを渡してOcrを実行
//var pix = TesseractOCR.Pix.Image.LoadFromMemory(BitmapConverter.ToMat(rectBitMap).ToBytes());
var pix = TesseractOCR.Pix.Image.LoadFromMemory(tempMat.ToBytes());
Page page = tesseract.Process(pix);
readTexts = page.Text;
if (readTexts != null && !string.Empty.Equals(readTexts))
{
readTexts = readTexts.Replace("'\r", "").Replace("\n", "");
}
if (readTexts == null || readTexts.Equals(""))
{
DateTime dt = DateTime.Now;
tempMat.SaveImage(@".\screenshot\" + dt.ToString("yyyy_MM_dd_HH_mm_ss_") + "gray.bmp");
}
pix.Dispose();
page.Dispose();
tempMat.Dispose();
rectBitMap.Dispose();
pix = null;
}
return readTexts == null ? "" : readTexts;
}
/**
* 画像認識 文字取得
*/
public string GetText2(int fromX, int fromY, int toX, int toY)
{
if (device == null) throw new ApplicationException("デバイスが見つかりません。");
GetScreenShot();
if (screenShotImage == null) return "";
//tessdataのパスを指定
var rootDir = Directory.GetCurrentDirectory();
var langPath = rootDir + @"\tessdata";
langPath = @"C:\Program Files\Tesseract-OCR\tessdata";
//tesseractでの使用する言語を指定
var langStr = "eng";
string readTexts = "";
using (var tesseract = new Engine(langPath, langStr))
{
//使用する文字を指定する(今回は数字と.のみを検出)
tesseract.SetVariable("tessedit_char_whitelist", "1234567890.");
Bitmap rectBitMap = ImageRoi(new Bitmap(screenShotImage), new Rectangle(fromX, fromY, toX - fromX, toY - fromY));
//rectBitMap.Save(@".\screenshot\temp.bmp");
var grayImage = BitmapConverter.ToMat(rectBitMap).CvtColor(ColorConversionCodes.BGR2GRAY);
//var thresholdImege = grayImage.Threshold(100, 255, ThresholdTypes.Binary);
var tempMatDst = new Mat();
Cv2.BitwiseNot(grayImage, tempMatDst);
var tempMat = new Mat();
Cv2.Threshold(tempMatDst, tempMat, 100, 255, ThresholdTypes.BinaryInv);
//tempMat.SaveImage(@".\screenshot\gray.bmp");
//画像データを渡してOcrを実行
//var pix = TesseractOCR.Pix.Image.LoadFromMemory(BitmapConverter.ToMat(rectBitMap).ToBytes());
var pix = TesseractOCR.Pix.Image.LoadFromMemory(tempMat.ToBytes());
Page page = tesseract.Process(pix);
readTexts = page.Text;
if (readTexts != null && !string.Empty.Equals(readTexts))
{
readTexts = readTexts.Replace("'\r", "").Replace("\n", "");
}
if (readTexts == null || readTexts.Equals(""))
{
DateTime dt = DateTime.Now;
grayImage.SaveImage(@".\screenshot\" + dt.ToString("yyyy_MM_dd_HH_mm_ss_") + "gray2.bmp");
}
pix.Dispose();
page.Dispose();
rectBitMap.Dispose();
pix = null;
}
return readTexts == null ? "" : readTexts;
}
/// <summary>
/// Bitmapの一部を切り出したBitmapオブジェクトを返す
/// </summary>
/// <param name="srcRect">元のBitmapクラスオブジェクト</param>
/// <param name="roi">切り出す領域</param>
/// <returns>切り出したBitmapオブジェクト</returns>
public Bitmap ImageRoi(Bitmap src, Rectangle roi)
{
//////////////////////////////////////////////////////////////////////
// srcRectとroiの重なった領域を取得(画像をはみ出した領域を切り取る)
// 画像の領域
var imgRect = new Rectangle(0, 0, src.Width, src.Height);
// はみ出した部分を切り取る(重なった領域を取得)
var roiTrim = Rectangle.Intersect(imgRect, roi);
// 画像の外の領域を指定した場合
if (roiTrim.IsEmpty == true) return null;
//////////////////////////////////////////////////////////////////////
// 画像の切り出し
// 切り出す大きさと同じサイズのBitmapオブジェクトを作成
var dst = new Bitmap(roiTrim.Width, roiTrim.Height, src.PixelFormat);
// BitmapオブジェクトからGraphicsオブジェクトの作成
var g = Graphics.FromImage(dst);
// 描画先
var dstRect = new Rectangle(0, 0, roiTrim.Width, roiTrim.Height);
// 描画
g.DrawImage(src, dstRect, roiTrim, GraphicsUnit.Pixel);
// 解放
g.Dispose();
return dst;
}
}
ProgramAutoJoinRaid.cs
今回の処理の本体です。一応スタミナ足りないときは自動で追加するようになっています。ただ、参加する部隊が1固定なので私は将軍付けずに歩兵1に設定していました。サブアカが育っていなかった(育ててない)のでレイドの戻りが遅い為、いっそダブルドロップを捨てて戻り時間短縮&プログラム単純化を狙いました(言い訳)。
private AutoPlayLib? lib;
public void execute(CancellationToken token, object? param)
{
if (param == null) return;
string[] args = (string[])param;
try
{
logger.Info("開始しました。。PORT={0}", args[0]);
lib = new AutoPlayLib(args);
int sleep_count = 0;
bool gameReload = false;
double min_power = 115.0;
try
{
min_power = double.Parse(args[1]);
}
catch (Exception)
{
// 何も無し
}
logger.Info("ターゲットパワー>={0}", min_power);
while (true)
{
// キャンセル処理
if (token.IsCancellationRequested)
{
logger.Info("キャンセルされました。PORT={0}", args[0]);
break;
}
// 2時間に一回再起動する
try
{
if (!gameReload)
{
float hour = float.Parse(DateTime.Now.ToString("HH"));
float minute = float.Parse(DateTime.Now.ToString("mm"));
if (hour % 8 == 0 && minute == 0 && gameReload == false)
{
logger.Info("定期再起動を行います。PORT={0}", args[0]);
while (true)
{
// キャンセル処理
if (token.IsCancellationRequested)
{
logger.Info("キャンセルされました。PORT={0}", args[0]);
break;
}
Thread.Sleep(500);
lib.GetScreenShot();
// ゲーム終了
if (lib.CheckImg(@".\template\game_close.jpg"))
{
lib.TouchImg(@".\template\game_exit.jpg");
logger.Info("再起動実行 PORT={0}", args[0]);
gameReload = true;
break;
}
else
{
lib.SendBackKeyEvent();
}
}
// キャンセル処理
if (token.IsCancellationRequested)
{
logger.Info("キャンセルされました。PORT={0}", args[0]);
break;
}
}
else
{
gameReload = false;
}
}
}
catch (Exception)
{
}
Thread.Sleep(2000);
lib.GetScreenShot();
// 不要画面回避設定
if (!SkipAds.Skip(lib))
{
sleep_count = 0;
continue;
}
// 同盟ボタン
if (lib.TouchImg(@".\template\AlianceButton.jpg"))
{
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
// 同盟レイド
else if (lib.TouchImg(@".\template\raid.jpg"))
{
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
// レイド参加
else if (lib.CheckImg(@".\template\raidSankaButton.jpg"))
{
List<System.Drawing.Point> pos = new List<System.Drawing.Point>();
if (lib.CheckImg2(@".\template\raidSankaButton.jpg", pos))
{
for (int i = 0; i < pos.Count; i++)
{
string power = lib.GetText(412, pos[i].Y - 270, 505, pos[i].Y - 230);
Console.WriteLine($"パワー:{pos[i]} {power}");
try
{
double result = double.Parse(power);
Console.WriteLine(result);
if (result > min_power)
{
lib.TouchPos(pos[i].X, pos[i].Y);
break;
}
}
catch (FormatException)
{
Console.WriteLine($"Unable to parse '{power}'");
}
}
}
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
// スタミナ不足
else if (lib.CheckImg(@".\template\sutaminaFusoku.jpg") || lib.CheckImg(@".\template\sutaminaFusoku2.jpg"))
{
if (lib.TouchImg(@".\template\sutaminaFusokuKakunin.jpg")
|| lib.TouchImg(@".\template\sutaminaFusokuKakunin2.jpg")
|| lib.TouchImg(@".\template\sutaminakakunin.jpg"))
{
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
}
// スタミナ使用
else if (lib.TouchImg(@".\template\sutaminaSiyo.jpg")
|| lib.TouchImg(@".\template\sutaminaSiyo2.jpg")
|| lib.TouchImg(@".\template\sutaminaSiyo3.jpg"))
{
Thread.Sleep(500);
lib.TouchPos(453, 508);
lib.TouchPos(453, 508);
lib.GetScreenShot();
Thread.Sleep(500);
lib.TouchImg(@".\template\use.jpg");
Thread.Sleep(500);
lib.GetScreenShot();
Thread.Sleep(500);
if (lib.TouchImg(@".\template\returnButton.jpg"))
{
Thread.Sleep(Setting.SLEEP_TIME);
lib.GetScreenShot();
}
Thread.Sleep(Setting.SLEEP_TIME);
}
// 部隊1.jpg
else if (lib.TouchImg(@".\template\butai_1.jpg"))
{
Thread.Sleep(Setting.SLEEP_TIME);
lib.TouchImg(@".\template\syutugeki.jpg");
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
// 出撃
else if (lib.TouchImg(@".\template\syutugeki.jpg"))
{
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
else
{
Thread.Sleep(3000);
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count++;
if (sleep_count > 3)
{
lib.SendBackKeyEvent();
Thread.Sleep(Setting.SLEEP_TIME);
sleep_count = 0;
}
}
}
}
catch (Exception ex)
{
logger.Error(ex, $"PORT='{args[0]}'");
}
logger.Info("終了しました。。PORT={0}", args[0]);
}
SkipAds.cs
targetlist.csv
。。。
最後に
ここまで書いていてだんだん面倒になってきたので気が向いたら最後まで書こうかと思います。一応ここまででもちょっと分かる人なら動かせるんじゃないですかね・・・
note初めてだったので使用感みたくてちょっと書いてみました。
もし最後まで読んだ方がいましたら適当な文章ですみませんでした。
以上