Files
my-botty/src/npc_manager.py
T

344 lines
18 KiB
Python

import time
import os
import numpy as np
from input_layer import keyboard
import template_finder
from config import Config
from screen import grab
from ui_manager import ScreenObjects, center_mouse, is_visible, wait_until_hidden
from utils.misc import color_filter, wait
from logger import Logger
from input_layer import mouse
from math import sqrt
class Npc:
#A1
KASHYA = "kashya"
CHARSI = "charsi"
AKARA = "akara"
CAIN = "cain"
#A2
FARA = "fara"
DROGNAN = "droganan"
LYSANDER = "lysander"
#A3
ORMUS = "ormus"
#A4
TYRAEL = "tyrael"
JAMELLA = "jamella"
HALBU = "halbu"
#A5
QUAL_KEHK = "qual_kehk"
MALAH = "malah"
LARZUK = "larzuk"
ANYA = "anya"
def _build_npcs():
"""Build the npcs dict — deferred so the module can import on systems
missing template assets (e.g. headless Linux or incomplete clones)."""
return {
Npc.QUAL_KEHK: {
"name_tag_white": color_filter(template_finder.get_template("QUAL_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("QUAL_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"resurrect": {
"white": color_filter(template_finder.get_template("RESURRECT"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("RESURRECT_BLUE"), Config().colors["blue"])[1],
"red": color_filter(template_finder.get_template("RESURRECT"), Config().colors["red"])[1],
}
},
"template_group": ["QUAL_0", "QUAL_45", "QUAL_45_B", "QUAL_90", "QUAL_135", "QUAL_135_B", "QUAL_135_C", "QUAL_180", "QUAL_180_B", "QUAL_225", "QUAL_225_B", "QUAL_270", "QUAL_315"],
"roi": [225, 57, (850-225), (442-57)],
"poses": [[350, 140], [310, 268], [385, 341], [481, 196], [502, 212], [771, 254]]
},
Npc.MALAH: {
"name_tag_white": color_filter(template_finder.get_template("MALAH_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("MALAH_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["MALAH_FRONT", "MALAH_BACK", "MALAH_45", "MALAH_SIDE", "MALAH_SIDE_2"],
"roi": [383, 193, (762-383), (550-193)],
"poses": [[445, 485], [526, 473], [602, 381], [623, 368], [641, 323], [605, 300], [622, 272], [638, 284], [677, 308], [710, 288]]
},
Npc.LARZUK: {
"name_tag_white": color_filter(template_finder.get_template("LARZUK_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("LARZUK_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade_repair": {
"white": color_filter(template_finder.get_template("TRADE_REPAIR"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_REPAIR_BLUE"), Config().colors["blue"])[1],
}
},
"roi": [570, 70, (1038-570), (290-70)],
"template_group": ["LARZUK_FRONT", "LARZUK_BACK", "LARZUK_SIDE", "LARZUK_SIDE_2", "LARZUK_SIDE_3"],
"poses": [[733, 192], [911, 143]]
},
Npc.ANYA: {
"name_tag_white": color_filter(template_finder.get_template("ANYA_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("ANYA_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["ANYA_FRONT", "ANYA_BACK", "ANYA_SIDE"]
},
Npc.TYRAEL: {
"name_tag_white": color_filter(template_finder.get_template("TYRAEL_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("TYRAEL_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"resurrect": {
"white": color_filter(template_finder.get_template("RESURRECT"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("RESURRECT_BLUE"), Config().colors["blue"])[1],
"red": color_filter(template_finder.get_template("RESURRECT"), Config().colors["red"])[1],
}
},
"roi": [569, 86, (852-569), (357-86)],
"template_group": ["TYRAEL_1", "TYRAEL_2"]
},
Npc.ORMUS: {
"name_tag_white": color_filter(template_finder.get_template("ORMUS_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("ORMUS_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
}
},
"roi": [444, 13, (816-444), (331-13)],
"poses": [[526, 131], [602, 192], [698, 218], [756, 188]],
"template_group": ["ORMUS_0", "ORMUS_1", "ORMUS_2", "ORMUS_3", "ORMUS_4", "ORMUS_5"]
},
Npc.FARA: {
"name_tag_white": color_filter(template_finder.get_template("FARA_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("FARA_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade_repair": {
"white": color_filter(template_finder.get_template("TRADE_REPAIR"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_REPAIR_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["FARA_LIGHT_1", "FARA_LIGHT_2", "FARA_LIGHT_3", "FARA_LIGHT_4", "FARA_LIGHT_5", "FARA_LIGHT_6", "FARA_LIGHT_7", "FARA_LIGHT_8", "FARA_LIGHT_9", "FARA_MEDIUM_1", "FARA_MEDIUM_2", "FARA_MEDIUM_3", "FARA_MEDIUM_4", "FARA_MEDIUM_5", "FARA_MEDIUM_6", "FARA_MEDIUM_7", "FARA_DARK_1", "FARA_DARK_2", "FARA_DARK_3", "FARA_DARK_4", "FARA_DARK_5", "FARA_DARK_6", "FARA_DARK_7"]
},
Npc.DROGNAN: {
"name_tag_white": color_filter(template_finder.get_template("DROGNAN_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("DROGNAN_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["DROGNAN_FRONT", "DROGNAN_LEFT", "DROGNAN_RIGHT_SIDE"]
},
Npc.LYSANDER: {
"name_tag_white": color_filter(template_finder.get_template("LYSANDER_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("LYSANDER_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["LYSANDER_FRONT", "LYSANDER_BACK", "LYSANDER_SIDE", "LYSANDER_SIDE_2"]
},
Npc.CAIN: {
"name_tag_white": color_filter(template_finder.get_template("CAIN_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("CAIN_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"identify": {
"white": color_filter(template_finder.get_template("IDENTIFY"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("IDENTIFY_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["CAIN_0", "CAIN_1", "CAIN_2", "CAIN_3"]
},
Npc.JAMELLA: {
"name_tag_white": color_filter(template_finder.get_template("JAMELLA_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("JAMELLA_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
},
"gamble": {
"white": color_filter(template_finder.get_template("GAMBLE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("GAMBLE_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["JAMELLA_FRONT", "JAMELLA_BACK", "JAMELLA_SIDE", "JAMELLA_SIDE_2", "JAMELLA_SIDE_3", "JAMELLA_DRAWING"]
},
Npc.HALBU: {
"name_tag_white": color_filter(template_finder.get_template("HALBU_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("HALBU_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade_repair": {
"white": color_filter(template_finder.get_template("TRADE_REPAIR"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_REPAIR_BLUE"), Config().colors["blue"])[1],
}
},
"template_group": ["HALBU_FRONT", "HALBU_BACK", "HALBU_SIDE", "HALBU_SIDE_2"]
},
Npc.AKARA: {
"name_tag_white": color_filter(template_finder.get_template("AKARA_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("AKARA_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade": {
"white": color_filter(template_finder.get_template("TRADE"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_BLUE"), Config().colors["blue"])[1],
}
},
"roi": [603, 176, (1002-603), (478-176)],
"poses": [[692, 377], [834, 378], [948, 345], [867, 290], [696, 317]],
"template_group": ["AKARA_FRONT", "AKARA_BACK", "AKARA_SIDE", "AKARA_SIDE_2"]
},
Npc.CHARSI: {
"name_tag_white": color_filter(template_finder.get_template("CHARSI_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("CHARSI_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"trade_repair": {
"white": color_filter(template_finder.get_template("TRADE_REPAIR"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("TRADE_REPAIR_BLUE"), Config().colors["blue"])[1],
}
},
"roi": [249, 76, (543-249), (363-76)],
"poses": [[331, 227], [368, 284], [484, 174]],
"template_group": ["CHARSI_FRONT", "CHARSI_BACK", "CHARSI_SIDE", "CHARSI_SIDE_2", "CHARSI_SIDE_3"]
},
Npc.KASHYA: {
"name_tag_white": color_filter(template_finder.get_template("KASHYA_NAME_TAG_WHITE"), Config().colors["white"])[1],
"name_tag_gold": color_filter(template_finder.get_template("KASHYA_NAME_TAG_GOLD"), Config().colors["gold"])[1],
"action_btns": {
"resurrect": {
"white": color_filter(template_finder.get_template("RESURRECT"), Config().colors["white"])[1],
"blue": color_filter(template_finder.get_template("RESURRECT_BLUE"), Config().colors["blue"])[1],
"red": color_filter(template_finder.get_template("RESURRECT"), Config().colors["red"])[1],
}
},
"template_group": ["KASHYA_FRONT", "KASHYA_BACK", "KASHYA_SIDE", "KASHYA_SIDE_2"]
}
}
npcs = {}
try:
npcs = _build_npcs()
except Exception:
pass # npcs stays empty on headless/incomplete setups — populated lazily at runtime
def escape_dialogue(img) -> np.ndarray:
while is_visible(ScreenObjects.NPCDialogue, img):
keyboard.send("esc")
if wait_until_hidden(ScreenObjects.NPCDialogue, 0.5):
break
img = grab()
return img
def open_npc_menu(npc_key: Npc) -> bool:
global npcs
roi = Config().ui_roi["cut_skill_bar"]
roi_npc_search = Config().ui_roi["search_npcs"]
# Search for npc name tags by hovering to all template locations that are found
start = time.time()
attempts = 0
while (time.time() - start) < 35:
img = grab()
results = []
for key in npcs[npc_key]["template_group"]:
if attempts == 0 and "roi" in npcs[npc_key] and (time.time() - start) < 6:
roi_npc = npcs[npc_key]["roi"]
else:
roi_npc = roi_npc_search
res = template_finder.search(key, img, threshold=0.35, roi=roi_npc)
if res.valid:
is_unique = True
for r in results:
if (abs(r["pos"][0] - res.center_monitor[0]) + abs(r["pos"][1] - res.center_monitor[1])) < 22:
is_unique = False
break
if is_unique:
min_dist=10000
if attempts == 0 and "poses" in npcs[npc_key]:
# find distance between template match and nearest pose (([x2] - x1)**2 + (y2 - y1)**2)
for pose in npcs[npc_key]["poses"]:
dist = sqrt((res.center_monitor[0] - pose[0])**2 + (res.center_monitor[1] - pose[1])**2)
min_dist = dist if dist < min_dist else min_dist
results.append({"pos": res.center_monitor, "score": res.score, "combo": min_dist / (res.score**2)})
# sort by composite of template match score and distance to NPC pose
results = sorted(results, key=lambda r: r["combo"])
for result in results:
mouse.move(*result["pos"], randomize=3, delay_factor=[0.3, 0.5])
wait(0.2, 0.3)
img = grab()
img = escape_dialogue(img)
_, filtered_inp_w = color_filter(img, Config().colors["white"])
_, filtered_inp_g = color_filter(img, Config().colors["gold"])
res_w = template_finder.search(npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.9, roi=roi).valid
res_g = template_finder.search(npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.9, roi=roi).valid
if res_w:
mouse.click(button="left")
attempts += 1
wait(0.7, 1.0)
_, filtered_inp = color_filter(grab(), Config().colors["gold"])
res = template_finder.search(npcs[npc_key]["name_tag_gold"], filtered_inp, 0.9, roi=roi).valid
if res:
return True
elif res_g:
return True
return False
def press_npc_btn(npc_key: Npc, action_btn_key: str):
global npcs
img = grab()
img = escape_dialogue(img)
_, filtered_inp_w = color_filter(img, Config().colors["white"])
res = template_finder.search(
npcs[npc_key]["action_btns"][action_btn_key]["white"],
filtered_inp_w, 0.85, roi=Config().ui_roi["cut_skill_bar"]
)
if not res.valid and "blue" in npcs[npc_key]["action_btns"][action_btn_key]:
# search for highlighted / blue action btn
_, filtered_inp_b = color_filter(img, Config().colors["blue"])
res = template_finder.search(
npcs[npc_key]["action_btns"][action_btn_key]["blue"],
filtered_inp_b, 0.85, roi=Config().ui_roi["cut_skill_bar"]
)
if not res.valid and "red" in npcs[npc_key]["action_btns"][action_btn_key]:
# search for red action btn (can't afford - text turns red)
_, filtered_inp_r = color_filter(img, Config().colors["red"])
res = template_finder.search(
npcs[npc_key]["action_btns"][action_btn_key]["red"],
filtered_inp_r, 0.85, roi=Config().ui_roi["cut_skill_bar"]
)
if res.valid:
Logger.warning(f"Cannot afford {action_btn_key} (red button detected). Skipping...")
keyboard.send("esc")
wait(0.3)
return False
if res.valid:
mouse.move(*res.center_monitor, randomize=3, delay_factor=[1.0, 1.5])
wait(0.2, 0.4)
mouse.click(button="left")
wait(0.04, 0.08)
center_mouse()
else:
Logger.error(f"Could not find {action_btn_key} btn. Should not happen! Continue...")
keyboard.send("esc")
# Testing: Stand close to Qual-Kehk or Malah and run
if __name__ == "__main__":
from screen import grab
from config import Config
import os
from input_layer import keyboard
keyboard.add_hotkey('f12', lambda: os._exit(1))
keyboard.wait("f11")
open_npc_menu(Npc.MALAH)