PoE(패스오브엑자일) 크래프팅을 도와주는 매크로


————————————–
2019-10-08
역병리그 업데이트가 되면서 전쟁지팡이 클래스가 추가 되었군요 !! 이제 전쟁지팡이 클래스도 해당 애드온 적용이 가능 할 겁니다. ! ㅎㅎ

 

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

오늘은 크래프팅을 도와 주는 매크로의 일환으로 내 인벤토리 무기 방어구에 대체의 오브, 카오스 오브를 발랐을 때 출현 가능한 Affix를 보여주고 변환의 오브나 카오스 오브를 바를 때 내가 입력한 Affix 조건에 만족시 마우스 클릭을 막아주는 매크로를 만들어 보겠습니다. !!! 자세한 사용 방법은 아래 영상을 참조해 주세요 !

그리고 해당 매크로는 영정의 가능성이 없을 것 같아 EXE 파일도 공개 하겠습니다. 아래 링크를 이용하시면 됩니다. 

orbHelper exe 파일 링크

 

  • 가능한 mod 확인 기능 영상 
  • 원하는 Affix 출현시 마우스를 막아주는 기능 영상 

1. Affix 

Affix(Prefix, Suffix) 는 PoE 매직, 레어, 유니크 아이템등에 붙을 수 있는 옵션입니다. 그리고 이 추가적인 옵션의 집합을 Explict 라고 하지요 

매직아이템의 경우에는 0~1 개의 Prefix와 Suffix가 붙을 수 있습니다. 레어아이템의 경우엔 1~3 개 이구요

즉 아래 아이템의 경우에는 희귀 아이템이고 3개의 Prefix와 3개의 Suffix가 붙어 있으니 이제 더 이상 추가적인 Affix는 붙을 수 없습니다.

그런데 이 Affix는 아이템 Base 종류별, 레벨별, 요구사항별 붙을 수 있는 옵션이 다 다릅니다. 그래서 이 매크로를 통해서 좀 더 쉽게 어떤 Affix가 붙을 수 있는지 알 수 있을거에요 ㅎㅎ

그럼 바로 필요한 라이브러리가 어떤게 있는지 보겠습니다. 

 

2. 라이브러리 

  • pip install wxPython 

wxPython은 cross-platform GUI toolkit 으로 윈도우, 맥, 리눅스 등에서 GUI 프로그램을 가능하게 해주는 라이브러리 입니다. 역사가 오래된 만큼 자료도 많고 예제도 많은 것 같습니다. 하지만 기본적인 위젯만을 지원하여 휘양 찬란한 GUI 프로그램을 만드는데는 적합하진 않은 것 같아요 ㅎㅎ 여기서는 뭐 기능 위주로만 하면 되니깐 한번 사용해 봤습니다. 

추가적으로 저번시간에 만들었던 inventoryFunc.py 파일이 필요 합니다. 이 파일은 코드 내용이 좀 바뀌어서 아래 코드란에 다시 올려 둘게요 ㅎㅎ 

 

3. config.cfg

[orb_helper]
key_possible_mod = F5
key_possible_mod_detail = F6
key_orb_mouse_block = F7

[tobe_item_explict]
exp = '|' or '&' and Prefix 0~1, Suffix 0~1 Prefix 1~3, Suffix 1~3
prefix = 회피 #% 증가
suffix = 

이번 매크로 기능에 필요한 데이터 입니다. 각 센션 및 옵션별 역할을 한번 볼게요 ㅎㅎ 

[orb_helper] 섹션

  • key_possible_mod = F5: 마우스를 아이템에 갖다 대고 F5를 누르면 해당 아이템 레벨에 붙을 수 있는 모든 Mod들이 Config.cfg에 써집니다. 
  • key_possible_mod =F6: Mod들의 Affix 이름과 구체적인 정보들이 Config.cfg에 써집니다. 
  • key_orb_mouse_block=F7: F7을 누르면 윈도우 창 하나가 뜨면서 [tobe_item_explict] 섹션의 조건들이 만족 되었을 때 마우스를 Block 시키는 기능이 실행 됩니다. 음 .. 말로 설명하려니 어렵내요 .. 영상 참조 부탁드립니다. ㅋㅋ 

 

[tobe_item_explict] 섹션은 F7 기능을 수행하기 위한 조건식을 입력하는 섹션입니다. 해당 섹션을 설명하기 이전에 프로그램 또는 파이썬 코드 실행 후 PoE 아이템에 마우스를 대고 F5를 누른 뒤  config.cfg의 내용을 봅시다. 

아래 내용이 추가되어 있겁니다. 

[current_item_explict]
val = 
  회피 74% 증가
  화염 저항 +15%

[possible_item_explict]
val = 
  -- Possible Mod on 투구(Dex) Item Level : 60
  - prefix
  ----------------------------------------
  생명력 최대치 #
  회피 #% 증가
  회피 #% 증가  /기절 및 막기 회복 #% 증가
  회피 #
  회피 # /생명력 최대치 #
  발견한 아이템 희귀도 #% 증가
  장착된 소환수 스킬 젬 레벨 #
  근접 공격자에게 #의 물리 피해 반사
  - suffix
  ----------------------------------------
  민첩 #
  지능 #
  발견한 아이템 희귀도 #% 증가
  정확도 #
  초당 생명력 # 재생
  화염 저항 #%
  냉기 저항 #%
  번개 저항 #%
  카오스 저항 #%
  기절 및 막기 회복 #% 증가
  능력치 요구사항 #% 감소
  시야 반경 #% 증가  /일반 정확도 #% 증가
  ----------------------------------------

참고로 위 데이터는 다음 아이템에 대한 데이터 입니다. 

여튼 [possible_item_explict] 섹션의 prefix와 suffix 중에 필요한 옵션을 [tobe_item_explict] 에 복붙 하시면 됩니다. 

예를 들어 내가 이 삼각모의 옵션에 생명력 최대치랑 카오스 저항이 들어가게 하고 싶다면 아래와 같이 입력하면 됩니다. 

[tobe_item_explict]
exp = '|' or '&' and Prefix 0~1, Suffix 0~1 Prefix 1~3, Suffix 1~3
prefix = 생명력 최대치 #
suffix = 카오스 저항 #%

희귀 아이템의 경우 prefix, suffix 가 세개 까지 붙을 수 있죠 ? 이 경우에는 |(or), &(and) 로 조건식을 세팅할 수 있습니다. 

예를 들어 위 삼각모가 희귀아이템이라고 하고 내가 원하는 옵션이

생명력 최대치와 기절 및 막기 회복 증가 중 하나가 있어야 하고

발견한 아이템 희귀도 증가가 있어야 할 경우 아래처럼 세팅 하시면 됩니다. 

[tobe_item_explict]
exp = '|' or '&' and Prefix 0~1, Suffix 0~1 Prefix 1~3, Suffix 1~3
prefix = 생명력 최대치 # | 기절 및 막기 회복 #% 증가 & 발견한 아이템 희귀도 #% 증가
suffix = 

참고로 프로그래밍의 세계에서는 &(and)가 우선이지만 여기서는 |(or) 가 우선입니다. 그리고 prefix 와 suffix 는 무조건 &(and) 입니다. !! 

조건식에 입력된 Mod가 [possible_item_explict] 에서 찾을 수 없는 것이거나 희귀도 에 부합하지 않은 식일 경우 아래처럼 CMD 창에 아래와 같이 Condition Expresion Error 라는 문구가 뜰겁니다. 

조건식이 정상일 경우 아래와 같은 창이 뜰거에요 !!!

그리고 오브를 바르다가 조건식을 만족하는 Explict가 나오면 아래와 같이 되면서 마우스 클릭을 못하게 됩니다. !!!

4. 코드

세개의 Python 파일과 Json DB 파일이 필요 합니다. Json DB 파일은 EXE 다운로드 링크에서 data 폴더를 가져오시면 됩니다. 즉 폴더 구조는 아래와 같아요 !

  • orbHelper.py
  • inventoryFunc.py
  • gui.py
  • data
    • item_bases_armour_kr.json
    • item_bases_kr.json
    • item_bases_weapon_kr.json
    • mods.json

 

—inventoryFunc.py—

#-*- coding:utf-8 -*-
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
import codecs
import json
import pywinauto as pwa
import copy

K_RARENESS = '희귀도'
K_ITEMNAME = 'item_name'
K_ITEMBASENAME = 'item_base_name'
K_ISSELL = 'isSell'
K_EXPLICT = 'explicitMods'
K_ITEMCLASS = 'itemClass'
K_CORRUPTION = '타락복제여부'

V_CURRENCY = '화폐'
V_NORMAL = '보통'
V_MAGIC = '마법'
V_RARE = '희귀'
V_UNIQUE = '고유'
V_CORRUPTION = '타락'
V_DUP = '복제'

K_CHECK = '미확인여부'
CHECK_ITME = '감정 주문서'

KG_SOCKET = '홈'
KG_REQUIRE = '요구사항'
KG_ITEMLV = '아이템 레벨'

STAT_USE = ['장갑', '장화', '갑옷', '투구', '방패']
STAT_TRANS = {'힘':'Str', '민첩':'Dex', '지능':'Int'}


EXCEPT_CURRENCY = ['감정 주문서', '포탈 주문서']
EXCEPT_SELLING = ['고유']




# Make Active PoE window
def setActivePoe():
    app = pwa.application.Application()
    try:
        app.connect(title_re='Path of Exile')
        app_dialog = app.window()
        app_dialog.set_focus()
    except Exception as e:
        print(e, ' SetActive POE Fail')


class InventoryTool():

    def __init__(self, inventorySize, listexceptCurr):
        # inventorySize x, y, w, h
        self.itemInfoInInven = {}
        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

    # init Data of inventory
    def initItemInfoInven(self):
        self.itemInfoInInven = {}

    # make unitPoint(0, 0) to realPoint(1200, 700)
    def realPoint(self, unitPointX, unitPointY):
        return (self.inven[unitPointX][unitPointY][0], self.inven[unitPointX][unitPointY][1])

    # make realPoint(1200, 700) to unitPoint(0, 0)
    def makeUnitPoint(self, realPointX, realPointY):
        a = [(ix, iy) for ix, row in enumerate(it.inven) for iy, i in enumerate(row) if i == (realPointX, realPointY)]
        return a[0]

    # clipboard string to dict
    def makeItemInfoOfClipboard(self, cText):
        itemInfo = {}

        specList = cText.split('--------')


        specList = [i.strip() for i in specList]

        # 확인 여부에 따라 아이템 Base 이름이 달라짐
        # -- Item Class, Name Info
        itemClassAndName = specList.pop(0)
        # -- 희귀도
        infoList = itemClassAndName.split('\n')
        infoList = [i.strip() for i in infoList]
        rareness = infoList.pop(0)
        rareness_key, rareness_value = rareness.split(':')

        rareness_key = rareness_key.strip()
        rareness_value = rareness_value.strip()
        itemInfo[rareness_key] = rareness_value

        # -- 요구 사항
        itemInfo[KG_REQUIRE] = {}
        for idx, data in enumerate(specList):
            if data.find(KG_REQUIRE) == 0:
                reqList = data.split('\n')
                reqList = [i.strip() for i in reqList]
                reqList.pop(0)
                for spec in reqList:
                    specK, specV = spec.split(':')
                    itemInfo[KG_REQUIRE][specK] = specV.strip()
                specList.pop(idx)
                break

        # -- 홈 and Item Level
        tempList = specList.copy()
        for idx, data in enumerate(tempList):
            if (data.find(KG_SOCKET) == 0) or (data.find(KG_ITEMLV) == 0):
                specK, specV = data.split(':')
                itemInfo[specK.strip()] = specV.strip()
                specList.remove(data)

        # -- 타락 여부
        itemInfo[K_CORRUPTION] = ''
        for idx, data in enumerate(tempList):
            if data.find(V_CORRUPTION) == 0:
                itemInfo[K_CORRUPTION] = V_CORRUPTION
                specList.remove(data)

        # -- 복제 여부
        itemInfo[K_CORRUPTION] = ''
        for idx, data in enumerate(tempList):
            if data.find(V_DUP) == 0:
                itemInfo[K_CORRUPTION] = V_DUP
                specList.remove(data)

        # -- 확인여부
        itemInfo[K_EXPLICT] = []
        try:
            if specList.index('미확인') >= 0:
                isCheck = True
            else:
                isCheck = False
            itemInfo[K_CHECK] = isCheck
        except Exception as e:
            isCheck = False
            itemInfo[K_CHECK] = isCheck

        # -- 미확인 Item
        if isCheck == True:
            itemInfo[K_CHECK] = isCheck
            # -- 아이템 Base 이름
            itemName = infoList.pop(0)
            itemInfo[K_ITEMBASENAME] = itemName

        # -- 확인 아이템
        else:
            # 희귀 혹은 고유 아이템의 경우 아이템 이름 존재
            if rareness_value == V_RARE or rareness_value == V_UNIQUE:
                itemName = infoList.pop(0)
                itemInfo[K_ITEMNAME] = itemName
                itemName = infoList.pop(0)
                itemInfo[K_ITEMBASENAME] = itemName
            # Normal 아이템 혹은 Magic 아이템의 경우 Base Name에 추가 내용이 붙음
            else:
                # -- 아이템 Base 이름만 존재
                # -- 마법아이템의 경우 아이템 Base 이름에 Prefix, Suffix 붙음
                itemName = infoList.pop(0)
                itemInfo[K_ITEMBASENAME] = itemName

            # -- explict
            if rareness_value == V_RARE or rareness_value == V_UNIQUE or rareness_value == V_MAGIC:
                # item 설명 제거
                if rareness_value == V_UNIQUE:
                    specList.pop()

                if itemInfo[K_ITEMBASENAME].find('주얼') >= 0 or itemInfo[K_ITEMBASENAME].find('플라스크') >= 0:
                    specList.pop()

                # ---------------------------------------------

                explicts = specList.pop()
                explictList = explicts.split('\n')
                explictList = [i.strip() for i in explictList]
                itemInfo[K_EXPLICT] = explictList

        #print('Not Parsed: ', specList)

        return itemInfo

    def getItemSize(self, itemSpec):
        try:
            return (itemSpec['Width'], itemSpec['Height'])
        except:
            return (1, 1)

    # 현제 좌표에서 아이템 영역
    def makeItemUnitSize(self, itemSize, CurrentCoord):
        x, y = itemSize
        coord = []
        for xi in range(x):
            for yi in range(y):

                coord.append((CurrentCoord[0] + xi, CurrentCoord[1] + yi))
        return coord

    # .json 파일의 결과인 itemDB 에서 item base name 검색
    def findItemOnDB(self, itemDB, invenItemDict, findKey=''):
        try:
            itemName = invenItemDict[K_ITEMBASENAME]
            itemRareness = invenItemDict[K_RARENESS]
        except Exception as e:
            print (e, 'Not Found',invenItemDict)
            return None, None

        for itemSubClass in itemDB:
            pkey = [*itemSubClass][0]

            for key, itemSpec in itemSubClass[pkey].items():
                # if itemRareness == V_MAGIC or itemRareness == V_NORMAL:
                if itemName.find(key) >=0:
                    print('Item Name : ', key, ', Item Name On Inventory: ', itemName)
                    if findKey != '':
                        try:
                            return findKey, itemSpec[findKey]
                        except:
                            print('Invalid Key ')
                            return None, None

                    else:
                        return key, self.getItemSize(itemSpec)
                # else:
                #     if key == itemName:
                #         print ('Found ', key)
                #         return key, self.getItemSize(itemSpec)
        return None, None

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

            itemData = win32clipboard.GetClipboardData()
            win32clipboard.EmptyClipboard()
            win32clipboard.CloseClipboard()
            itemInfo = self.makeItemInfoOfClipboard(itemData)
            self.itemInfoInInven[unitPoint] = itemInfo
            return itemInfo

        except Exception as e:
            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 checkItemInInventoryWithInfo(self, boxRegions, itemDB = []):
        oned_array = np.reshape(self.inven, (np.prod(self.inven.shape),))
        exceptUnitCoord = []
        for coord in oned_array:
            if self.checkEmptyUnitPoint(boxRegions, coord):
                unitPoint = self.makeUnitPoint(coord[0], coord[1])
                if (unitPoint in exceptUnitCoord) == False:
                    mo.move(coord[0], coord[1])
                    time.sleep(0.03)

                    itemInfo = self.getItemInfoFromClipboard(unitPoint)
                    if itemDB != [] and itemInfo != None:
                        itemName, itemSize = self.findItemOnDB(itemDB, itemInfo)
                        # inventory item이 DB에 있을 시
                        if itemSize != None:
                            exceptUnitCoord = exceptUnitCoord + self.makeItemUnitSize(itemSize, unitPoint)

                            if self.itemInfoInInven[unitPoint][K_RARENESS] in EXCEPT_SELLING:
                                self.itemInfoInInven[unitPoint][K_ISSELL] = False
                            else:
                                self.itemInfoInInven[unitPoint][K_ISSELL] = True
                        else:
                            self.itemInfoInInven[unitPoint][K_ISSELL] = False



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

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

        keys.release('ctrl')
        print('--------------------')
        self.itemInfoInInven = {}


    def findItemOnInventory(self, itemName):
        for key, item in self.itemInfoInInven.items():
            if item[K_ITEMBASENAME] == itemName:
                return key
        return None

    def itemConfirm(self):
        checkItemUnitCoord = self.findItemOnInventory(CHECK_ITME)
        if checkItemUnitCoord == None:
            return

        checkItemPoint = self.realPoint(checkItemUnitCoord[0], checkItemUnitCoord[1])

        print('-----itemConfirm-----')
        for key, item in self.itemInfoInInven.items():
            if item[K_CHECK] == True:
                # -- 감정주문서로 이동 후 우클릭
                mo.move(checkItemPoint[0], checkItemPoint[1])
                time.sleep(0.05)
                mo.click(mo.RIGHT)
                time.sleep(0.05)
                # -- 감정할 아이템이로 가서 클릭
                itemPoint = self.realPoint(key[0], key[1])
                mo.move(itemPoint[0], itemPoint[1])
                time.sleep(0.05)
                mo.click()
                time.sleep(0.05)
                print('Item Confirmed : ', item[K_ITEMBASENAME])
        print('--------------------')

    def itemSell(self):

        print('-----itemSell-----')
        keys.press('ctrl')
        for key, item in self.itemInfoInInven.items():
            if item[K_ISSELL] == True:
                itemPoint = self.realPoint(key[0], key[1])
                mo.move(itemPoint[0], itemPoint[1])
                time.sleep(0.05)
                mo.click()
                time.sleep(0.05)
                print('Item On Sell : ', item[K_ITEMBASENAME])
        self.itemInfoInInven = {}
        keys.release('ctrl')
        print('--------------------')

    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

    # img's bgr min, max
    def getBGRMinMax(self, img):

        # frame = cv2.GaussianBlur(frame, (5, 5), 0)
        min_ch = (np.amin(img[:, :, 0]), np.amin(img[:, :, 1]), np.amin(img[:, :, 2]))
        max_ch = (np.amax(img[:, :, 0]), np.amax(img[:, :, 1]), np.amax(img[:, :, 2]))


        #print("max_ch = ", max_ch, min_ch)
        return min_ch, max_ch

    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)

        copyImg = copy.copy(frame)

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        imgPath = os.path.dirname(os.path.realpath(__file__)) + '\\' + templateName
        template = cv2.imread(imgPath)
        tmin, tmax = self.getBGRMinMax(template)

        template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)


        w, h = template.shape[::-1]
        # 입력된 templateName 과 같은 그림을 이벤토리 에서 찾음
        res = cv2.matchTemplate(gray, template, cv2.TM_CCOEFF_NORMED)
        threshold = confidence
        loc = np.where(res >= threshold)

        for pt in zip(*loc[::-1]):

            cropedImg = copyImg[pt[1]:pt[1] + h, pt[0]:pt[0] + w]
            dmin, dmax = self.getBGRMinMax(cropedImg)
            # compare only min
            if dmin[0] <= tmin[0] and dmin[1] <= tmin[1] and dmin[2] <= tmin[2]:
                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)


    # ----------- mods
    def getItemInfoForMods(self, unitPoint, itemDB):
        itemInfo = self.getItemInfoFromClipboard(unitPoint)
        if itemInfo != None:
            itemSubClassKey, itemSubClass = self.findItemOnDB(itemDB, itemInfo, 'Item Class')
            if itemSubClass == None:
                print('No Item In DB')
                return None
                #return itemInfo

            itemChar = ''
            if KG_REQUIRE in itemInfo and itemSubClass in STAT_USE:
                for key, val in itemInfo[KG_REQUIRE].items():
                    if key in STAT_TRANS:
                        itemChar += STAT_TRANS[key]

            if itemChar != '':
                itemChar = '(' + itemChar + ')'
            itemSubClassForMods = itemSubClass + itemChar
            itemInfo[K_ITEMCLASS] = itemSubClassForMods

        return itemInfo

—orbHelper.py—

#-*- coding:utf-8 -*-
import inventoryFunc as invenF
import gui as gui
import mouse as mo
import keyboard as keys
import pprint
import time
import os
import codecs
import json
import re

import multiprocessing as mp
import configparser
from ast import literal_eval

jsonFileDBs = ['mods']
modsDB = {}
for jDB in jsonFileDBs:
    path = os.path.dirname(os.path.realpath(__file__)) + '\\' + 'data' + '\\' + jDB + '.json'
    with codecs.open(path, 'r', 'utf-8-sig') as f:
        json_data = json.load(f)
        modsDB = json_data
    f.close()

jsonFileDBs = ['item_bases_weapon_kr', 'item_bases_kr', 'item_bases_armour_kr']
itemDB = []
for jDB in jsonFileDBs:
    path = os.path.dirname(os.path.realpath(__file__)) + '\\' + 'data' + '\\' + jDB + '.json'
    with codecs.open(path, 'r', 'utf-8-sig') as f:
        json_data = json.load(f)
        itemDB.append(json_data)
    f.close()


GUITHREAD = None
GUIQ = mp.Queue()
ORBHELPERQ = mp.Queue()

# mod.json의 Prefix Suffix 항목들을 Lv 별로 재정리 
def sortAndFilt(moddict, itemLevel):

    itemLevel = int(itemLevel)
    valList = []
    for key, val in moddict.items():
        tmpDict = {key: val}
        valList.append(tmpDict)

    newlist = sorted(valList, key=lambda k: int([*k.values()][0]['lv']))
    filtList = list(filter(lambda k: int([*k.values()][0]['lv']) < itemLevel, newlist))

    #print(filtList)
    if filtList == []:
        return None, None, None


    minVal = [*filtList[0].values()][0]['val']
    maxVal = [*filtList[-1].values()][0]['val']

    detailStr = ''
    for data in filtList:
        for k, v in data.items():
            #detailStr += '-- affix: %-8s lv: %-3s stat: %s \n' % (k, v['lv'], v['val'])
            detailStr += '-- affix: ' + k.ljust(8) + 'lv: ' + v['lv'].ljust(4) + 'stat: ' + v['val'] + '\n'

    return minVal, maxVal, detailStr

def makeItemModStr(modStr, strToAdd):
    if modStr.find('\n') >= 0:
        tmpStr = modStr.split('\n')
        strToAdd += ' /'.join(tmpStr) + '\n'
    else:
        strToAdd += modStr + '\n'

    return strToAdd

def writeConfigFile(configFile, section, writeStr, opt = 'val'):
    config = configparser.ConfigParser(interpolation=None)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))

    result = section in config.sections()
    if result == False:
        config.add_section(section)

    config[section][opt] = writeStr

    with open(configFile, 'w', encoding='UTF-8-sig') as config_file:
        config.write(config_file)

def getCurrentItemExplict(modValReg, invenItem):

    comExpStringList = []

    if invenF.K_EXPLICT in invenItem:
        for expString in invenItem[invenF.K_EXPLICT]:
            comExpString = expString

            while True:
                ms = modValReg.search(comExpString)
                if ms == None:
                    break

                start, end = ms.span()
                comExpString = comExpString[:start] + '#' + comExpString[end:]
            comExpStringList.append(comExpString)

    return comExpStringList



# 특정 아이템의 레벨별 모드 정보를 불러옴 
def getPossibleMod(modValReg, invenItem, configFile, detail=False):

    if invenItem == None:
        return
    # pprint.pprint(result)
    print('-----------')

    affixDict = {}

    try:
        itemLv = int(invenItem[invenF.KG_ITEMLV])
    except:
        print('No ITEM LEVEL')


    itemClass = invenItem[invenF.K_ITEMCLASS]
    comExpStringList = []
    configModStr = ''
    if invenF.K_EXPLICT in invenItem:
        for expString in invenItem[invenF.K_EXPLICT]:
            comExpString = expString
            configModStr += expString + '\n'
            while True:
                ms = modValReg.search(comExpString)
                if ms == None:
                    break

                start, end = ms.span()
                comExpString = comExpString[:start] + '#' + comExpString[end:]
            comExpStringList.append(comExpString)
        configModStr = '\n' + configModStr
        writeConfigFile(configFile, SECTION_CUR, configModStr)

    print('Curren Item Explict:' + configModStr)
    print('----------------------------------------')
    configModStr = ''
    for mainClass, itemData in modsDB.items():
        if itemClass in itemData:
            configModStr += '-- Possible Mod on ' + itemClass + ' Item Level : ' + str(itemLv) + '\n'

            for affix in ['prefix', 'suffix']:
                configModStr += '- ' + affix + '\n'
                configModStr += '----------------------------------------' + '\n'
                affixDict[affix] = []

                for invenItemMod, modVal in itemData[itemClass][affix].items():
                    modMin, modMax, modStr = sortAndFilt(modVal['modval'], itemLv)
                    if modMin != None:
                        affixDict[affix].append(invenItemMod)
                        configModStr = makeItemModStr(invenItemMod, configModStr)
                        if detail == True:
                            configModStr += modStr

            configModStr += '----------------------------------------' + '\n'
            print(configModStr)
            # minMax = sortAndFilt(modVal['modval'], itemLv)
            # print('\t minmax: ', minMax)
            configModStr = '\n' + configModStr
            writeConfigFile(configFile, SECTION_POS, configModStr)

            break

            # for invenItemMod in comExpStringList:
            #     if invenItemMod in [*itemData[itemClass]['prefix']]:
            #         print(invenItemMod, 'is', ' prefix')
            #     elif invenItemMod in [*itemData[itemClass]['suffix']]:
            #         print(invenItemMod, 'is', ' suffix')
    return affixDict

# 입력한 조건식이 유효한지 판단 
def checkConditionValidation(conditionList, rarity, possibleMods):

    affixCntDict = {}
    returnModList = []
    for affix, condition in conditionList.items():
        affixCntDict[affix] = 0
        andModList = condition.split('&')
        andModList = [i.strip() for i in andModList]
        if andModList[0] == '':
            continue

        returnModList += andModList
    # 정규표현식
    # modStrRegEx = re.compile('([^|&]+)')
    # operatorRegEx = re.compile('([|&]+)')
    #
    # modList = modStrRegEx.findall(condition)
    # modList = [i.strip() for i in modList]
    #
    # opList = operatorRegEx.findall(condition)
    # opList = [i.strip() for i in opList]

        print('----------------------------------------')
        print(affix, ' Condition Mode List:', andModList)


        checkValidCondition = 0

        for orExpresion in andModList:
            condMods = orExpresion.split('|')
            condMods = [i.strip() for i in condMods]
            for condMod in condMods:
                for modDB in possibleMods[affix]:
                    if modDB.find(condMod) >= 0:
                        print(affix, ' cond: ', condMod, affix, ' db : ', modDB)
                        checkValidCondition = 1
                        break
                if checkValidCondition == 0:
                    print('no condition found on DB')
                    return False
                checkValidCondition = 0
            affixCntDict[affix] += 1

        print(affix + 'CNT : ' +  str(affixCntDict[affix]))
    print('----------------------------------------')

    totalCnt = 0
    for affix, cnt in affixCntDict.items():
        totalCnt += cnt
        if rarity == invenF.V_MAGIC:
            if cnt > 1:
                return False
        elif rarity == invenF.V_RARE:
            if cnt > 3:
                return False

    if totalCnt == 0:
        return False

    return returnModList


# 입력한 조건식과 실제 아이템의 Explict List 가 부합하는지 확인 
def compareConditionExpress(explictList, andModList):

    condition = 0
    condList = []

    for orExpresion in andModList:
        condMods = orExpresion.split('|')
        condMods = [i.strip() for i in condMods]
        for condMod in condMods:
            for itemMod in explictList:
                if itemMod.find(condMod) >= 0:
                    condition = True
        condList.append(condition)
        condition = False

    if False in condList:
        return False
    return True

    pass

# Gui 프로세스 
def guiProcess(gui_q, orbHelper_q, guiInitPosSize, configFile):
    if guiInitPosSize != None:
        initPos = guiInitPosSize[:2]
        initSize = guiInitPosSize[2:]
    else:
        initSize = None
        initPos = None
    app = gui.wx.App(False)
    frm = gui.AppFrame(gui_q, initSize, configFile)
    if initPos != None:
        gui.setFrmPosition(frm, initPos)
    frm.Show()
    app.MainLoop()
    orbHelper_q.put_nowait('end')

def runGui(guiInitPosSize, configFile):
    global GUITHREAD
    global GUIQ
    global ORBHELPERQ
    if GUITHREAD == None:
        GUITHREAD = mp.Process(target=guiProcess, args=(GUIQ, ORBHELPERQ, guiInitPosSize, configFile))
        GUITHREAD.start()
        #GUITHREAD.join()
    else:
        print('gui Alive')

def mainOrbHelper(keyboardKey, configFile):
    global GUITHREAD
    it = invenF.InventoryTool((2599, 1152, 1213, 513), [])
    modValReg = re.compile('([+.0-9]+)')

    moLState = 0
    moRState = 0
    listKeyState = [0] * len(keyboardKey)

    orbPicked = 0
    blockMouseOn = 0

    conditionList = False


    while True:
        time.sleep(0.001)
        valLeft = mo.is_pressed('left')
        valRight = mo.is_pressed('right')
        try:
            dataFromGui = ORBHELPERQ.get_nowait()
            if dataFromGui == 'end':
                GUITHREAD = None
                blockMouseOn = False
                conditionList = False
                orbPicked = 0

        except:
            dataFromGui = None
            pass

        if blockMouseOn == True:
            # -- left mouse
            if valLeft != moLState and valLeft == True and orbPicked == 1:
                GUIQ.put_nowait('item/wait')
                time.sleep(0.3)

                #GUIQ.put_nowait('item/match')

                result = it.getItemInfoForMods((0, 0), itemDB)
                if result != None:
                    itemExplictList = getCurrentItemExplict(modValReg, result)
                    print('item Explict : ', itemExplictList)
                    if itemExplictList != []:
                        matching = compareConditionExpress(itemExplictList, conditionList)
                        if matching == True:
                            print('Condition Matched')
                            blockMouseOn = False
                            conditionList = False
                            orbPicked = 0
                            GUIQ.put_nowait('item/match')
                        else:
                            print('Condition Not Matched')
                            GUIQ.put_nowait('item/missmatch')
                    else:
                        print('no item explict')


                moLState = valLeft
            elif valLeft != moLState and valLeft == False:
                orbPicked = 0
                moLState = valLeft

            # -- right mouse
            if valRight != moRState and valRight == True:
                moRState = valRight
                pass

            elif valRight != moRState and valRight == False:

                result = it.getItemInfoFromClipboard((0, 0))
                try:
                    if result[invenF.K_ITEMBASENAME].find('오브') >= 0:
                        msg = 'item/name/' + result[invenF.K_ITEMBASENAME]
                        GUIQ.put_nowait(msg)
                        orbPicked = 1
                except:
                    pass
                moRState = valRight

        # -- keys
        for idx, pressedKey in enumerate(keyboardKey):
            value = keys.is_pressed(pressedKey)
            if value == True:
                # F5
                if idx == 0 and listKeyState[idx] != value:
                    invenF.setActivePoe()
                    result = it.getItemInfoForMods((0, 0), itemDB)
                    getPossibleMod(modValReg, result, configFile)

                    listKeyState[idx] = value
                # F6
                elif idx == 1 and listKeyState[idx] != value:
                    invenF.setActivePoe()
                    result = it.getItemInfoForMods((0, 0), itemDB)
                    getPossibleMod(modValReg, result, configFile, detail=True)

                    listKeyState[idx] = value
                # F7
                elif idx == 2 and listKeyState[idx] != value:
                    invenF.setActivePoe()
                    listKeyState[idx] = value
                    # -- Condition Check 후 현제 아이템 상태가 조건을 만족하는지 확인
                    print('Mouse Blocker Start')
                    if blockMouseOn == False:
                        guiInitPosSize = gui.readConfigFile(configFile, gui.GUI_SECTION, gui.GUI_OPT_FRM_SIZE)
                        if guiInitPosSize != None:
                            guiInitPosSize = literal_eval(guiInitPosSize)

                        #runGui(guiInitPosSize)


                        config = configparser.ConfigParser(interpolation=None)
                        config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))
                        condDict ={
                            'prefix' : config[SECTION_COND]['prefix'],
                            'suffix' : config[SECTION_COND]['suffix']
                        }

                        result = it.getItemInfoForMods((0, 0), itemDB)
                        if result != None:
                            modDict = getPossibleMod(modValReg, result, configFile)
                            conditionList = checkConditionValidation(condDict, result[invenF.K_RARENESS], modDict)

                            if conditionList != False:



                                print('Condition you put: ', conditionList, 'is Valid')
                                itemExplictList = getCurrentItemExplict(modValReg, result)
                                print('item Explict : ', itemExplictList)
                                if itemExplictList != []:
                                    runGui(guiInitPosSize, configFile)

                                    condMatch = compareConditionExpress(itemExplictList, conditionList)
                                    print('Condition Match : ', condMatch)
                                    if condMatch:
                                        GUIQ.put_nowait('item/match')
                                else:
                                    print('this item have no explict')
                                    break
                            else:
                                print('Condition Expresion Error')
                                break
                        else:
                            break

                        blockMouseOn = True
                    else:
                        conditionList = False
                        blockMouseOn = False

            else:
                if listKeyState[idx] != value:
                    listKeyState[idx] = value

if __name__=="__main__":
    # multiprocessing code 를 excutable로 만들경우 아래 코드가 필요 
    mp.freeze_support()

    SECTION_ORB = 'orb_helper'
    SECTION_POS = 'possible_item_explict'
    SECTION_CUR = 'current_item_explict'
    SECTION_COND = 'tobe_item_explict'

    configFile = os.path.dirname(os.path.realpath(__file__)) + '\\' + 'config.cfg'

    config = configparser.ConfigParser(interpolation=None)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))

    getPosModKey = config[SECTION_ORB]['key_possible_mod']
    getPosModKeyDetail = config[SECTION_ORB]['key_possible_mod_detail']
    blockMouse = config[SECTION_ORB]['key_orb_mouse_block']



    print('orb helper started !')
    mainOrbHelper([getPosModKey, getPosModKeyDetail, blockMouse], configFile)



 

 

—gui.py—

import wx
import win32api
import win32con
import win32gui
import configparser
import os
import codecs
import threading

colVal = (0, 0, 0)
stopColor = (247, 245, 231)
GUI_SECTION = 'gui'
GUI_OPT_FRM_SIZE = 'fram_size'

def readConfigFile(configFile, section, opt):

    config = configparser.ConfigParser(interpolation=None)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))

    try:
        result = config[section][opt]
    except:
        result = None
    return result

def writeConfigFile(configFile, section, writeStr, opt = 'val'):

    config = configparser.ConfigParser(interpolation=None)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))

    result = section in config.sections()
    if result == False:
        config.add_section(section)

    config[section][opt] = writeStr

    with open(configFile, 'w', encoding='UTF-8-sig') as config_file:
        config.write(config_file)


def setFrmPosition(win, initPos):
    win.SetPosition(initPos)

class InvenPanel(wx.Panel):

    stisfiedTxt = "조건 만족 !!"

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
        self.SetBackgroundColour(wx.BLACK)
        self.mFrame = parent


        self.Bind(wx.EVT_LEFT_DCLICK, self.OnClick)

    #--------------------------------------------------------

    def OnClick(self, e):
        if e.LeftDClick():
            print('Click')

    def changeBackGround(self):
        self.SetBackgroundColour(stopColor)
        self.Refresh()
        print(self.GetSize())

        resultLabel = wx.StaticText(self, -1, self.stisfiedTxt, style=wx.ALIGN_CENTER)
        font = wx.Font(32, family=wx.DECORATIVE, style = wx.ITALIC, weight=wx.BOLD, underline=False)
        resultLabel.SetFont(font)
        resultLabel.SetForegroundColour(wx.Colour(255, 0, 0))

        resultLabel.Center()

    def setInitBackgroundColor(self):
        self.SetBackgroundColour(wx.BLACK)


class AppFrame(wx.Frame):

    satisfied = False
    resized = False
    moving = False
    pressedKey = ''


    def __init__(self, gui_q, initSize, configFile):
        if initSize != None:
            s = wx.Size(initSize[0], initSize[1])
        else:
            s = wx.DefaultSize
        self.configFile = configFile
        wx.Frame.__init__(self, None, title="Inventory", size=s,
                          style=wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP )

        self.statusbar = self.CreateStatusBar(1)

        self.gui_q = gui_q
        self.makeTransparent()

        self.pnl = InvenPanel(self)
        hbox = wx.BoxSizer(wx.HORIZONTAL)

        hbox.Add(self.pnl, 1, wx.EXPAND | wx.ALL, 5)
        self.SetSizer(hbox)
        self.Centre()

        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.Bind(wx.EVT_MOVE, self.OnMove)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_IDLE, self.OnIdle)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)

        self.qThreadKill = False
        self.qThread = threading.Thread(target=self.qReceive, args=(self.gui_q, ))
        self.qThread.daemon = True
        self.qThread.start()

        #end AppFrame class


    #--------------------------------------------------------

    def makeTransparent(self):

        hwnd = self.GetHandle()
        win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
                           win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)
        # colVal 에 해당하는 색을 가진 영역을 투명하게 변경 후 마우스 입력을 받도록 설정
        win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(*colVal), 0, win32con.LWA_COLORKEY)

    def makeTransparentWhole(self):
        # 전체 프레임을 투명하게 함
        hwnd = self.GetHandle()
        win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
                           win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)
        win32gui.SetLayeredWindowAttributes(hwnd, 0, 60, win32con.LWA_ALPHA)

    def changePnlColor(self):
        self.pnl.changeBackGround()

    def setStatusBarText(self, strs):
        self.statusbar.SetStatusText(strs)

    # --------------------------------------------------------

    def qReceive(self, msgQ):
        while not self.qThreadKill:
            if not msgQ.empty():
                msg = msgQ.get()
                if msg == 'item/match':
                    print('gui item match ')
                    #self.makeTransparent()
                    wx.CallAfter(self.changePnlColor)
                elif msg == 'item/wait':
                    wx.CallAfter(self.setStatusBarText, 'Wait ....')

                elif msg == 'item/missmatch':
                    wx.CallAfter(self.setStatusBarText, 'Miss Match')

                elif msg.find('item/name') >= 0:
                    pickedItem = os.path.basename(msg)
                    stText = pickedItem + 'Picked'
                    wx.CallAfter(self.setStatusBarText, stText)


        print('gui thread end')

    #--------------------------------------------------------
    def OnMove(self, e):
        self.moving = True


    def OnSize(self, e):
        e.Skip()
        self.resized = True


    def OnIdle(self, e):
        if self.resized:
            frmSize = tuple(self.GetPosition()) + tuple(self.GetSize())
            writeConfigFile(self.configFile, GUI_SECTION, str(frmSize), GUI_OPT_FRM_SIZE)
            self.resized = False

        elif self.moving:
            frmSize = tuple(self.GetPosition()) + tuple(self.GetSize())
            writeConfigFile(self.configFile, GUI_SECTION, str(frmSize), GUI_OPT_FRM_SIZE)
            #print(self.pnl.GetSize())
            self.moving = False
    #--------------------------------------------------------
    # 테스트
    def OnKeyDown(self, e):
        self.pressedKey = e.GetKeyCode()
        print(self.pressedKey)


    def OnKeyUp(self, e):
        if self.pressedKey == e.GetKeyCode():
            if self.pressedKey == wx.WXK_F1:
                print('Pressed')
                self.makeTransparent()
                self.pnl.changeBackGround()
                #self.pnl.SetBackgroundColour(wx.BLUE)

            self.pressedKey = ''
    # --------------------------------------------------------

    def OnCloseWindow(self, evt) :
        self.pnl.setInitBackgroundColor()
        self.qThreadKill = True
        self.Destroy()
    # --------------------------------------------------------
#-----------------------------------------------------
#
# class invenProcessor(wx.App):
#     def OnInit(self):
#         frame = AppFrame(None)
#         frame.Show(True)
#         return True


#end AppFrame class

#=======================================================

if __name__ == '__main__' :

    # test
    from ast import literal_eval
    import wx.lib.inspection
    import queue

    testQ = queue.Queue()

    configFile = os.path.dirname(os.path.realpath(__file__)) + '\\' + 'config.cfg'
    config = configparser.ConfigParser(interpolation=None)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))


    try:
        initPosSize = literal_eval(config[GUI_SECTION][GUI_OPT_FRM_SIZE])
        initPos = initPosSize[:2]
        initSize = initPosSize[2:]
    except:
        initSize = None
        initPos = None


    app = wx.App(False)
    frm = AppFrame(testQ, initSize)
    if initPos != None:
        setFrmPosition(frm, initPos)
    frm.Show()
    #wx.lib.inspection.InspectionTool().Show()
    app.MainLoop()


 

 

드럽게 길군요 ㅜ_ㅜ

여튼 실행은 python orbHeler.py 로 하시면 됩니다. 

나중에 정리 되면 git 에 올리던지 해야 겠어요 ㅜㅜ 여튼 코드는 위와 같습니다. 주석은 최대한 달았는데 .. 혹시 이해 안되시는 부분 있으시면 댓글 달아 주세요 ㅎㅎ 

 

네 오늘은 여기 까지입니다. !!

저번주에 개도 안걸린다는 여름 감기 걸려서 죽다 살아 났어요 ㅋㅋ 콧물이 거의 물처럼 흐르더라구요 감기 조심하시구 굿 라이프 온 룸 되세요 ~ ㅎ 

 



블로그 구독하기 !!

You may also like...

37 Responses

  1. ㅇㅇ 댓글:

    파이썬? 파이참 이런걸 잘 모르는 사람이라 설명이 좀 어렵네요 ㅎㅎ;
    파이참 깔고 config에서 prefix에 옵션 복사해서 넣고 ㅣ 또 다른거 적고 저장하니까 실행안되고 꺼지네요 ㅜㅜ

    • 호그 댓글:

      그냥 코드로 실행시키시나요 ?? ㅎㅎ혹시 EXE 파일을 원하시면 본문 상단에 파일이 있습니다. ㅎㅎ
      키를 누를실때 POE 게임창을 한번 클릭하시고 해보세요 !

  2. ㅇㅇ 댓글:

    1~3티어의 옵션을 전부 다 하나씩만 떠도 알람뜨게하고싶은데 어떻게해야하나요?

    • 호그 댓글:

      티어별 옵션은 지원하지 않습니다. ㅜㅜ 수치들을 전부 #처리 해서 비교하거든요 ㅜ
      F5를 눌러서 원하는 옵션이 있을 경우 그 옵션을 그대로 prefix 항목에 넣으시면 됩니다.

  3. ㅇㅇ 댓글:

    아 글쿤요 ㅎㅎ 티어별로 인식할수있다면 최고일테지만 지금도 훌륭하네요 ㅎㅎ 좋은자료감사합니다

    • 호그 댓글:

      ㅎㅎ 감사합니다. 티어 별로는 좀 어려운 점이 아이템에 대고 ctrl+c를 누르면 티어에 상관없이 옵션만나오는데 문제는 서로 다른 티어지만 같은 옵션의 경우 수치가 합쳐져서 나오더라구요 ㅜㅜ 이럴 경우 판단이 불가 하더라구요 ㅜ ㅎㅎ

  4. 행인 댓글:

    1. exe파일로 실행하는것과 파이썬으로 실행하는게 다른가요? 어제 exe로 해보니 잘 돌아가서 사실 굳이 사용법만 알면 exe로 해도 같은것 같아서요…

    2. 한 접두어에 여러 속성이 붙는건 어떻게 설정할수 있을까요? 예를들어 보석이 박힌 검 같은 경우, ‘황제의’나 ‘ ‘독재자의’같은 옵션은 접두어 하나에 물리피해와 정확도가 둘다 붙는데 .. 정확도는 접미어에 있다고 뜨거든요. 그러면 조건식을 접두어 물리피해 & 접미어 정확도
    이렇게 붙여야할까요?

    3. 혹시 해당 조건에 맞게 자동으로 돌아가게 하는건 없을까요? 어제 해보니 클릭을 너무 빨리하면 인식전에 넘어가버리는 경우가 생기는것같더라구요. 프로그램에서 알아서 클릭하고 확인하고 넘어가고 원하는 옵션 나오면 멈추게끔 ㅎㅎ

  5. 행인 댓글:

    댓글 남겼는데 지워졌네요 ㅠㅠ..길게 썼는데 그냥 간략하게 다시..

    1. exe파일로 실행하는것과 파이썬으로 실행하는것 차이가 있나요?
    2. 접두어 하나에 여러 옵이 붙는건 어떻게 조건을 줘야할까요? 만약 보석이 박힌 검 같은경우에 ‘독재자의’ 라는 접두어에 물리피해와 정확도가 같이 붙게 되는데 정확도 하나만 보면 접미어에 붙거든요.
    그냥 보이는대로 검색하니깐 이런 경우는 접두어 물리피해 & 접미어 정확도로 검색해야하나요?

    3. 혹시 자동으로 돌아가게하는건 없나요? 손으로 변화오브 바르다보면 너무 빠르게 클릭할때는 지나쳐버리는 경우들이 생기네요. 적당한 속도로 인식하게 해주는건 없을까요

    • 호그 댓글:

      1. 기능상의 차이는 없지만 성능상 코드를 실행 시키는게 조금 빠른거 같습니다. ㅎㅎ
      2. 이 경우는 아래 처럼 하는게 안전할 것 같습니다. 아이템에 ctrl+c 를 했을 때 접두어 접미어가 구별되어 나오지 않습니다. 즉 말씀하신 접두어 접미어가 모두 나올경우 프로그램은 물리피해 and 정확도 밬에 받지 못합니다.
      prefix = 물리피해
      suffix = 정확도
      3. 코드를 수정하면 자동으로 돌아가게 할 수 있겠지만 .. 지나치게 정책 위반이라서요 ..ㅎㅎ 일단 현제는 팝업창 밑에 검증이 완료되기 전에 waiting 이라고 뜨는데 이걸 보면서 바르는 수밬에 없습니다. ㅜ

  6. 원해요 댓글:

    혹시 쉐이퍼랑 엘더 옵션 추가할 계획없으신가요?

  7. 니퍼 댓글:

    혹시 아이템에 커서대놓고 F5

    눌럿을시 도스창에 템 정보까지 나오고 No item in DB 라고 뜨는데

    왜이런지 아시나요? DB파일은 위에꺼 그대로 가져온거고…혹시 신규템 DB라 안들어갓나 해서

    다른템 해봐도 모든템이 노 아이템 디비로 표기되네여

    • 호그 댓글:

      안녕하세요 ㅜㅜ 업데이트 되면서 ctrl+c 누를시 희귀도 Key가 아이템 희귀도로 변경되었어요 ㅜㅜ 다시 올려놨으니 다시 해보시기 바랄게요 ㅜ

  8. 니퍼 댓글:

    댓글이 삭제되었네요

    노 아이템 db라고 뜨면서 db를 못불러오는데 뭐가문제일까요?

    파일은 다있는상태입니다.

    • 호그 댓글:

      안녕하세요 ㅜㅜ 업데이트 되면서 ctrl+c 누를시 희귀도 Key가 아이템 희귀도로 변경되었어요 ㅜㅜ 다시 올려놨으니 다시 해보시기 바랄게요 ㅜ

  9. 행인 댓글:

    갑자기 오류가 생기네요. 아이템 f5로 누르면 cmd창에 NO item in DB라고 뜹니다

    • 호그 댓글:

      안녕하세요 ㅜㅜ 업데이트 되면서 ctrl+c 누를시 희귀도 Key가 아이템 희귀도로 변경되었어요 ㅜㅜ 다시 올려놨으니 다시 해보시기 바랄게요 ㅜ

  10. 익명 댓글:

    쉐이퍼 아이템은 옵션에 상관없이 explict에 “쉐이퍼 아이템”이라고만 나오네요. ㅠㅠ 쉐이퍼도추가부탁드립니다

  11. 익명 댓글:

    막누르고 싶은데 인식속도 빠르게 해주실순 업나요? ㅠㅠ

    • 호그 댓글:

      ㅠㅠ 일ㄷㄴ현제로썬 불가능합니다 다른 방법을 강구해 보고 있어요 ㅠㅠ

  12. 크래프터 댓글:

    어이쿠 아닙니다 정말 고생이 만으십니다 생기는것도 없는데 이런것 만들어주시니 너무 감사할따름입니다.
    아참 옷기 쉐이퍼 반지에 12레벨 암살징표 라던가 활에 포악한 투사체 같은 옵도 넣어주실수 있는지요..
    염치없지만 크래프팅이 낙이라;

    • 호그 댓글:

      넵 지금 쉐이퍼 엘더 옵션도 지원하도록 코드 만들고 있습니다. ㅎㅎ 조만간 올리도록 할게요

  13. 익명 댓글:

    F6누르면 디테일하게 수치까지 나오는데 이걸 적용시켜서 1등급 옵션을 띄우고 싶은데 어떻게 해야 하나요…??

    • 호그 댓글:

      지금은 1등급 옵션으로 조건 식을 만드는건 안됩니다. f6은 그냥 디테일한 옵션을 보고싶을 때 용도로만 사용해 주세요 ㅜㅜ

  14. 익명 댓글:

    여러가지 옵션을 넣는 방법은 없을까요 예를들면 프리픽스에서 옵션 둘중 하나만 떠도 만족하게

  15. 익명 댓글:

    여러가지 옵션을 넣는 방법은 없을까요 프리픽스에서 옵션 두가지를 넣어서 둘중 하나만 만족해도 뜰수있게

  16. poe 댓글:

    안녕하세요 너무너무 사용하고싶은 기능인데
    올려주신 exe 로 실행해서 누르면 not found / no item in db 가로 뜨는건 왜그럴까요? ㅠㅠ

    • 호그 댓글:

      혹시 어떤 아이템으로 하셨나요 ?? 지원 안되는 아이템 종류도 있습니다. ㅜㅜ

  17. 금연 댓글:

    안녕하세요
    혹시 이번 3.9 버전도 지원 가능하신지 알 수 있을까요?
    전쟁군주 베이스에 원하는 옵션이 있어서요..

    또, 매직 아이템을 크래프팅할때 접두나 접미 둘중 하나만 일치해도 성공 메세지가 나오게 할 수 있는 방법은 없을까요?

    좀 보기쉽게 말씀드리자면 지금의 유효성 검사는

    접두1 | 접두2 | 접두3 & 접미1 | 접미2 | 접미3 이런 방식을
    접두1 | 접두2 | 접두3 | 접미1 | 접미2 | 접미3 이렇게 될 수 있는지요?

    • 호그 댓글:

      아마 구글 드라이브에 올라가 있는 거는 되는 걸로 알고 있습니다. ㅎㅎ

      • 금연 댓글:

        네 말씀 감사합니다.
        사실 실행파일을 다운받아 사용해 봤는데 작동이 되질 않아서요 ㅜㅜ
        환경은 윈도우10이며 카스퍼스키 백신을 사용하고 있습니다.
        해상도는 2560×1440 이구요.

        프로그램창에서 f5나 f7을 눌렀을경우 반응이 없다고 여러차례 시도하면
        어떤 메세지가 순간적으로 보이다가 창이 닫혀버리네요.

        지금 그게 어떤 내용인지 확인하고 싶어서 파이썬 공부를 시작했습니다 ㅎㅎ

  18. 행상 댓글:

    f5눌러서 나온 옵션을 복사하여 prefix에 붙여넣기 하여 OK 버튼 누른후
    나오는 GUI 창을 유튜브처럼 인벤토리에 맞추고
    변화의 오브로 돌렸습니다 혹시 몰라서 가장 잘뜨는 생명력 최대치로 갑옷에 테스트 해봤는데
    생명력 최대치가 나왔음에도 불구하고 멈추는 GUI 창이뜨질않고.. Miss match 라고 나오네요..
    혹시 뭐를 확인해바야 하나요 ㅠ

  19. 행상1 댓글:

    뭐지 댓글이 안달리는건가..

  20. 행상2 댓글:

    테스트 해보고 있는데 원하는 옵션이 나왔는데도 GUI 창에서 마우스 클릭을 막는 창이 뜨질 않습니다..
    GUI창 밑에 메시지 에는 Miss Match 라고 나오고..
    orbHelper 에서는 Condition Not Matched 라고 나오네요….
    현재는 사용이 안되는 건가요??

    • 행상2 댓글:

      위에 테스트 해본 아이템은 십자군 베이스 판금 갑옷 입니다.