POE(패스오브엑자일) 인벤 자동 정리 !!! [1]


* 경고 *

해당 포스팅은 교육적인 목적입니다. 절대 악용, 남용 하면 아니되옵니다.

안녕하세요 라이프온룸입니다. 

폐지를 열심히 줍고 아이템을 내다 팔면 Currency가 엄청 너저분하게 쌓이죠 ㅜㅜㅜ 오늘은 그런 문제를 해결하고 빠른 폐지 줍기를 위해서 캐릭터 인벤토리에서 한번의 Keyboard 입력으로 모든 Currency를 창고, stash 죠 ~ 로 보내보겠습니다. 

 

POE 에서 마우스 커서를 아이템에 대고 ctrl + c를 누르면 아이템 정보가 Clipboard로 복사되는 기능이 있죠 ? 그 기능을 적극 사용했습니다. ! 자세한 동작 원리는 영상을 참조해 주세요 ~ ㅎㅎ

우선 어떤 라이브러리를 사용했는지 보겠습니다. 

1. 필요 라이브러리

pip install pywin32
pip install pillow
pip install numpy
pip install mss
pip install pyautogui==0.9.38
pip install opencv-python
pip install pprint

사실 POE 자동 물약 먹기 !! if 체력 반피시 … Post 에서 사용했던 라이브러리가 똑같이 필요합니다. 다만 pprint 를 새롭게 설치하는 데요. 이 라이브러리는 파이썬 자료형을 좀 더 보기좋게 Print 하기 위한 라이브러리입니다. 

2. 코드 

코드소개를 하기전에 해줘야 할 사전 작업이 있습니다. 캐릭터 인벤토리가 보이는 상태에서 아래 코드의 run변수를 1번부터 5번까지 차례대로 변경하신 후 실행을 하면 되요 !! 각 단계에 따른 설명은 아래 내용을 참고하세요 

run = 1. Inventory의 좌표를 구합니다. 저번 포스팅에서 했던 cvFunc.py 파일이 필요합니다. (파란색 네모 스크린샷)

run = 2. Inventory 의 첫번째 그리드 스크린샷을 저장합니다. 이 스크린샷은 비어 있는 인벤토리 유닛을 찾을 때 사용됨으로 맨 첫번째 인벤토리는 비워 놓고 실행해야 합니다.  

run = 3. 비어 있는 인벤토리 유닛이 잘 검출 되는지 확인합니다. 혹시 검출이 잘 안된다면 config.cfg (밑에 설명 있습니다.)의 confidence 인자 값을 조절해 보세요 !! Clipboard 복사 시 시간이 꽤 들어가기 때문에 비어있는 유닛을 검출하여 예외 처리 합니다. 

run = 4. 실제 Currency가 Stash로 잘 들어가는지 확인 합니다. 코드를 실행 시키고 3초 이내에  POE 게임화면 을 클릭해서 POE를 Active 윈도우로 만들어야 정상 동작 합니다. 

run = 5. 자 이제 준비 과정은 끝났습니다. 게임 중 F2 키를 누르면 캐릭터 인벤토리의 Currency를 전부 Stash로 옮깁니다. 

그리고 아래 함수에 보면 EXCEPT_CURRENCY 라는 전역 변수가 있습니다. 이 변수는 창고로 옮기지 않을 Currency에 대한 설정입니다. 

inventoryFunc.py

import win32clipboard
import pyautogui as pa
import mouse as mo
import keyboard as keys
import time
import numpy as np
import pprint
import cv2.cv2 as cv2
import mss
import os
from PIL import Image

K_RARENESS = '희귀도'
K_ITEMNAME = 'item_name'
V_CURRENCY = '화폐'
EXCEPT_CURRENCY = ['감정 주문서', '포탈 주문서']



class InventoryTool():
    itemInfoInInven = {}

    def __init__(self, inventorySize, listexceptCurr):
        # inventorySize x, y, w, h
        self.inventorySize = inventorySize
        self.inven = np.empty(shape=(12, 5), dtype=tuple)
        self.invenUnitSize = (int(inventorySize[2] / 12), int(inventorySize[2] / 12))
        self.invenUnitBox = (inventorySize[0] + 5, inventorySize[1] + 5, \
                               self.invenUnitSize[0]-5, self.invenUnitSize[1]-5)
        self.listexceptCurr = listexceptCurr
        for xunit in range(12):
            for yunit in range(5):
                x = self.inventorySize[0] + xunit * self.invenUnitSize[0]
                y = self.inventorySize[1] + yunit * self.invenUnitSize[0]
                cord = pa.center((x, y, self.invenUnitSize[0], self.invenUnitSize[1]))
                self.inven[xunit][yunit] = cord


    def realPoint(self, unitPointX, unitPointY):
        return (self.inven[unitPointX][unitPointY][0], self.inven[unitPointX][unitPointY][1])

    # get clipboard data
    def getItemInfoFromClipboard(self, unitPoint):
        itemInfo = {}
        keys.send("ctrl+c")
        time.sleep(0.03)
        try:
            win32clipboard.OpenClipboard()

            itemData = win32clipboard.GetClipboardData()
            win32clipboard.EmptyClipboard()
            win32clipboard.CloseClipboard()

            itemKinds = itemData.split('--------')[0].strip()
            rarenessAndName = itemKinds.split('\n')
            rareness_key = rarenessAndName[0].split(':')[0]
            rareness_value = rarenessAndName[0].split(':')[1]

            itemInfo[rareness_key] = rareness_value.strip()
            itemInfo[K_ITEMNAME] = rarenessAndName[1]
            self.itemInfoInInven[unitPoint] = itemInfo
            return itemInfo
        except:
            print('No Item ', unitPoint)
            return None


    def checkItemInInvertory(self, boxRegions):

        for x in range(np.shape(self.inven)[0]):
            for y in range(np.shape(self.inven)[1]):

                rpoint = self.realPoint(x, y)
                if self.checkEmptyUnitPoint(boxRegions, rpoint):
                    mo.move(rpoint[0], rpoint[1])
                    time.sleep(0.03)
                    self.getItemInfoFromClipboard((x, y))

    def moveCurrencyToStash(self):
        keys.press('ctrl')
        for unitPoint, iteminfo in self.itemInfoInInven.items():
            rpoint = self.realPoint(unitPoint[0], unitPoint[1])
            try:
                # print('move', iteminfo[K_ITEMNAME])
                if iteminfo[K_RARENESS] == V_CURRENCY \
                        and not iteminfo[K_ITEMNAME] in self.listexceptCurr:

                    mo.move(rpoint[0], rpoint[1])
                    time.sleep(0.05)
                    mo.click()
                    print('Move', iteminfo[K_ITEMNAME])
            except:
                pass

        keys.release('ctrl')

    def checkEmptyUnitPoint(self, boxRegions, centerRpoint):
        for br in boxRegions:
            x, y, w, h = br
            if (x < centerRpoint[0] < x + w) and (y < centerRpoint[1] < y + h):
                # nothing on inven
                return False
        # something on inven
        return True


    def findImage(self, templateName, show=0, confidence=0.6):
        boxRegions = []
        x, y, w, h = self.inventorySize
        mon = {'top': y, 'left': x, 'width': w, 'height': h}
        sct = mss.mss()
        sct.grab(mon)

        img = Image.frombytes('RGB', (w, h), sct.grab(mon).rgb)
        frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        imgPath = os.path.dirname(os.path.realpath(__file__)) + '\\' + templateName
        template = cv2.imread(imgPath, 0)

        w, h = template.shape[::-1]

        res = cv2.matchTemplate(gray, template, cv2.TM_CCOEFF_NORMED)
        threshold = confidence
        loc = np.where(res >= threshold)

        for pt in zip(*loc[::-1]):
            if show == 1:
                cv2.rectangle(frame, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)
            realX = self.inventorySize[0] + pt[0]
            realY = self.inventorySize[1] + pt[1]
            boxRegions.append((realX, realY, w, h))

        if show == 1:
            cv2.imshow('image', frame)
            cv2.waitKey(0)
            cv2.destroyAllWindows()

        return boxRegions

    def makeTemplate(self, templateName, templatePoint):
        x, y, w, h = templatePoint
        mon = {'top': y, 'left': x, 'width': w, 'height': h}
        sct = mss.mss()
        sct.grab(mon)
        img = Image.frombytes('RGB', (w, h), sct.grab(mon).rgb)
        frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)


        imgPath = os.path.dirname(os.path.realpath(__file__)) + '\\' + templateName
        cv2.imwrite(imgPath, frame)

if __name__=="__main__":
    import configparser
    import cvFunc as cvf
    from ast import literal_eval

    configFile = os.path.dirname(os.path.realpath(__file__)) + '\\' + 'config.cfg'
    config = configparser.ConfigParser()
    config.read(configFile)
    inventory_size = literal_eval(config['inventory']['inven_region'])
    templateName = config['inventory']['inven_unit_empty_pic']
    templateConfidence = float(config['inventory']['confidence'])

    run = 1

    # -- screenshot whole inventory and get size
    if run == 1:
        ip = cvf.ScreenShot()
        ip.partScreenShot()

    # -- screenshot empty inventory unit and save
    elif run == 2:
        it = InventoryTool(inventory_size, EXCEPT_CURRENCY)
        it.makeTemplate(templateName, it.invenUnitBox)

    # -- find empty inventory unit image on whole inventory
    elif run == 3:
        it = InventoryTool(inventory_size, EXCEPT_CURRENCY)
        it.findImage(templateName, show=1, confidence=templateConfidence)

    # -- run 1 time
    elif run == 4:
        time.sleep(3)
        it = InventoryTool(inventory_size, EXCEPT_CURRENCY)
        boxRegions = it.findImage(templateName, 0, confidence=templateConfidence)
        # print(boxRegions)
        it.checkItemInInvertory(boxRegions)

        pprint.pprint(it.itemInfoInInven, indent=4)
        it.moveCurrencyToStash()

    # -- run forever
    elif run == 5:
        keyState = 0
        while True:
            time.sleep(0.001)
            value = keys.is_pressed('F2')
            if value == True:
                it = InventoryTool(inventory_size, EXCEPT_CURRENCY)
                boxRegions = it.findImage(templateName, 0, confidence=templateConfidence)
                # print(boxRegions)
                it.checkItemInInvertory(boxRegions)

                pprint.pprint(it.itemInfoInInven, indent=4)
                it.moveCurrencyToStash()
                keyState = value

 

 

3. Config.cfg

[inventory]
# 4K
# inven_region = (2599, 1152, 1213, 513)
# Full HD
inven_region = (1307, 576, 597, 250)
inven_unit_empty_pic = invenUnitEmpty.png
confidence = 0.8

 

inventory 섹션이 추가되었습니다. 

  • inven_region: inventory 의 x, y, w, h 를 의미합니다. run = 1 으로 얻을 수 있습니다.
  • inven_unit_empty_pic: run = 2 로 얻은 인벤토리 빈 공간 사진 이름입니다. 
  • confidence : OpenCV 에서 template Matching 시 어느정도의 비슷함을 허용할 것이냐에 대한 값입니다. 1에 가까워 질수록 주어진 Template 사진과 똑같은 영역만 검출됩니다. 

 

이렇게 하시고 inventoryFunc.py 를 실행하시면 됩니다. ! 

추가적으로 가끔씩 옮겨지지 않는 Currency가 생긴다면 getItemInfoFromClipboard 함수에서 time.sleep(0.03) time 을 0.05 정도로 늘려 주세요 ! 

ctrl+c 이후 너무 빠르게 Clipboard 내용을 읽게 되면 빠지는 아이템이 생기는 것 같습니다. 

 

넵 오늘 포스팅은 여기까지 입니다. 해당 코드는 엄청 기계적이고 빠른 마우스와 키보드 움직임을 보여주고 있습니다! 그만큼 걸리기 쉽다는 의미이기도 하니 역시 악용과 남용을 하시면 아니되겠죠 ???? ㅎㅎ

아 그나저나 듀얼리스트 내맘빌드로 빌드를 했는데 말라카이가 죽질 않네요 …. ㅜㅜ 나름 몹 빨리 죽길래 쎈캐인줄 알았는뎅 ㅜㅜ 여튼 모든 액린이분들 화이팅 입니다. !!! 좋은밤되세요 ~~ ㅎㅎ



블로그 구독하기 !!

You may also like...

4 Responses

  1. Kang 댓글:

    C:\Users\K\PycharmProjects\untitled\venv\Scripts\python.exe C:/Users/K/PycharmProjects/untitled/POE/dist/inventoryFunc.py
    could not open port ‘COM16’: FileNotFoundError(2, ‘지정된 파일을 찾을 수 없습니다.’, None, 2)
    Traceback (most recent call last):
    File “C:/Users/K/PycharmProjects/untitled/POE/dist/inventoryFunc.py”, line 135, in
    ip = cvf.ScreenShot()
    AttributeError: module ‘cvFunc’ has no attribute ‘ScreenShot’

    Process finished with exit code 1

    이렇게 나오는건 어떻게 해야하나요~?
    이 기능은 아두이노 보드를 사야 하는건가요? 그냥 소프트웨어만 가지고는 못쓰는 기능인가요?

    • Kang 댓글:

      아 끙끙 싸매다가 해결 했습니다.. ㅋ cvfunc.py 안에 코드를 잘못 붙여 넣었더라구요
      그래서 막힘없이 쭉쭉 진행했는데.. run4 이제 템을 보관함으로 이동하는 부분에서

      체크 후에 템 이동을 안하길래 본문 내용에서 첨부해주신 바와 같이

      time 0.05로 바꾸어 봐도 템이 보관함으로 이동하지 않습니다 ㅠ… 조언 부탁드립니다

      • Kang 댓글:

        C:\Users\K\PycharmProjects\untitled\venv\Scripts\python.exe C:/Users/K/PycharmProjects/untitled/POE/dist/inventoryFunc.py
        { (0, 0): {‘item_name’: ‘선대의 피난처\r’, ‘희귀도’: ‘희귀’},
        (0, 1): {‘item_name’: ‘키클로프의 거주지\r’, ‘희귀도’: ‘희귀’},
        (0, 2): {‘item_name’: ‘카오스 피난처\r’, ‘희귀도’: ‘희귀’},
        (0, 4): {‘item_name’: ‘포탈 주문서(Portal Scroll)’, ‘희귀도’: ‘화폐’},
        (1, 0): {‘item_name’: ‘숨겨진 관점\r’, ‘희귀도’: ‘희귀’},
        (1, 1): {‘item_name’: ‘너덜해진 안식처\r’, ‘희귀도’: ‘희귀’},
        (1, 2): {‘item_name’: ‘잊힌 구획\r’, ‘희귀도’: ‘희귀’},
        (1, 3): {‘item_name’: ‘뒤틀린 피난처\r’, ‘희귀도’: ‘희귀’},
        (1, 4): {‘item_name’: ‘감정 주문서(Scroll of Wisdom)’, ‘희귀도’: ‘화폐’},
        (3, 0): {‘item_name’: ‘진화의 오브(Orb of Transmutation)’, ‘희귀도’: ‘화폐’},
        (3, 1): {‘item_name’: ‘변화의 오브(Orb of Alteration)’, ‘희귀도’: ‘화폐’},
        (3, 2): {‘item_name’: ‘소멸의 오브(Orb of Annulment)’, ‘희귀도’: ‘화폐’},
        (3, 3): {‘item_name’: ‘기회의 오브(Orb of Chance)’, ‘희귀도’: ‘화폐’},
        (3, 4): {‘item_name’: ‘진화의 파편’, ‘희귀도’: ‘화폐’},
        (4, 0): {‘item_name’: ‘변화의 파편’, ‘희귀도’: ‘화폐’},
        (4, 1): {‘item_name’: ‘소멸의 파편’, ‘희귀도’: ‘화폐’},
        (4, 2): {‘item_name’: ‘확장의 오브(Orb of Augmentation)’, ‘희귀도’: ‘화폐’},
        (4, 4): {‘item_name’: ‘후회의 오브(Orb of Regret)’, ‘희귀도’: ‘화폐’},
        (5, 0): {‘item_name’: “대장장이의 숫돌(Blacksmith’s Whetstone)”, ‘희귀도’: ‘화폐’},
        (5, 1): {‘item_name’: ‘제왕의 오브(Regal Orb)’, ‘희귀도’: ‘화폐’},
        (5, 2): {‘item_name’: ‘연금술의 파편’, ‘희귀도’: ‘화폐’},
        (5, 3): {‘item_name’: ‘정제의 오브(Orb of Scouring)’, ‘희귀도’: ‘화폐’},
        (5, 4): {‘item_name’: ‘바알 오브(Vaal Orb)’, ‘희귀도’: ‘화폐’},
        (6, 2): {‘item_name’: ‘기술자의 오브’, ‘희귀도’: ‘화폐’},
        (6, 3): {‘item_name’: ‘카오스 오브(Chaos Orb)’, ‘희귀도’: ‘화폐’},
        (6, 4): {‘item_name’: ‘축복의 오브(Blessed Orb)’, ‘희귀도’: ‘화폐’}}
        Move 포탈 주문서(Portal Scroll)
        Move 감정 주문서(Scroll of Wisdom)
        Move 진화의 오브(Orb of Transmutation)
        Move 변화의 오브(Orb of Alteration)
        Move 소멸의 오브(Orb of Annulment)
        Move 기회의 오브(Orb of Chance)
        Move 진화의 파편
        Move 변화의 파편
        Move 소멸의 파편
        Move 확장의 오브(Orb of Augmentation)
        Move 후회의 오브(Orb of Regret)
        Move 대장장이의 숫돌(Blacksmith’s Whetstone)
        Move 제왕의 오브(Regal Orb)
        Move 연금술의 파편
        Move 정제의 오브(Orb of Scouring)
        Move 바알 오브(Vaal Orb)
        Move 기술자의 오브
        Move 카오스 오브(Chaos Orb)
        Move 축복의 오브(Blessed Orb)

        Process finished with exit code 0

        이렇게 move 라고 나오지만 실제로 움직이지 않습니다;; 7/5일 패치에서 아이템 이름 옆에 영어로 추가 되었는데

        그게 문제인걸까요?

        • 호그 댓글:

          moveCurrencyToStash 함수 안에서 mo.click() 밑에 time.sleep(0.2) 을 추가하여 해결하셨습니다. ㅎㅎ

댓글 남기기

이메일은 공개되지 않습니다.