* integrated diablo scaffold still pending pathing & kill logic * minor changes to cross river of flame - but loop not working * River of Flame sucessfully skipped - CS Entrance cleared * Seal B works. * Everthing is slow :( * reparied slowness * revert deleted main code * Added CS Entrance recognition & changed Pentagram templates * Merge branch 'diablo' of https://github.com/jobithu/botty into diablo * Seal B works for both layouts - still clumsy, but it works. * added Vizier (A). working: A1, B1, B2 - pending: A2,C1,C2,Diaaaa * C identifcation implemented. * All seals done, optimization needed for pathing & fighting sequences * fixed minor error in game.ini * should now run - badly. all seal spots & paths need optimization * A1Y works stable * A1Y stable, WP now has issues * A2Y seal mouseover added - new bug, could not determine window offset * Both A seals work great now! * A2L Seals work consistently. A1Y quite good. * easteregg * A2L improvements * Succesful kill on A2L, B1S, C1G * Tweaked Diablo Kill Wait Time * start cleanup * added functions for each seal layout * First stable dia kills with fixed map layout * added Sealdance function * Fixed Sealdance. A1L is finished. All others work in Progress. * A2L + B1S Works * A2L and B1S works * Script runs well now on A1L, B1S, C1G. Sealdance needs more movement. * Update diablo.py * A1L, C2F, B2F working. Kills Dia Hell well. * added issue log. testing all layouts now. * Individual runs work, Sealdance needs tweaking * All seals work, got frustrated, added sorc&she rox * tweaked sorc vizier & infector fights * rebuild templates & nodes for A2Y, B1U, C2G * Forgot to add Seal A to the run oO * renamed forgotten templates for A2Y * fixed minor bugs on A1Y. succesfull dia kill * Optimized A2Y * Reworked A1Y templates & fight - very stable! Yay! * A2Y, B2U, C2G work - but i think it should be B1U * 4/6 seals work (B1S & C1F pending) * some clean up * remove unwated code * 5/6 Seals done - added C1F * sealdance and error handling * try to merge stuff somehow * add additional template coordinates to failed nodes * fix health manager by adding diablo lcoation * remove thresholds * fix template names and error handling for c seals * C1F added. 5/6 seals done. * All seals work - let's get it tested! * B1S fight coregraphy improvements * New paths for layout check to increase consistency * fixed A2Y edge lava port (625>626) * All seals work, lets test with more ppl & finetune * C1F & B1S switched to static pathing * Improved overall robustness * improved A layout check consistency * added A layout check consistency & CTA rebuff * Home now a loop, B now first seal to pop. * Trash clear at pentagram, B1S, C1F loop added. * Changed DeSeis attack pattern so MERC can kill * tweaked diablo spawn timer & vizier attack pattern * fixed minor pickit error. * fixed Layout B Check loop & improved C1F * improved C1F pathing home * improved B layout check by removing trash kill * added C2G static path to infector & loop home * tweaked pathing * lots of pathing tweaks to increase consistency * Clarified Logger.Info * Fixed A1L Tele to Lava Bug * Fixed A1Y Tele Lava bug * Fixed B2U missing pentagram on loop back * fixed logger * Completely reworked layout checks * Reworked C seal layout check * fixed ending up too low at Diablo * optimized loop home for C * added all seals * removed duplicate trash sequence at C * Fixed B2U looping home, added essence * Template Check Threshold raised for Layout Checks * Reverted A layout check back to inital version * Aggressive Layout Checks implemented. * Replaced Pentagram Templates to improve [602] * Fixed loops for new pent layout * Reverted A layout checks back to initial version * merged 6.1? i hope it worked. * Pressure Test: A2Y 98% layout check success * Massive improvements on Layout Checks * Integrated Skizot's A4 merchants * Fixed an error on A1L. * fixed a small error on final pickit * Slight Cleanup * tweaked A2Y nodes to avoid lava jumps * Added B2U Seal Logging * Add templ. to 611 & 610 to fix stuck pather * WiZ/SwePow suggestions * Some more tweaking * Trying to improve B layout checks (not full run) * Seal B logic pressure test (no full run) * added timeouts to weak traversenodes (no full run) * Pacifist Version - just checking layouts no bosses * Pure Layoutcheck Pressure Test (double checks) * Pacifist mode: Layout Check * Update bot.py * Added Templates to improve Layoutchecks * Tweaked B2U Templates (Pacifist Version) * add B2U nodes & calibration to impr. layout check * added nodes to layout checks * Added A1L Calibration Node for Layoutcheck * Full running script * Fixed Calibrations - Full Run * fixed mousover keyerror * yet another mousover * fixed a pather issue on B1S, full run * yet another mousover. full run. * mousover... ful run * yet another mousover. full run * fixed C2G looping across pentagram * Update game.ini * WiZ testing * added seal_node to sealdance * added C1F layoutcheck nodes * Added Wiz Speedrun * tweaked wait in sealdance * A1L-Static path to Pentagrram * added A2Y hop 622 * A2Y static path home * B1S static path home * changed C1F fake template * added new C1F seals & static home * Speed or no speed Using kill_cs_trash config * fixed a 36b key error * Random guess actually is random now It wasnt random at all before so trying out if this is better * Minor fixes and tweaks * B2U - Static Path Home * Redworked C1F - not stable yet * Seal order B,C,A and DeSeis fix There was copy/paste bug in DeSeis making it use wrong atk_len and then I also tuned the attacks a bit. * fixed final B2U issue, should be stable now! * added B2U 644_646 static path * added mapping * A seals fixed hopefully More testing tomorrow! * run diablo py Yours is the diabloC, I didnt have time to compare them before sleep and wanted you to have both in case * so this should be super stable now! * Reverted A1L & A2Y Seal Order (but kept WiZ Idea) * Reverted Seal Order * Added Rudi TP :) * all loop home now * added TP template to loop * typo * stashs & shrine gfx, TP counter * removed pent_6 (many false positives looping home) * clean up for aeon * fixed typos * New A2Y & C2G home pathing * Reworked C1F 704 to avoid stuck * Ready to test! * fixed pather clicking outside D2R * Tweaked Seal A * Loot to Sealdance() & A2Y tweaks * removed FALSE from TP fail * Updated Readme.md * remove env * clean up code * account for used tp Co-authored-by: aeon0 <[email protected]> Co-authored-by: WiZ <[email protected]>
269 lines
14 KiB
Python
269 lines
14 KiB
Python
import configparser
|
|
import numpy as np
|
|
import os
|
|
import re
|
|
from dataclasses import dataclass
|
|
from logger import Logger
|
|
|
|
@dataclass
|
|
class ItemProps:
|
|
pickit_type: int = 0
|
|
include: list[str] = None
|
|
exclude: list[str] = None
|
|
include_type: str = "OR"
|
|
exclude_type: str = "OR"
|
|
|
|
class Config:
|
|
def _select_val(self, section: str, key: str = None):
|
|
if section in self._custom and key in self._custom[section]:
|
|
return self._custom[section][key]
|
|
elif section in self._config:
|
|
return self._config[section][key]
|
|
elif section in self._pickit_config:
|
|
return self._pickit_config[section][key]
|
|
elif section in self._shop_config:
|
|
return self._shop_config[section][key]
|
|
else:
|
|
return self._game_config[section][key]
|
|
|
|
def __init__(self, print_warnings: bool = False):
|
|
# print_warnings, what a hack... here it is, not making the effort
|
|
# passing a single config instance through bites me in the ass
|
|
self._print_warnings = print_warnings
|
|
self._config = configparser.ConfigParser()
|
|
self._config.read('config/params.ini')
|
|
self._game_config = configparser.ConfigParser()
|
|
self._game_config.read('config/game.ini')
|
|
self._pickit_config = configparser.ConfigParser()
|
|
self._pickit_config.read('config/pickit.ini')
|
|
self._shop_config = configparser.ConfigParser()
|
|
self._shop_config.read('config/shop.ini')
|
|
self._custom = configparser.ConfigParser()
|
|
if os.environ.get('RUN_ENV') != "test" and os.path.exists('config/custom.ini'):
|
|
self._custom.read('config/custom.ini')
|
|
|
|
self.general = {
|
|
"saved_games_folder": self._select_val("general", "saved_games_folder"),
|
|
"name": self._select_val("general", "name"),
|
|
"monitor": int(self._select_val("general", "monitor")),
|
|
"max_game_length_s": float(self._select_val("general", "max_game_length_s")),
|
|
"exit_key": self._select_val("general", "exit_key"),
|
|
"resume_key": self._select_val("general", "resume_key"),
|
|
"auto_settings_key": self._select_val("general", "auto_settings_key"),
|
|
"restore_settings_from_backup_key": self._select_val("general", "restore_settings_from_backup_key"),
|
|
"settings_backup_key": self._select_val("general", "settings_backup_key"),
|
|
"graphic_debugger_key": self._select_val("general", "graphic_debugger_key"),
|
|
"logg_lvl": self._select_val("general", "logg_lvl"),
|
|
"randomize_runs": bool(int(self._select_val("general", "randomize_runs"))),
|
|
"difficulty": self._select_val("general", "difficulty"),
|
|
"message_api_type": self._select_val("general", "message_api_type"),
|
|
"custom_message_hook": self._select_val("general", "custom_message_hook"),
|
|
"discord_status_count": False if not self._select_val("general", "discord_status_count") else int(self._select_val("general", "discord_status_count")),
|
|
"discord_status_condensed": bool(int(self._select_val("general", "discord_status_condensed"))),
|
|
"info_screenshots": bool(int(self._select_val("general", "info_screenshots"))),
|
|
"loot_screenshots": bool(int(self._select_val("general", "loot_screenshots"))),
|
|
"d2r_path": self._select_val("general", "d2r_path"),
|
|
"restart_d2r_when_stuck": bool(int(self._select_val("general", "restart_d2r_when_stuck")))
|
|
}
|
|
|
|
# Added for dclone ip hunting
|
|
self.dclone = {
|
|
"region_ips": self._select_val("dclone", "region_ips"),
|
|
"dclone_hotip": self._select_val("dclone", "dclone_hotip"),
|
|
}
|
|
|
|
self.routes = {}
|
|
order_str = self._select_val("routes", "order").replace("run_eldritch", "run_shenk")
|
|
self.routes_order = [x.strip() for x in order_str.split(",")]
|
|
del self._config["routes"]["order"]
|
|
for key in self._config["routes"]:
|
|
self.routes[key] = bool(int(self._select_val("routes", key)))
|
|
|
|
self.char = {
|
|
"type": self._select_val("char", "type"),
|
|
"show_items": self._select_val("char", "show_items"),
|
|
"inventory_screen": self._select_val("char", "inventory_screen"),
|
|
"stand_still": self._select_val("char", "stand_still"),
|
|
"force_move": self._select_val("char", "force_move"),
|
|
"num_loot_columns": int(self._select_val("char", "num_loot_columns")),
|
|
"take_health_potion": float(self._select_val("char", "take_health_potion")),
|
|
"take_mana_potion": float(self._select_val("char", "take_mana_potion")),
|
|
"take_rejuv_potion_health": float(self._select_val("char", "take_rejuv_potion_health")),
|
|
"take_rejuv_potion_mana": float(self._select_val("char", "take_rejuv_potion_mana")),
|
|
"heal_merc": float(self._select_val("char", "heal_merc")),
|
|
"heal_rejuv_merc": float(self._select_val("char", "heal_rejuv_merc")),
|
|
"chicken": float(self._select_val("char", "chicken")),
|
|
"merc_chicken": float(self._select_val("char", "merc_chicken")),
|
|
"tp": self._select_val("char", "tp"),
|
|
"belt_rows": int(self._select_val("char", "belt_rows")),
|
|
"show_belt": self._select_val("char", "show_belt"),
|
|
"potion1": self._select_val("char", "potion1"),
|
|
"potion2": self._select_val("char", "potion2"),
|
|
"potion3": self._select_val("char", "potion3"),
|
|
"potion4": self._select_val("char", "potion4"),
|
|
"belt_rejuv_columns": int(self._select_val("char", "belt_rejuv_columns")),
|
|
"belt_hp_columns": int(self._select_val("char", "belt_hp_columns")),
|
|
"belt_mp_columns": int(self._select_val("char", "belt_mp_columns")),
|
|
"stash_gold": bool(int(self._select_val("char", "stash_gold"))),
|
|
"gold_trav_only": bool(int(self._select_val("char", "gold_trav_only"))),
|
|
"use_merc": bool(int(self._select_val("char", "use_merc"))),
|
|
"id_items": bool(int(self._select_val("char", "id_items"))),
|
|
"open_chests": bool(int(self._select_val("char", "open_chests"))),
|
|
"pre_buff_every_run": bool(int(self._select_val("char", "pre_buff_every_run"))),
|
|
"cta_available": bool(int(self._select_val("char", "cta_available"))),
|
|
"weapon_switch": self._select_val("char", "weapon_switch"),
|
|
"battle_orders": self._select_val("char", "battle_orders"),
|
|
"battle_command": self._select_val("char", "battle_command"),
|
|
"casting_frames": int(self._select_val("char", "casting_frames")),
|
|
"atk_len_arc": float(self._select_val("char", "atk_len_arc")),
|
|
"atk_len_trav": float(self._select_val("char", "atk_len_trav")),
|
|
"atk_len_pindle": float(self._select_val("char", "atk_len_pindle")),
|
|
"atk_len_eldritch": float(self._select_val("char", "atk_len_eldritch")),
|
|
"atk_len_shenk": float(self._select_val("char", "atk_len_shenk")),
|
|
"atk_len_nihlatak": float(self._select_val("char", "atk_len_nihlatak")),
|
|
"atk_len_diablo_vizier": float(self._select_val("char", "atk_len_diablo_vizier")),
|
|
"atk_len_diablo_deseis": float(self._select_val("char", "atk_len_diablo_deseis")),
|
|
"atk_len_diablo_infector": float(self._select_val("char", "atk_len_diablo_infector")),
|
|
"atk_len_diablo": float(self._select_val("char", "atk_len_diablo")),
|
|
"atk_len_cs_trashmobs": float(self._select_val("char", "atk_len_cs_trashmobs")),
|
|
"kill_cs_trash": float(self._select_val("char", "kill_cs_trash")),
|
|
"always_repair": bool(int(self._select_val("char", "always_repair"))),
|
|
}
|
|
|
|
# Sorc base config
|
|
sorc_base_cfg = dict(self._config["sorceress"])
|
|
if "sorceress" in self._custom:
|
|
sorc_base_cfg.update(dict(self._custom["sorceress"]))
|
|
# blizz sorc
|
|
self.blizz_sorc = dict(self._config["blizz_sorc"])
|
|
if "blizz_sorc" in self._custom:
|
|
self.blizz_sorc.update(dict(self._custom["blizz_sorc"]))
|
|
self.blizz_sorc.update(sorc_base_cfg)
|
|
# light sorc
|
|
self.light_sorc = dict(self._config["light_sorc"])
|
|
if "light_sorc" in self._custom:
|
|
self.light_sorc.update(dict(self._custom["light_sorc"]))
|
|
self.light_sorc.update(sorc_base_cfg)
|
|
# nova sorc
|
|
self.nova_sorc = dict(self._config["nova_sorc"])
|
|
if "nova_sorc" in self._custom:
|
|
self.nova_sorc.update(dict(self._custom["nova_sorc"]))
|
|
self.nova_sorc.update(sorc_base_cfg)
|
|
|
|
# Palandin config
|
|
self.hammerdin = self._config["hammerdin"]
|
|
if "hammerdin" in self._custom:
|
|
self.hammerdin.update(self._custom["hammerdin"])
|
|
|
|
# Assasin config
|
|
self.trapsin = self._config["trapsin"]
|
|
if "trapsin" in self._custom:
|
|
self.trapsin.update(self._custom["trapsin"])
|
|
|
|
# Barbarian config
|
|
self.barbarian = self._config["barbarian"]
|
|
if "barbarian" in self._custom:
|
|
self.barbarian.update(self._custom["barbarian"])
|
|
self.barbarian = dict(self.barbarian)
|
|
self.barbarian["cry_frequency"] = float(self.barbarian["cry_frequency"])
|
|
|
|
# Basic config
|
|
self.basic = self._config["basic"]
|
|
if "basic" in self._custom:
|
|
self.basic.update(self._custom["basic"])
|
|
|
|
# Basic Ranged config
|
|
self.basic_ranged = self._config["basic_ranged"]
|
|
if "basic_ranged" in self._custom:
|
|
self.basic_ranged.update(self._custom["basic_ranged"])
|
|
|
|
# Necro config
|
|
self.necro = self._config["necro"]
|
|
if "necro" in self._custom:
|
|
self.necro.update(self._custom["necro"])
|
|
|
|
self.advanced_options = {
|
|
"pathing_delay_factor": min(max(int(self._select_val("advanced_options", "pathing_delay_factor")), 1), 10),
|
|
"message_headers": self._select_val("advanced_options", "message_headers"),
|
|
"message_body_template": self._select_val("advanced_options", "message_body_template"),
|
|
"message_highlight": bool(int(self._select_val("advanced_options", "message_highlight"))),
|
|
"d2r_windows_always_on_top": bool(int(self._select_val("advanced_options", "d2r_windows_always_on_top"))),
|
|
"graphic_debugger_layer_creator": bool(int(self._select_val("advanced_options", "graphic_debugger_layer_creator"))),
|
|
}
|
|
|
|
self.items = {}
|
|
for key in self._pickit_config["items"]:
|
|
try:
|
|
self.items[key] = self.parse_item_config_string(key)
|
|
if self.items[key].pickit_type and not os.path.exists(f"./assets/items/{key}.png") and self._print_warnings:
|
|
print(f"Warning: You activated {key} in pickit, but there is no img available in assets/items")
|
|
except ValueError as e:
|
|
if self._print_warnings:
|
|
print(f"Error with pickit config: {key} ({e})")
|
|
|
|
self.colors = {}
|
|
for key in self._game_config["colors"]:
|
|
self.colors[key] = np.split(np.array([int(x) for x in self._select_val("colors", key).split(",")]), 2)
|
|
|
|
self.ui_pos = {}
|
|
for key in self._game_config["ui_pos"]:
|
|
self.ui_pos[key] = int(self._select_val("ui_pos", key))
|
|
|
|
self.ui_roi = {}
|
|
for key in self._game_config["ui_roi"]:
|
|
self.ui_roi[key] = np.array([int(x) for x in self._select_val("ui_roi", key).split(",")])
|
|
|
|
self.path = {}
|
|
for key in self._game_config["path"]:
|
|
self.path[key] = np.reshape(np.array([int(x) for x in self._select_val("path", key).split(",")]), (-1, 2))
|
|
|
|
self.shop = {
|
|
"shop_trap_claws": bool(int(self._select_val("claws", "shop_trap_claws"))),
|
|
"shop_melee_claws": bool(int(self._select_val("claws", "shop_melee_claws"))),
|
|
"shop_3_skills_ias_gloves": bool(int(self._select_val("gloves", "shop_3_skills_ias_gloves"))),
|
|
"shop_2_skills_ias_gloves": bool(int(self._select_val("gloves", "shop_2_skills_ias_gloves"))),
|
|
"trap_min_score": int(self._select_val("claws", "trap_min_score")),
|
|
"melee_min_score": int(self._select_val("claws", "melee_min_score")),
|
|
"shop_hammerdin_scepters": bool(int(self._select_val("scepters", "shop_hammerdin_scepters"))),
|
|
}
|
|
|
|
def parse_item_config_string(self, key: str = None) -> ItemProps:
|
|
item_props = ItemProps()
|
|
# split string by commas NOT contained within parentheses
|
|
item_string_as_list = re.split(r',\s*(?![^()]*\))', self._select_val("items", key).upper())
|
|
trim_strs=["AND(", "OR(", "(", ")", " "]
|
|
clean_string = [re.sub(r'|'.join(map(re.escape, trim_strs)), '', x).strip() for x in item_string_as_list]
|
|
item_props.pickit_type = int(clean_string[0])
|
|
try:
|
|
item_props.include = clean_string[1].split(',') if clean_string[1] else None
|
|
item_props.include_type = "AND" if "AND" in item_string_as_list[1] else "OR"
|
|
except IndexError as error:
|
|
pass
|
|
except Exception as exception:
|
|
Logger.error(f"Item parsing error: {exception}")
|
|
try:
|
|
item_props.exclude = clean_string[2].split(',') if clean_string[2] else None
|
|
item_props.exclude_type = "AND" if "AND" in item_string_as_list[2] else "OR"
|
|
except IndexError as error:
|
|
pass
|
|
except Exception as exception:
|
|
Logger.error(f"Item parsing error: {exception}")
|
|
return item_props
|
|
|
|
if __name__ == "__main__":
|
|
config = Config(print_warnings=True)
|
|
|
|
# Check if any added items miss templates
|
|
for k in config.items:
|
|
if not os.path.exists(f"./assets/items/{k}.png"):
|
|
print(f"Template not found: {k}")
|
|
|
|
# Check if any item templates miss a config
|
|
for filename in os.listdir(f'assets/items'):
|
|
filename = filename.lower()
|
|
if filename.endswith('.png'):
|
|
item_name = filename[:-4]
|
|
blacklist_item = item_name.startswith("bl__")
|
|
if item_name not in config.items and not blacklist_item:
|
|
print(f"Config not found for: " + filename)
|