Lineage 2用のPythonクリッカーボットの作成







まえがき



お正月休みを楽しむ方法は? コンピューターゲームをプレイしますか? いや! これを行うボットを書いて、雪だるまを自分で彫ってグリューワインを飲むほうがいいでしょう。







学生時代に、人気のあるMMORPGの1つであるLineage 2に魅了されました。ゲームでは、クラン、グループで団結し、友達を作り、ライバルと戦うことができます。 )







その結果、ボットは1つの問題を解決する必要があると判断しました。ファームです。 制御のために、エミュレートされたマウスクリックとキーストロークが使用され、宇宙での向き-コンピュータービジョン、プログラミング言語-Pythonが使用されます。







一般に、L2用のボットを作成することは新しいことではなく、かなりの数の準備が整っています。 これらは、クライアントとクリッカーの作業で実装される2つの主要なグループに分けられます。







1つ目は難しいチートです;ゲームに関しては、それらを使用するのはスポーツマンらしくないです。 2番目のオプションは、いくつかの変更を加えて他のゲームに適用できることを考えると、より興味深いものであり、実装はより興味深いものになります。 私が見つけたクリッカーは、さまざまな理由で動作しなかったか、不安定に動作しました。







重要:ここに記載されているすべての情報は、教育目的でのみ提供されています。 特に、ゲーム開発者がボットをうまく処理できるようにします。







要するに。







ウィンドウワーク



ここではすべてが簡単です。 ゲームのウィンドウのスクリーンショットを使用します。

これを行うには、ウィンドウの座標を定義します。 win32guiモジュールを使用してウィンドウを操作します。 目的のウィンドウは、タイトル「Lineage 2」によって決まります。







ウィンドウ位置メソッドを取得するためのコード
def get_window_info(): # set window info window_info = {} win32gui.EnumWindows(set_window_coordinates, window_info) return window_info # EnumWindows handler # sets L2 window coordinates def set_window_coordinates(hwnd, window_info): if win32gui.IsWindowVisible(hwnd): if WINDOW_SUBSTRING in win32gui.GetWindowText(hwnd): rect = win32gui.GetWindowRect(hwnd) x = rect[0] y = rect[1] w = rect[2] - x h = rect[3] - y window_info['x'] = x window_info['y'] = y window_info['width'] = w window_info['height'] = h window_info['name'] = win32gui.GetWindowText(hwnd) win32gui.SetForegroundWindow(hwnd)
      
      





ImageGrabを使用して目的のウィンドウの画像を取得します。







 def get_screen(x1, y1, x2, y2): box = (x1 + 8, y1 + 30, x2 - 8, y2) screen = ImageGrab.grab(box) img = array(screen.getdata(), dtype=uint8).reshape((screen.size[1], screen.size[0], 3)) return img
      
      





次に、コンテンツを操作します。







モンスター検索



最も興味深いこと。 私が見つけたそれらの実装は私には向いていません。 たとえば、人気のある有料のゲームの1つでは、これはゲームマクロを通じて行われます。 また、「プレーヤー」は、「/ターゲットモンスター名Bla Bla」のようなマクロで、各タイプのモンスターに登録する必要があります。







この場合、このロジックに従います。まず、画面上のすべての白いテキストを見つけます。 白いテキストは、モンスターの名前だけでなく、キャラクター自身の名前、NPCまたは他のプレイヤーの名前にもなります。 したがって、カーソルをオブジェクトの上に移動する必要があり、必要なパターンでハイライトが表示される場合、ターゲットを攻撃できます。







これが私たちが扱う元の写真です:













邪魔にならないように名前を黒で埋めて、写真をb / wに変換します。 RGBの元のイメージ-b / wが1つの値である場合、各ピクセルは0〜255の3つの値の配列です。 したがって、データ量を大幅に削減します。







 img[210:230, 350:440] = (0, 0, 0) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
      
      











すべてのオブジェクトを白で検索します(これはモンスターの名前を含む白いテキストです)







 ret, threshold1 = cv2.threshold(gray, 252, 255, cv2.THRESH_BINARY)
      
      











形態変換:







  1. サイズ50x5の長方形でフィルタリングします。 そのような長方形が何よりも上に出てきました。
  2. テキストを含む長方形内のノイズを除去します(実際、文字間のすべてを白で埋めます)
  3. もう一度、フィルターを使用してノイズ、ぼかし、ストレッチを削除します


 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 5)) closed = cv2.morphologyEx(threshold1, cv2.MORPH_CLOSE, kernel) closed = cv2.erode(closed, kernel, iterations=1) closed = cv2.dilate(closed, kernel, iterations=1)
      
      











結果のスポットの真ん中を見つける







 (_, centers, hierarchy) = cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
      
      





それは機能しますが、面白くすることができます(たとえば、遠くにいるため名前が見えないモンスターの場合)- ここにあるようにTensorFlow Object Detectionを使用します。







ここで、見つかったモンスターにカーソルを合わせ、cv2.matchTemplateメソッドを使用してハイライトが表示されるかどうかを確認します。 LMBと攻撃ボタンを押したままにします。







クリックして



モンスターの検索が判明すると、ボットはすでに画面上でターゲットを見つけて、それらの上にホバーすることができます。 ターゲットを攻撃するには、マウスの左ボタンをクリックして「攻撃」をクリックする必要があります(「1」ボタンで攻撃をバインドできます)。 カメラを回転するには右クリックが必要です。







ボットをテストしたサーバーで、AutoItを介してクリックが発生しましたが、何らかの理由で機能しませんでした。







結局のところ、ゲームはさまざまな方法でオートクリッカーから保護されています。









また、一部のアプリケーションは、このサーバーのクライアントとして、OSレベルでクリックのソースを特定できます。 (誰かが正確にその方法を教えてくれれば素晴らしいことです)。







クリックできるフレームワーク(pyautogui、ロボットフレームワークなど)を試しましたが、どのオプションも機能しませんでした。 考えがすり抜けて、ボタンを押すデバイスを作成しました(誰かがそれをしました)。 可能な限り激しくクリックする必要があるようです。 その結果、私はドライバーを書くことに目を向け始めました。







インターネットで、問題を解決する方法が見つかりました。目的の信号を供給するようにプログラムできるusbデバイス-Digispark。







私はAliexpressで数週間待ちたくないので、検索を続けました。







その結果、 素晴らしいCライブラリが見つかりました

彼女とPythonのラッパーが見つかりました







ライブラリがPython 3.6で起動しませんでした-アクセス違反エラーが発生しました。 そのため、Python 2.7にジャンプする必要がありましたが、すべてが魅力的に機能しました。







カーソルの動き



ライブラリは、マウスの移動先など、任意のコマンドを送信できます。 しかし、それはテレポーテーションカーソルのように見えます。 禁止されないように、カーソルをスムーズに移動させる必要があります。







基本的に、タスクはAutoHotPyラッパーを使用してカーソルをポイントAからポイントBに移動します。 あなたは本当に数学を覚える必要がありますか?







少し熟考した後、グーグルで検索することにしました。 何も発明する必要がないことが判明しました。この問題は、コンピューターグラフィックスで最も古いアルゴリズムの1つであるBresenhamアルゴリズムによって解決されました。







ウィキペディアから直接実装を取得できます







作業ロジック



すべてのツールがありますが、最も簡単なことはスクリプトを書くことです。







  1. モンスターが生きている場合、攻撃を続ける
  2. ターゲットがない場合、ターゲットを見つけて攻撃を開始します
  3. ターゲットが見つからない場合は、少し向きを変えてください
  4. 5回も誰も見つからなかった場合-側に行って、もう一度始めてください


多かれ少なかれ興味深いことから、私は被害者の健康状態をどのように受け取ったかを説明します。 一般的に、OpenCVパターンを使用してターゲットのヘルスステータスを示すコントロール要素を見つけ、1ピクセルの高さのストリップを取得し、赤でシェーディングされている割合としてカウントします。







被害者健康法コード
 def get_targeted_hp(self): """ return victim's hp or -1 if there is no target """ hp_color = [214, 24, 65] target_widget_coordinates = {} filled_red_pixels = 1 img = get_screen( self.window_info["x"], self.window_info["y"], self.window_info["x"] + self.window_info["width"], self.window_info["y"] + self.window_info["height"] - 190 ) img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) template = cv2.imread('img/target_bar.png', 0) # w, h = template.shape[::-1] res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED) threshold = 0.8 loc = np.where(res >= threshold) if count_nonzero(loc) == 2: for pt in zip(*loc[::-1]): target_widget_coordinates = {"x": pt[0], "y": pt[1]} # cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (255, 255, 255), 2) if not target_widget_coordinates: return -1 pil_image_hp = get_screen( self.window_info["x"] + target_widget_coordinates['x'] + 15, self.window_info["y"] + target_widget_coordinates['y'] + 31, self.window_info["x"] + target_widget_coordinates['x'] + 164, self.window_info["y"] + target_widget_coordinates['y'] + 62 ) pixels = pil_image_hp[0].tolist() for pixel in pixels: if pixel == hp_color: filled_red_pixels += 1 percent = 100 * filled_red_pixels / 150 return percent
      
      





これで、ボットは被害者のHPの大きさと、彼女がまだ生きているかどうかを理解します。







基本的なロジックの準備ができました。次のようになります。

忙しい人のために、私は1.30で加速しました









作業停止



カーソルとキーボードでのすべての作業は、autohotpyオブジェクトを介して行われます。autohotpyオブジェクトの操作は、ESCボタンを押すことでいつでも停止できます。







問題は、ボットがキャラクターのアクションのロジックを担当するループの実行に忙しく、オブジェクトのイベントハンドラーとautohotpyがループが終了するまでイベントのリッスンを開始しないことです。 次のように、プログラムをマウスで停止することはできません ボットはそれを制御し、必要な場所にカーソルを移動します。







これは私たちには合わないので、ボットを2つのスレッドに分割する必要がありました。イベントをリッスンし、キャラクターのアクションのロジックを実行します。







2つのスレッドを作成する







  # init bot stop event self.bot_thread_stop_event = threading.Event() # init threads self.auto_py_thread = threading.Thread(target=self.start_auto_py, args=(auto_py,)) self.bot_thread = threading.Thread(target=self.start_bot, args=(auto_py, self.bot_thread_stop_event, character_class)) # start threads self.auto_py_thread.start() self.bot_thread.start()
      
      





そして、ESCでハンドラーを切断します。







 auto_py.registerExit(auto_py.ESC, self.stop_bot_event_handler)
      
      





ESCを押すと、イベントが設定されます







 self.bot_thread_stop_event.set()
      
      





文字ロジックループでは、イベントが設定されているかどうかを確認します。







 while not stop_event.is_set():
      
      





ESCボタンを使用して、ボットを静かに停止します。







おわりに



なぜ実際的な利益をもたらさない製品に時間を無駄にするのでしょうか?







実際、コンピュータービジョンの観点からのコンピューターゲームは、カメラで撮影された現実とほとんど同じであり、アプリケーションの可能性は膨大です。 優れた例は、レーザーでサケを撃つ水中ロボットに関する記事に記載されています。 また、この記事は、ゲーム開発者がボットドライバーと戦うのに役立ちます。







さて、私はPythonに精通し、コンピュータービジョンに触れ、最初の認知症の人工知能を書き、多くの喜びを得ました。

おもしろいと思いました。







PS リポジトリへのリンク








All Articles