Per-action jitter cannot reach the strongest remaining signals. Averaged over a session jitter converges; what does not converge is a player who starts at the same hour, plays the same length, and never does anything without a purpose. Session budget (session_budget_h, default 6). Stops the bot after roughly N hours, rolled per run at 0.65-1.35x so consecutive days differ in length. NOTE: this genuinely stops the bot — set to 0 for unlimited. Scheduled breaks were already implemented at bot.py:1140 and simply switched off (break_length_m=0). Enabled at 120m/15m, and the interval and duration are now RE-ROLLED after every break: a break at exactly 120 minutes every time is still a pattern, just a slower one than no break at all. Per-game chicken threshold. A fixed 0.40 every game is a precise tell, but the randomisation is deliberately one-directional: it only ever RAISES the threshold, capped at base+spread. Lowering it would cost deaths, and an uncapped Gaussian tail reached 0.55 on a 0.40 base, which throws away healthy games. Rolled on the BOT thread and handed to health_manager through a setter — that thread is a read-only monitor by design and must not roll it itself. Idle cursor drift during long idles; between actions the cursor otherwise sits exactly where the last click left it. Bot thread only, screen-bounds clamped. Occasional unproductive town action (open inventory, close it) and occasional walking past an item the filter wanted. Both are rolled behaviours the bot has never had — it otherwise picks up exactly what the rules say, instantly, every single time. The town action is strictly best-effort and can never fail a maintenance step. All seven appear in the STEALTH> manifest, so any of them going inert is visible at startup rather than after 225 games. DELIBERATELY NOT IMPLEMENTED: pathing node jitter and route variation. Both would be good cover, and both are the system that produced Bugs 25 and 28, where a fabricated node position walked the character into the town wall. Nothing here touches the health manager's potion path or the attack sequences either — today demonstrated that cost twice. Tests: 20. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
833 lines
33 KiB
INI
833 lines
33 KiB
INI
; There is detailed documentation for each parameter in the README.md
|
|
[general]
|
|
; Personal values can reference env keys from repo-root .env:
|
|
; Example syntax:
|
|
; custom_message_hook=${BOTTY_CUSTOM_MESSAGE_HOOK}
|
|
; custom_loot_message_hook=${ENV:BOTTY_CUSTOM_LOOT_MESSAGE_HOOK}
|
|
; bnet_name=${BOTTY_BNET_NAME}
|
|
; bnet_pass=${BOTTY_BNET_PASS}
|
|
; char_name=${BOTTY_CHAR_NAME}
|
|
; difficulty: game difficulty to create ("normal", "nightmare", "hell")
|
|
;
|
|
; Hammerdin difficulty guide (CTA build with Conviction aura):
|
|
; NORMAL - Very easy. 1-2h Hammer kills most packs. No CTA needed.
|
|
; NIGHTMARE - Recommended starting point. Bosses hit ~100-200 dmg.
|
|
; CTA + Conviction drops resists. Need decent armor (ED/HR).
|
|
; Gear targets: ~200 AR, ~15 ED, ~30% HR, ~50% FCR on hammer.
|
|
; HELL - Bosses hit 400-800+ dmg per swing. Conviction is mandatory.
|
|
; Gear targets: ~350+ AR, ~20+ ED, ~50%+ HR, ~60%+ FCR,
|
|
; ~150% IAS on weapon. Full Rejuv belt recommended.
|
|
; If you chicken/die repeatedly, drop to Nightmare first.
|
|
difficulty=hell
|
|
; name: bot profile name used in logs/messages and mod launch option replacement
|
|
name=profile1
|
|
; randomize_runs: 0 = run in listed order, 1 = shuffle run order
|
|
randomize_runs=0
|
|
; target_tz: target Terror Zone id (leave as default unless you know the mapping)
|
|
target_tz=1
|
|
; saved_games_folder: optional override path to D2R Saved Games folder (blank = auto)
|
|
saved_games_folder=
|
|
; level_max_steps: max pathing steps for leveling-style routines
|
|
level_max_steps=20
|
|
|
|
; Set to 1 to enable auto-login and auto-launch of D2R on startup.
|
|
; Credentials below are ONLY used when auto_login=1.
|
|
auto_login=0
|
|
bnet_name=${BOTTY_BNET_NAME}
|
|
bnet_pass=${BOTTY_BNET_PASS}
|
|
|
|
; Character name to auto-select from the character selection screen
|
|
; If empty, bot relies on the saved character template from previous sessions
|
|
char_name=${BOTTY_CHAR_NAME}
|
|
|
|
; messaging
|
|
; custom_loot_message_hook: optional separate webhook for loot notifications
|
|
custom_loot_message_hook=${BOTTY_CUSTOM_LOOT_MESSAGE_HOOK}
|
|
; custom_message_hook: main webhook for status/death/chicken messages
|
|
custom_message_hook=${BOTTY_CUSTOM_MESSAGE_HOOK}
|
|
; discord_log_chicken: 1 = send chicken/death style notifications
|
|
discord_log_chicken=1
|
|
; discord_log_errors: 1 = send a Discord message + error screenshot every time a
|
|
; run fails (approach/battle/exception). Set to 0 to keep error screenshots on
|
|
; disk only. Can also be toggled via [discord_events] error=0.
|
|
discord_log_errors=1
|
|
; discord_status_runs: send periodic status every X completed runs (blank/0 disables)
|
|
discord_status_runs=10
|
|
; discord_status_count: legacy fallback, send periodic status every X games (blank/0 disables)
|
|
discord_status_count=20
|
|
; discord_timing_report_h: post a timing + failure digest to Discord every N hours
|
|
; (0 disables). Aggregated from the same TL> timeline the log uses, so the report and the
|
|
; log can never disagree. The window resets on every send.
|
|
discord_timing_report_h=2
|
|
; pickup_drought_window: warn/alert after this many consecutive games with zero
|
|
; item pickups. Raise this if you run a strict pickit and 0-pickup streaks are
|
|
; expected/normal for you (a fast boss-only rush route with a tight filter can
|
|
; easily go 10 games without a keep-worthy drop).
|
|
pickup_drought_window=10
|
|
; message_api_type: "" disables messaging, "discord" or "generic_api"
|
|
message_api_type=discord
|
|
|
|
; breaks
|
|
; break_length_m: scheduled break duration in minutes (0 = disabled)
|
|
break_length_m=15
|
|
; max_runtime_before_break_m: runtime before taking scheduled break (0 = disabled)
|
|
max_runtime_before_break_m=120
|
|
|
|
; timers / fail handling
|
|
; d2r_path: Diablo II: Resurrected install path
|
|
d2r_path=C:\Program Files (x86)\Diablo II Resurrected
|
|
; max_consecutive_fails: stop bot after this many failed runs in a row
|
|
max_consecutive_fails=5
|
|
; max_game_length_s: emergency timeout per run/game
|
|
; 900: a full Chaos Sanctuary clear (3 seals + bosses + loot) takes ~10 min;
|
|
; 600 force-quit a game while literally waiting for Diablo to spawn (2026-06-10).
|
|
; Genuinely stuck games are caught much earlier by max_maintenance_time_s and
|
|
; approach step timeouts.
|
|
max_game_length_s=900
|
|
; max_maintenance_time_s: if the town maintenance loop (heal/buy/stash/repair) takes
|
|
; longer than this many seconds, save-and-exit and rejoin a fresh game.
|
|
; Prevents the bot staying stuck in A5 town forever when NPCs or pathing fail.
|
|
max_maintenance_time_s=240
|
|
; auto_downgrade_threshold: if combined chickens+deaths exceed this number within 1 hour,
|
|
; bot automatically lowers difficulty by one tier (hell->nightmare->normal) and restarts.
|
|
; Set to 0 to disable. (NOTE: feature is parsed but not yet active in bot logic)
|
|
auto_downgrade_threshold=0
|
|
|
|
restart_d2r_when_stuck=1
|
|
; hardcore: 1 enables hardcore-safe assumptions in some routines
|
|
hardcore=0
|
|
|
|
; screenshots
|
|
; info_screenshots: save screenshots for info/chicken/death events
|
|
info_screenshots=1
|
|
; error_screenshots: save a screenshot to log/screenshots/error/ every time a run
|
|
; fails (approach failure, battle failure, or an exception) so logs and visuals can
|
|
; be reviewed side by side. Falls back to info_screenshots if unset.
|
|
error_screenshots=1
|
|
; loot_screenshots: save screenshots for picked loot
|
|
loot_screenshots=0
|
|
; pickit_screenshots: save screenshots for pickit debugging
|
|
pickit_screenshots=1
|
|
|
|
; recovery
|
|
; disable_run_after_failures: after this many CONSECUTIVE failures of the same run
|
|
; (e.g. run_vizier), the bot disables just that run for the rest of the session and
|
|
; keeps doing the other runs instead of stopping. A single success resets the count.
|
|
; If every run gets disabled the bot stops for investigation. Set high to effectively
|
|
; disable this behaviour.
|
|
disable_run_after_failures=5
|
|
; stash_scan_interval: scan all stash tabs and export stash_list.csv every X runs (0 disables)
|
|
stash_scan_interval=0
|
|
|
|
[discord_events]
|
|
; Fine-grained Discord event toggles (1=send, 0=disable)
|
|
; status: periodic status + generic bot messages (breaks, shop notifications, etc.)
|
|
status=1
|
|
; item_keep: send kept item notifications (loot webhooks/embeds)
|
|
item_keep=1
|
|
; death: send death notifications
|
|
death=1
|
|
; chicken: send chicken (emergency leave) notifications
|
|
chicken=1
|
|
; stash_full: send stash full notifications
|
|
stash_full=1
|
|
; gold_full: send gold full notifications
|
|
gold_full=1
|
|
; error: send run-failure notifications (message + error screenshot)
|
|
error=1
|
|
|
|
[stealth]
|
|
; Multiplies all wait() calls by a random value in this range each call
|
|
; 1.0 = no change. Set range wider for more human-like timing variation.
|
|
; wait_jitter_* multiplies EVERY wait() in the codebase, not just mouse moves.
|
|
; The floor is clamped to jitter_min*0.8, so 0.85 allowed waits to come out 32%
|
|
; SHORT — the one place jitter stole time from an action instead of adding it
|
|
; between actions. 0.95 keeps waits at-or-longer (effective floor 0.76 vs the
|
|
; old 0.68, and the Gaussian centre sits above 1.0), so a wait can no longer
|
|
; expire before the UI it was waiting on has settled.
|
|
wait_jitter_min = 0.95
|
|
wait_jitter_max = 1.20
|
|
|
|
; Extra pixel variance added to every mouse click (on top of existing randomize=5)
|
|
; 0 = off, 10 = +/-10px extra random offset per click
|
|
; NOTE: click position randomization lives at the CALL SITES (npc_manager,
|
|
; ui/waypoint) where it is tuned to real button geometry — 2-3px for NPC
|
|
; hover targets, +/-9px inside a 47px waypoint button. There is deliberately
|
|
; no global click-offset knob: stacking one on top of those is what starts
|
|
; missing NPCs (see CLAUDE.md Bugs 3/4/6/7).
|
|
|
|
; Re-shuffle run order after completing a full rotation (vs only at session start)
|
|
; 0: keep [pindle, diablo] fixed so every game ENDS in A4 town (Diablo run TPs
|
|
; there) — the next game then spawns at A4 where Jamella/Cain/Tyrael work,
|
|
; avoiding the A5 Malah vendor entirely (stale templates in current patch).
|
|
reshuffle_each_rotation = 0
|
|
|
|
; Probability (0-100) of randomly skipping a run each game
|
|
; 0 = never skip, 20 = skip ~1 in 5 runs
|
|
skip_run_chance = 10
|
|
|
|
; Probability (0-100) of taking an unscheduled AFK break after any given run
|
|
afk_break_chance = 5
|
|
; Break duration range in minutes
|
|
afk_break_min_m = 2
|
|
afk_break_max_m = 12
|
|
|
|
; Vary run/battle duration by a Gaussian factor (default +/-15%)
|
|
; 0.0 = no variation, 0.3 = up to +/-30% variation
|
|
run_duration_variance = 0.15
|
|
|
|
; Micro-pause between actions (simulates human hesitation in milliseconds)
|
|
micro_pause_min_ms = 20
|
|
micro_pause_max_ms = 120
|
|
|
|
; Vary kill time to avoid perfectly consistent boss fight durations
|
|
; Varies boss attack duration. Only ever LENGTHENS the fight (1.0-1.4x) —
|
|
; shortening it risks leaving a boss alive, which is a failed run, not stealth.
|
|
vary_kill_time = 1
|
|
|
|
; Human mouse curve complexity (1.0 = default, 0.5 = more direct, 1.5 = more winding)
|
|
human_curve_complexity = 1.0
|
|
|
|
; Arrival-to-click delay: human-like pause between mouse arriving and clicking (milliseconds)
|
|
; 50-800ms range simulates "is this the right thing?" hesitation
|
|
; Hesitation between arriving at a target and clicking it. OFF by default:
|
|
; it applies to EVERY click, so the old 800ms ceiling could add minutes per
|
|
; run. Enable with click_delay_enabled=1 if you want it; 250 is a realistic
|
|
; ceiling that stays affordable.
|
|
click_delay_enabled = 0
|
|
click_delay_min_ms = 50
|
|
click_delay_max_ms = 250
|
|
|
|
; Key press duration variance: how long a key is held (milliseconds)
|
|
; Most presses are short (20-100ms), some linger (up to 200ms)
|
|
key_press_min_ms = 20
|
|
key_press_max_ms = 200
|
|
|
|
; Skill rotation hesitation: pause before casting a skill (milliseconds)
|
|
; Prevents machine-speed skill spam
|
|
skill_hesitation_min_ms = 80
|
|
skill_hesitation_max_ms = 300
|
|
|
|
; Wrong waypoint chance: 2-3% of selecting wrong TP portal then correcting
|
|
; Humans occasionally misclick waypoint targets
|
|
; ── session rhythm ───────────────────────────────────────────────────────────
|
|
; The strongest remaining signal is not per-click timing: averaged over a
|
|
; session jitter converges, but a player who starts at the same hour, plays the
|
|
; same length and never does anything unproductive does not.
|
|
;
|
|
; session_budget_h: stop the bot after roughly this many hours (0 = unlimited).
|
|
; Actual length is rolled per run at 0.65-1.35x, so consecutive days differ.
|
|
session_budget_h = 6
|
|
; Small cursor movement during long idles. Between actions the cursor otherwise
|
|
; sits exactly where the last click left it.
|
|
idle_drift_enabled = 1
|
|
; Per-game chicken threshold spread. Only ever RAISES the threshold (safer) —
|
|
; a fixed 0.40 every game is a precise tell, but lowering it would cost deaths.
|
|
chicken_variance = 0.08
|
|
; Chance of an unproductive town action (open inventory, close it).
|
|
town_browse_chance = 0.06
|
|
; Chance of walking past an item the filter wanted. Costs real loot — keep low.
|
|
pickup_skip_chance = 0.02
|
|
wrong_waypoint_chance = 0.025
|
|
|
|
; Skill mistake chance: 1-2% chance of miscasting and correcting
|
|
; Simulates human error during combat
|
|
; Presses a DIFFERENT bound skill before the intended one. Disabled: on a
|
|
; character mid-fight this swaps the active skill or aura, and the attack
|
|
; sequences rely on ending in a known skill state. The stealth value is small
|
|
; next to the risk of a stray keypress landing on something that is not a skill
|
|
; at all — which is exactly how the loot filter ended up being toggled all
|
|
; session (params.ini [paladin] vigor=f4, unbound on this char, F4 = filter).
|
|
; Set back to 0.015 to re-enable.
|
|
skill_mistake_chance = 0
|
|
|
|
[routes]
|
|
; Controls which farm runs Botty performs each game.
|
|
; "order" is a comma-delimited list and runs left-to-right when randomize_runs=0.
|
|
; If randomize_runs=1 (in [general]), Botty shuffles enabled runs each game.
|
|
;
|
|
; Hammerdin keyrun recommendation (stable-first):
|
|
; order=run_countess, run_arcane, run_nihlathak
|
|
;
|
|
; Route quick notes:
|
|
; run_trav (Act 3 Travincal council farm; short/high-density run)
|
|
; run_pindle (Act 5 Nihlathak temple red portal boss farm)
|
|
; run_eldritch (Act 5 Frigid Highlands Eldritch-only run)
|
|
; run_eldritch_shenk (Act 5 Eldritch then Shenk in same game)
|
|
; run_nihlathak (Act 5 Halls of Vaught, teleport strongly recommended)
|
|
; run_arcane (Act 2 Arcane Sanctuary / Summoner, teleport strongly recommended)
|
|
; run_diablo (Act 4 Chaos Sanctuary, teleport recommended)
|
|
; run_vizier (Act 4 Chaos Vizier-only route; lighter/faster than full Diablo run)
|
|
; run_andariel (Act 1 Catacombs)
|
|
; run_countess (Act 1 Forgotten Tower)
|
|
; run_mephisto (Act 3 Durance of Hate)
|
|
; run_baal (Act 5 Throne of Destruction)
|
|
; run_baal_xp (Join public Baal games, hide, collect XP, leave — see [baal_xp])
|
|
order=run_pindle, run_diablo
|
|
|
|
[char]
|
|
; ==========================
|
|
; ==== Mandatory Fields ====
|
|
; ==========================
|
|
; These configs have to be alligned with your d2r settings and char build
|
|
; type: character build profile to use (must match one section below)
|
|
; examples: blizz_sorc, hammerdin, fohdin
|
|
type=hammerdin
|
|
; belt_rows: in-game belt size (2/3/4)
|
|
belt_rows=4
|
|
; casting_frames: your char cast breakpoint (affects action timing)
|
|
casting_frames=8
|
|
; cta_casting_frames: cast breakpoint on CTA swap (if used)
|
|
cta_casting_frames=8
|
|
; attack_frames: base attack animation timing for non-cast attacks
|
|
attack_frames=15
|
|
; cta_available: 1 if Call to Arms swap exists, else 0
|
|
cta_available=1
|
|
;Do we want to cast non-cta buffs (ex energy shield) with cta
|
|
buff_with_cta=1
|
|
|
|
; safer_routines: enable for optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players)
|
|
safer_routines=1
|
|
|
|
; num_loot_columns: Number of empty columns from left to right of inventory to be used for looting.
|
|
; Store charms, etc. to the right of the inventory.
|
|
;
|
|
; This drives ui_roi[restricted_inventory_area] in config.py: the columns to the RIGHT of
|
|
; this count are protected — mouse._is_clicking_safe() cancels any click landing there, so
|
|
; the bot never sells, drops or moves anything in them. Both tomes MUST live in that
|
|
; reserved area: common.tome_state() only searches restricted_inventory_area for them.
|
|
; 3 columns = 12 loot slots, 28 reserved
|
|
; 4 columns = 16 loot slots, 24 reserved <- now
|
|
; 6 columns = 24 loot slots, 16 reserved (tried 2026-08-27, reverted — shrinking the
|
|
; reserve is the wrong direction when the
|
|
; books and the charms both live there)
|
|
; Lower this if the tomes or charms are running out of room; raise it only if the reserved
|
|
; area is provably empty.
|
|
num_loot_columns=4
|
|
|
|
; game hotkeys:
|
|
; NOTE: each key must match your in-game binding exactly
|
|
force_move=e
|
|
inventory_screen=i
|
|
potion1=1
|
|
potion2=2
|
|
potion3=3
|
|
potion4=4
|
|
; show_belt is different from the default hotkey as "~" is for many keyboards not reachable without also pressing altgr
|
|
show_belt=k
|
|
show_items=alt
|
|
; stand_still cannot be the default "shift" as it would interfere with merc healing
|
|
stand_still=capslock
|
|
; teleport: leave empty if you can't use
|
|
teleport=b
|
|
town_portal=6
|
|
; call to arms settings:
|
|
; weapon_switch/battle_orders/battle_command only used when cta_available=1
|
|
weapon_switch=w
|
|
battle_orders=7
|
|
battle_command=8
|
|
|
|
; ==========================
|
|
; ==== Optional configs ====
|
|
; ==========================
|
|
stash_gold=1
|
|
use_merc=1
|
|
|
|
; Attack length for barbarians should be as high as 8-10 and even 10-12 for trav/shenk
|
|
;
|
|
; Hammerdin attack lengths (seconds of hammer spam per boss):
|
|
; atk_len_trav = 4.0 (Council of 3 - 3 council members, easy)
|
|
; atk_len_pindle = 8.0 (Pindle — Hell: 13094-16070 HP, 75% fire, 100% poison)
|
|
; Pindle stats by difficulty:
|
|
; Normal: 1064-1588 HP, 75% fire, 70% poison
|
|
; Nightmare: 4773-5859 HP, 100% poison
|
|
; Hell: 13094-16070 HP, 75% fire, 50% cold, 33% light, 100% poison
|
|
; With Conviction (-33% res), Hell Pindle = 50% fire, 17% cold, 0% light, 67% poison
|
|
; CTA adds +100% dmg to hammer. Need ~60%+ FCR and good IAS to kill in 8s.
|
|
; atk_len_nihlathak = 4.0 (Nihlathak - single boss)
|
|
; atk_len_eldritch = 3.0 (Eldritch only - single boss)
|
|
; atk_len_shenk = 4.0 (Shenk only - single boss)
|
|
; atk_len_arc = 2.5 (Summoner in Arcane - single boss)
|
|
; atk_len_diablo = 10.0 (Diablo in CS - longest single boss)
|
|
; atk_len_countess = 3.0 (Countess - single boss, easy)
|
|
; atk_len_andariel = 4.0 (Andariel - single boss)
|
|
; atk_len_mephisto = 12.0 (Mephisto - single boss, high HP)
|
|
; atk_len_baal = 10.0 (Baal - single boss)
|
|
; atk_len_baal_waves = 30.0 (Baal's spawn waves before boss)
|
|
;
|
|
; Chaos Sanctuary (Diablo run) individual fights:
|
|
; atk_len_cs_trashmobs = 2.0 (Trash packs in CS)
|
|
; atk_len_diablo_vizier = 2.0 (Vizier of Chaos - seal boss A)
|
|
; atk_len_diablo_infector = 4.0 (Infector of Souls - seal boss C)
|
|
; atk_len_diablo_deseis = 5.0 (Lord De Seis - seal boss B, hardest seal)
|
|
;
|
|
; Increase these if your hammer kills slower (low IAS/FCR).
|
|
; Decrease if your hammer kills faster (high IAS/FCR, good gear).
|
|
; If you die during a fight, the attack length is too long for your defense.
|
|
atk_len_arc=2.5
|
|
atk_len_eldritch=3.0
|
|
atk_len_nihlathak=4.0
|
|
atk_len_pindle=8.0
|
|
atk_len_shenk=4.0
|
|
atk_len_diablo=3.0
|
|
atk_len_trav=4.0
|
|
|
|
; Boss run attack lengths (per-character defaults in kill_* methods)
|
|
; Adjust these if your build is faster/slower against these bosses
|
|
atk_len_andariel=4.0
|
|
atk_len_countess=3.0
|
|
atk_len_mephisto=12.0
|
|
atk_len_baal=10.0
|
|
atk_len_baal_waves=30.0
|
|
|
|
; Chaos Sanctuary settings
|
|
atk_len_cs_trashmobs=2.0
|
|
atk_len_diablo_deseis=5.0
|
|
atk_len_diablo_infector=4.0
|
|
atk_len_diablo_vizier=2.0
|
|
cs_mob_detect=0
|
|
; cs_town_visits is currently broken, ignore for now
|
|
cs_town_visits=0
|
|
kill_cs_trash=1
|
|
|
|
; Belt settings
|
|
belt_hp_columns=1
|
|
belt_mp_columns=1
|
|
belt_rejuv_columns=2
|
|
|
|
; Potion/chicken settings
|
|
take_health_potion=0.60
|
|
take_mana_potion=0.40
|
|
take_rejuv_potion_health=0.45
|
|
take_rejuv_potion_mana=0.10
|
|
heal_merc=0.70
|
|
heal_rejuv_merc=0.45
|
|
chicken=0.40
|
|
merc_chicken=0.20
|
|
|
|
; Misc.
|
|
; helps reduce accidental pickups when enabled especially on walking characters
|
|
enable_no_pickup=0
|
|
; fill_shared_stash_first: 1 = prefer shared stash tabs before personal stash
|
|
fill_shared_stash_first=0
|
|
; to gamble, add any/all of the following: circlet, ring, coronet, talon, amulet
|
|
gamble_items=
|
|
; open_chests: 1 = open clickable chests along path when possible
|
|
open_chests=1
|
|
; pre_buff_every_run: 1 = always recast buffs at run start
|
|
pre_buff_every_run=1
|
|
; runs_per_repair: visit repair vendor every X runs (blank/0 disables)
|
|
runs_per_repair=5
|
|
; repair_npc: preferred repair vendor strategy.
|
|
; - a5_larzuk (recommended: stays in A5, no cross-act trip; falls back to Halbu)
|
|
; - a4_halbu (requires WP trip to A4 every repair — act desync risk if it fails)
|
|
; 2026-06-10: switched to a5_larzuk — session logs showed Halbu detection failing
|
|
; 100% (body score ~0.39) and each failed A4 trip desynced the bot's act state.
|
|
repair_npc=a5_larzuk
|
|
; resurrect_npc: which NPC revives the mercenary. Blank = whichever act the character is
|
|
; already in. Set to a4_tyrael to always use Act 4 Tyrael.
|
|
; Measured 2026-08-27 on this setup:
|
|
; A4 Tyrael ok 8.2s, ok 24.9s - 0 errors, ever
|
|
; A5 Qual-Kehk fail 113.6s / 52.0s / 163.1s / 72.6s - 43 errors
|
|
; Tyrael stands on a fixed spot by the A4 waypoint; Qual-Kehk is the least reliable NPC in
|
|
; the route. A failed hunt costs more than the whole A5->A4 trip, and one 163s hunt pushed
|
|
; the town visit past max_maintenance_time_s and killed the game.
|
|
resurrect_npc=a4_tyrael
|
|
; runs_per_stash: stash/sell every X runs (blank/0 disables)
|
|
runs_per_stash=4
|
|
; sell_junk: 1 = vendor non-keep items automatically
|
|
sell_junk=1
|
|
; protect_shields_from_sell: 1 = never vendor items with "shield" in detected name.
|
|
; DISABLED 2026-08-27. This is redundant: the EQUIPPED shield is already protected
|
|
; positionally by mouse._is_clicking_safe(), which cancels any click landing in
|
|
; ui_roi[equipped_inventory_area] while the inventory is open. The name rule only ever
|
|
; hit shields sitting in the INVENTORY grid, which the pickit had already judged.
|
|
; Cost of leaving it on: 130 blocked sells in one day (FIEND SHIELD 64, AERIN SHIELD 41,
|
|
; HERALDIC SHIELD 23...). The same shields were re-evaluated and re-blocked every single
|
|
; game — "Discarding FIEND SHIELD." then "Blocked sell for protected item: FIEND SHIELD" —
|
|
; so they could never leave the pack and permanently occupied slots.
|
|
protect_shields_from_sell=0
|
|
; protect_charms_from_sell: 1 = never drop/vendor charms regardless of pickit verdict
|
|
; (safety net — a misread charm can't be un-sold). Set to 0 to let your pickit rules
|
|
; decide keep/discard for charms same as any other item.
|
|
;
|
|
; KEEP THIS AT 1. Charms only give their bonus while they sit in the INVENTORY, and the
|
|
; resistances matter in nightmare. Briefly set to 0 on 2026-08-27 to reclaim slots and
|
|
; that was wrong — it vendored LARGE CHARM OF FIRE, STOUT SMALL CHARM, SMALL CHARM OF
|
|
; FLAME, STOUT SMALL CHARM OF STRENGTH and LAPIS SMALL CHARM OF VITA (+20 life,
|
|
; cold resist +7%) before it was reverted.
|
|
;
|
|
; The "673 blocked sells" in the log are NOT a bug to fix by selling — they are the guard
|
|
; doing its job, repeatedly, on charms the pickit does not have a keep rule for. If those
|
|
; slots are needed, the answer is a pickit rule that keeps the good charms and stops
|
|
; picking up junk ones, NOT disabling this guard.
|
|
protect_charms_from_sell=1
|
|
; pick_rares_for_gold: 1 = pick all yellow (rare) ground items; non-keep rares will be sold
|
|
; Requires sell_junk=1 to convert extra pickups into gold.
|
|
pick_rares_for_gold=0
|
|
; pick_gold: 1 = pick up ground gold piles, 0 = ignore all ground gold
|
|
pick_gold=1
|
|
|
|
[transmute]
|
|
;stash tabs by priority where to put transmuted gems
|
|
stash_destination=0,1,2,3
|
|
; Add these possible gems to "transmute" to transmute them:
|
|
; chipped, flawed, standard, flawless
|
|
; DISABLED 2026-08-26: gems are stashed to the GEMS tab and left alone, no cube/convert
|
|
; runs. An empty value makes run_transmutes() bail at "No gem tiers configured", which
|
|
; also holds for force=True (tools/gem_transmute.py), unlike transmute_every_x_game=0.
|
|
; Re-enable by listing tiers again, e.g. transmute=flawless
|
|
transmute=
|
|
;how often we want to run transmute routine(e.g. every 100 games)
|
|
transmute_every_x_game=60
|
|
; number of stash tabs — drives tab click geometry in inventory/common.tab_properties().
|
|
; MUST match the tab bar on screen. This client shows 5: PERSONAL SHARED GEMS MATERIALS RUNES.
|
|
; It was 6, which made tab_properties compute centres of 63/127/192/256/320/384 while the
|
|
; real label centres are 68/144/220/295/370 — tabs 2, 3 and 4 clicked the gaps between tabs.
|
|
stash_tabs=5
|
|
; potion transmute settings
|
|
; convert_rejuv: 1 = convert regular Rejuv Potions to Full Rejuv via cube (3 -> 1)
|
|
convert_rejuv=1
|
|
; min_rejuv_to_convert: minimum regular rejuv potions in inventory before starting conversion
|
|
min_rejuv_to_convert=6
|
|
|
|
; ===========================
|
|
; ==== Builds: Sorceress ====
|
|
; ===========================
|
|
[sorceress]
|
|
energy_shield=f4
|
|
frozen_armor=f7
|
|
static_field=f5
|
|
telekinesis=f6
|
|
thunder_storm=
|
|
|
|
[light_sorc]
|
|
; chain_lightning must be bound to left skill (hotkey optional as it shouldnt change)
|
|
chain_lightning=
|
|
; lightning must be right skill, hotkey required
|
|
lightning=
|
|
; frozen orb must be right skill, hotkey recommended to increase safty for you and merc at trav!
|
|
frozen_orb=
|
|
|
|
[blizz_sorc]
|
|
; blizzard must be right skill, hotkey required
|
|
blizzard=f1
|
|
; ice_blast must be left skill (hotkey optional as it shouldnt change)
|
|
ice_blast=f2
|
|
|
|
[blizzorb_sorc]
|
|
; frozen orb must be left skill and preselected (no hotkey required)
|
|
|
|
;All others must be right skill and hotkey required!
|
|
blizzard=f1
|
|
glacial_spike=f7
|
|
|
|
[nova_sorc]
|
|
; nova must be right skill, hotkey required
|
|
nova=f1
|
|
|
|
[hydra_sorc]
|
|
; only supports Pindle and Eldritch currently
|
|
; alt_attack is any alternate attacking right skill. Fireball,Lightning,Frozen Orb, hotkey required
|
|
alt_attack=f7
|
|
; hydra must be right skill, hotkey required
|
|
hydra=f1
|
|
|
|
; =========================
|
|
; ==== Builds: Paladin ====
|
|
; =========================
|
|
[paladin]
|
|
cleansing=f9
|
|
holy_shield=f2
|
|
redemption=f3
|
|
vigor=f4
|
|
|
|
[fohdin]
|
|
; foh must be left skill, hotkey required
|
|
blessed_hammer=f1
|
|
concentration=f8
|
|
conviction=f5
|
|
foh=f6
|
|
holy_bolt=f7
|
|
|
|
[hammerdin]
|
|
blessed_hammer=f1
|
|
concentration=f8
|
|
conviction=f5
|
|
|
|
; =========================
|
|
; ==== Builds: Warlock ====
|
|
; =========================
|
|
[warlock]
|
|
; All skills should be on right-click unless otherwise specified
|
|
deathmark=f3
|
|
lethargy=f4
|
|
;Set summon_demon to one of the three demons of your choice.
|
|
summon_demon=f5
|
|
;If you are summoning a 2nd demon set hotkey here. Can be same as above
|
|
summon_demon2=
|
|
psychic_ward=
|
|
|
|
[fire_lock]
|
|
; Supported runs: Pindle
|
|
;flame_wave must be assigned to left-click and doesn't require hotkey
|
|
ring_of_fire=f1
|
|
apocalypse=f2
|
|
|
|
[abyss_lock]
|
|
; Supported runs: Pindle
|
|
;miasma_bolt must be assigned to left-click and doesn't require hotkey
|
|
miasma_chain=f1
|
|
abyss=f2
|
|
|
|
[echo_lock]
|
|
; Supported runs: Trav (tele only)
|
|
echo_strike=f1
|
|
eldritch_blast=f2
|
|
hex_bane=f3
|
|
;Set ring of fire if you want to stun before echo blast
|
|
ring_of_fire=
|
|
|
|
; ==========================
|
|
; ==== Builds: Assassin ====
|
|
; ==========================
|
|
; Currently no Trav implementaiton!
|
|
[trapsin]
|
|
; We assume fire blast or shock web is left-click skill which is optional hotkey if pre-assigned.
|
|
skill_left=
|
|
; Assume all other skills are right-click and hotkey required!
|
|
burst_of_speed=f1
|
|
death_sentry=f2
|
|
fade=
|
|
lightning_sentry=f3
|
|
shadow_warrior=f4
|
|
mind_blast=f5
|
|
|
|
; ===========================
|
|
; ==== Builds: Barbarian ====
|
|
; ===========================
|
|
; Supported runs: Trav, Pindle, Eld, Shenk, Nihlathak (tele only)
|
|
; Make sure leap hotkey is set if you do not have Enigma
|
|
; Ensure Battle Order/Command hotkeys are set above in the [char] section
|
|
[barbarian]
|
|
cry_frequency=0.7
|
|
find_item=
|
|
leap=
|
|
shout=
|
|
war_cry=
|
|
|
|
; ===========================
|
|
; ==== Builds: Amazon ====
|
|
; ===========================
|
|
; Supported runs: Trav, Pindle, Eld, Shenk, Nihlathak (tele only)
|
|
; Make sure leap hotkey is set if you do not have Enigma
|
|
; Ensure Battle Order/Command hotkeys are set above in the [char] section
|
|
[amazon]
|
|
;all must be right-click skill
|
|
valkyrie=f1
|
|
|
|
[javazon]
|
|
; charged strike must be left-click skill and preselected, no hotkey required
|
|
|
|
;all others must be right-click
|
|
lightning_fury=f2
|
|
|
|
; ==========================
|
|
; ==== Builds: Necro ====
|
|
; ==========================
|
|
; Pindle+eld+shenk+trav (walking only!)
|
|
; needs all of these skills
|
|
;skill_left is just some ranged damage spell (spear,teeth, nova,etc)
|
|
;leave teleport blank for now this build currently is walking only
|
|
;tested with something like this https://d2.maxroll.gg/d2planner/3x01067c
|
|
;recomended to set skeleton mage to same as revive hot hey
|
|
;skeleton mages cause pathing issues not ideal to use yet
|
|
[necro]
|
|
amp_dmg=
|
|
bone_armor=
|
|
clay_golem=
|
|
clear_pindle_pack=
|
|
corpse_explosion=
|
|
heart_of_wolverine=
|
|
raise_mage=
|
|
raise_revive=
|
|
raise_skeleton=
|
|
skill_left=
|
|
|
|
[poison_necro]
|
|
bone_armor=
|
|
# Can use any golem
|
|
clay_golem=
|
|
clear_pindle_pack=
|
|
corpse_explosion=
|
|
heart_of_wolverine=
|
|
# Can Use any curse
|
|
lower_res=
|
|
poison_nova=
|
|
raise_mage=
|
|
raise_revive=
|
|
raise_skeleton=
|
|
# Not Required
|
|
skill_left=
|
|
|
|
[bone_necro]
|
|
bone_armor=
|
|
bone_spear=
|
|
bone_spirit=
|
|
bone_wall=
|
|
clay_golem=
|
|
teeth=
|
|
; Damage scaling. One setting to adjust kill speed to account for better/worse gear.
|
|
; ex: 2 will cast all attack skills twice a long, .5 half as long. Currently only used by bone necro
|
|
damage_scaling=1
|
|
|
|
|
|
; ==== Builds: Basic ====
|
|
; Supported runs: UNTESTED (this is your job!)
|
|
; this will basically continuously left attack, unless right attack is available.
|
|
; if the buffs are not set they are not used (they are part of the prebuff setup)
|
|
|
|
[basic]
|
|
left_attack=1
|
|
right_attack=
|
|
buff_1=
|
|
buff_2=
|
|
|
|
|
|
; ==== Builds: Basic Ranged====
|
|
; Supported runs: UNTESTED (this is your job!)
|
|
; this will basically continuously left attack, unless right attack is available.
|
|
; if the buffs are not set they are not used (they are part of the prebuff setup)
|
|
|
|
[basic_ranged]
|
|
left_attack=
|
|
right_attack=
|
|
buff_1=
|
|
buff_2=
|
|
|
|
|
|
; ==== Run: Cold Plains clear ====
|
|
; Roams an outdoor area and kills with ONE configured skill. Works with any build
|
|
; and from clvl 1, so it is the levelling counterpart to the boss runs.
|
|
; Enable by adding run_cold_plains to [routes] order above.
|
|
;
|
|
; PREREQUISITE: the character must already have the destination waypoint.
|
|
; A fresh character has none - walk there once by hand first.
|
|
[cold_plains]
|
|
; Any Act 1 waypoint label from src/ui/waypoint.py _WAYPOINTS, e.g.
|
|
; "Cold Plains", "Stony Field", "Dark Wood", "Black Marsh".
|
|
area=Cold Plains
|
|
; Key holding the attack skill. NOTE Fireball is clvl 12 - on a fresh character
|
|
; put Fire Bolt (clvl 1) here and re-bind to Fireball later; nothing else changes.
|
|
attack_hotkey=
|
|
; Most sorc/caster skills cast on right click. Melee builds want "left".
|
|
attack_button=right
|
|
; How many roam-or-fight steps before heading back to town.
|
|
max_steps=12
|
|
; Casts per engagement before re-scanning for targets.
|
|
casts_per_target=4
|
|
; Ignore anything further than this many px from the character.
|
|
target_radius=600
|
|
; Delay between casts. Raise if you out-run your cast animation.
|
|
cast_delay=0.25
|
|
; Hard runtime cap per run, seconds - stops a bad area pinning the bot.
|
|
max_runtime_s=180
|
|
; Engagements per step, so an unkillable/misdetected target cannot stall the run.
|
|
max_engagements=8
|
|
|
|
[baal_xp]
|
|
; Baal XP farm: join public Baal games, hide, collect XP, leave. Repeat.
|
|
; Enable by adding run_baal_xp to [routes] order above.
|
|
enabled=1
|
|
; Substring to match against game names (case-insensitive). Empty = join first game.
|
|
game_name_filter=
|
|
; Max seconds to wait in a game before leaving (XP keeps ticking while you hide).
|
|
max_wait_s=900
|
|
; Leave early if XP gained in this game reaches this value.
|
|
xp_threshold=50000000
|
|
; Leave immediately if HP drops below this percentage (chicken).
|
|
min_hp_pct=40
|
|
; Screen position (client coords) to walk to and stand still while hiding.
|
|
hide_x=640
|
|
hide_y=360
|
|
; Max seconds to wait for a matching game to appear in the browser.
|
|
join_timeout_s=60
|
|
|
|
[advanced_options]
|
|
; startup hotkeys
|
|
; select_runs_key: open run selector UI
|
|
select_runs_key=pagedown
|
|
; restore_settings_from_backup_key: restore backed-up D2R settings
|
|
restore_settings_from_backup_key=insert
|
|
; settings_backup_key: create backup of current D2R settings
|
|
settings_backup_key=pause
|
|
; auto_settings_key: auto-apply required D2R settings
|
|
auto_settings_key=pageup
|
|
; graphic_debugger_key: toggle on-screen debug layers
|
|
graphic_debugger_key=delete
|
|
; resume_key: start/pause bot loop
|
|
resume_key=f11
|
|
; exit_key: hard stop bot
|
|
exit_key=f12
|
|
; cycle_pickit_profile_key: cycle through pickit profiles in config/pickit_profiles/
|
|
cycle_pickit_profile_key=f10
|
|
|
|
; etc.
|
|
; graphic_debugger_layer_creator: 1 = enable interactive layer creator tooling
|
|
graphic_debugger_layer_creator=0
|
|
; hwnd_window_process: process regex used to locate D2R window handle
|
|
hwnd_window_process=D2R\.exe
|
|
; hwnd_window_title: optional title regex override for window lookup
|
|
hwnd_window_title=
|
|
; launch_options: will replace <name> with setting for [general] "name" above
|
|
launch_options=-mod <name> -txt
|
|
; logg_lvl: logging verbosity (debug/info/warning/error)
|
|
logg_lvl=debug
|
|
; message_body_template: payload template for generic_api mode
|
|
message_body_template={{"content": "{msg}"}}
|
|
; message_headers: optional JSON headers for generic_api mode
|
|
message_headers=
|
|
; ocr_during_pickit: 1 = run OCR while looting (slower, more diagnostics)
|
|
ocr_during_pickit=0
|
|
;use "can_teleport_natively" or "can_teleport_with_charges" if you want to force certain behavior in case autodetection isn't working properly
|
|
override_capabilities=can_teleport_natively
|
|
; pathing_delay_factor: movement/click delay multiplier (1 fast .. 10 slow)
|
|
pathing_delay_factor=2
|
|
; If you want to control Hyper-V window from host use 0,51 here
|
|
; window_client_area_offset: x,y pixel offset for captured game client area
|
|
window_client_area_offset=0,0
|
|
|
|
[log_rotation]
|
|
; Log rotation prevents screenshot directories from filling the disk.
|
|
; Each managed directory has a max file count and max total size.
|
|
; When limits are exceeded, oldest files are deleted automatically.
|
|
; Checked every 60 seconds (to avoid I/O overhead during runs).
|
|
;
|
|
; The text log (log/log.txt) is size-capped separately: it rotates at 50 MB,
|
|
; keeps 5 zipped backups in log/archive/ (max 30 archives). Override the per-file
|
|
; cap with the BOTTY_LOG_MAX_MB environment variable (e.g. set 100 for 100 MB).
|
|
|
|
; pickit directory (log/screenshots/pickit/):
|
|
; Can grow very fast — every item scan writes a PNG + JSON.
|
|
; pickit_max_files=500 ; max files before rotation kicks in
|
|
; pickit_max_mb=500 ; max total size in MB
|
|
pickit_max_files=300
|
|
pickit_max_mb=200
|
|
|
|
; info directory (log/screenshots/info/):
|
|
; Debug screenshots for deaths, chickens, errors, etc.
|
|
; info_max_files=200 ; max files before rotation kicks in
|
|
; info_max_mb=500 ; max total size in MB
|
|
info_max_files=100
|
|
info_max_mb=200
|
|
|
|
; items directory (log/screenshots/items/):
|
|
; Loot screenshots sent to Discord.
|
|
; items_max_files=100
|
|
; items_max_mb=100
|
|
items_max_files=50
|
|
items_max_mb=50
|
|
|
|
; discord_notify_rotation: 1 = send Discord message when log rotation deletes files
|
|
discord_notify_rotation=0
|