Skip to content

Instantly share code, notes, and snippets.

@alivesay
Last active September 6, 2025 00:44
Show Gist options
  • Save alivesay/9ba31eb7befbcc02b8679d83ab9483c1 to your computer and use it in GitHub Desktop.
Save alivesay/9ba31eb7befbcc02b8679d83ab9483c1 to your computer and use it in GitHub Desktop.
# flake8: noqa: F401
# -----------------------------------------------------------------------------
#
# MrBelvedere's MEGA-AUTO
#
# WARNING: Using auto-attack/target/follow AFK may be bannable on your shard!!!
#
# Notes:
# - Set TazUO -> Mobiles -> Follow Distance = 1 for sticky follow.
# - If radius indicator stays on, run -radius client command.
# -----------------------------------------------------------------------------
from typing import Optional
import API
import time
# ================================= Config ====================================
AA_MAX_DISTANCE = 30 # max distance to search for enemies
AA_SHOW_RADIUS = False # show visual radius circle
AA_ATTACK_STEP_MS = 50 # attack tick interval (ms)
AA_POST_KILL_COMPLETE_LOOT = True # True = stay until corpse has no rule-matching items
AA_POST_KILL_LOOT_MIN_HOLD_MS = 1200 # (used only when COMPLETE_LOOT=False)
AA_POST_KILL_LOOT_QUIET_ROUNDS = 3 # (used only when COMPLETE_LOOT=False)
AA_POST_KILL_LOOT_MAX_HOLD_MS = 4500 # safety cap either way
AA_ABILITY_USE = "secondary" # ability mode: "primary", "secondary", or "off"
AA_ABILITY_MANA_MIN = 36 # min mana required for ability use
AB_HP_MISSING_MIN = 10 # min HP missing before bandage starts
AB_ALLOW_WHILE_HIDDEN = False # allow bandages while hidden
AB_BANDAGE_POLL_MS = 60 # bandage poll interval (ms)
AB_ETA_MS = 2000 # bandage expected completion time (ms)
AB_MIN_RETRY_GAP_MS = 450 # min gap between bandage retries (ms)
AB_AFTER_HIT_GRACE_MS = 700 # delay after taking damage before bandaging (ms)
AL_ENABLED_DEFAULT = True # enable auto-loot by default
AL_RANGE = 4 # max tile range for loot
AL_LOOT_TICK_MS = 140 # loot tick interval (ms)
AL_RECHECK_COOLDOWN_MS = 300 # cooldown between re-checks of same container (ms)
AL_MOVE_PAUSE_MS = 350 # pause after moving an item (ms)
AL_PASS_BUDGET_MS = 150 # time budget per loot container (ms)
AL_WAIT_READY_SLICE_MS = 25 # wait slice for busy checks (ms)
AL_ONLY_CORPSES = False # True = loot only corpses; False = also scan world containers
AL_SKIP_SECURE = True # skip locked-down / secure containers
AL_OBJECT_NAME_DENY = [
"locked down",
"secure",
"strongbox",
"strong box",
"mailbox",
"commodity",
]
AL_FAIL_LINES = ["you must wait before performing another action"]
AL_RULES = [
{
"label": "Gold",
"enabled": True,
"names": ["gold", "pile of gold", "gold coin", "gold coins"],
"graphics": [0x0EED],
"min_amount": 0,
"dest": lambda: API.Backpack,
},
{
"label": "Gems",
"enabled": True,
"names": [
"amber",
"amethyst",
"citrine",
"diamond",
"emerald",
"ruby",
"sapphire",
"star sapphire",
"tourmaline",
],
"graphics": [],
"min_amount": 0,
"dest": lambda: API.Backpack,
},
{
"label": "Reagents",
"enabled": True,
"names": ["grave dust"],
"graphics": [],
"min_amount": 0,
"dest": lambda: API.Backpack,
},
]
for r in AL_RULES:
r["_names_lc"] = [n.lower() for n in (r.get("names") or [])]
def now_ms() -> int:
return int(time.monotonic() * 1000)
def _safe(fn, *a, **kw):
try:
return fn(*a, **kw)
except:
return None
def api_busy() -> bool:
try:
return API.IsGlobalCooldownActive()
except:
return False
def cancel_motion_and_target():
for fn in (
"CancelAutoFollow",
"CancelPathfinding",
"ClearMoveQueue",
"CancelTarget",
):
_safe(getattr(API, fn))
# For single-instance enforcement: only the most recent instance runs
INSTANCE_KEY = "MEGA_AUTO_INSTANCE_TS"
INSTANCE_CHECK_MS = 1000
def _refresh_lock():
try:
API.SavePersistentVar(LOCK_KEY, str(now_ms()), API.PersistentVar.Char)
except:
pass
def _acquire_lock() -> bool:
try:
val = API.GetPersistentVar(LOCK_KEY, "", API.PersistentVar.Char)
if val:
try:
ts = int(val)
except:
ts = 0
if (now_ms() - ts) >= LOCK_STALE_MS:
_release_lock()
else:
API.SysMsg(
"MEGA-AUTO: another instance seems to be running. Aborting.", 32
)
return False
API.SavePersistentVar(LOCK_KEY, str(now_ms()), API.PersistentVar.Char)
return True
except:
_safe(API.SysMsg, "MEGA-AUTO: WARN: lock unavailable, proceeding.", 32)
return True
def _release_lock():
try:
API.RemovePersistentVar(LOCK_KEY, API.PersistentVar.Char)
except:
pass
def player_hits() -> int:
try:
return int(API.Player.Hits)
except:
return 0
def player_hits_max() -> int:
try:
return int(API.Player.HitsMax)
except:
return max(1, player_hits())
def player_mana() -> int:
try:
return int(API.Player.Mana)
except:
return 0
def player_serial() -> int:
try:
return int(API.Player.Serial)
except:
return 0
def is_poisoned() -> bool:
try:
return bool(API.Player.IsPoisoned)
except:
try:
return API.BuffExists("Poison")
except:
return False
def is_hidden() -> bool:
try:
if API.BuffExists("Hiding") or API.BuffExists("Hidden"):
return True
except:
pass
try:
return bool(API.Player.Hidden)
except:
return False
DEFAULT_FONT_SIZE = 18
AV_FONT = "Avadonia"
def Label(text, size=16, color="#FFFFFF", align="left", maxW=0):
return API.CreateGumpTTFLabel(
text,
size if size is not None else DEFAULT_FONT_SIZE,
color,
font=(AV_FONT or ""),
aligned=align,
maxWidth=maxW if maxW else 0,
)
_radius_last = {"shown": False, "dist": 0, "hue": 32}
def radius_apply(force: bool = False):
try:
should_show = bool(AA_SHOW_RADIUS)
desired_dist = int(AA_MAX_DISTANCE)
desired_hue = int(_radius_last["hue"])
if should_show:
if (
force
or (not _radius_last["shown"])
or (_radius_last["dist"] != desired_dist)
):
API.DisplayRange(desired_dist, desired_hue)
_radius_last.update({"shown": True, "dist": desired_dist})
else:
if force or _radius_last["shown"]:
API.DisplayRange(0)
_radius_last.update({"shown": False, "dist": 0})
except:
pass
aa_enabled = False
aa_auto_follow = False
aa_honor = True
aa_abilities = True
aa_new_target = False
_aa_target_serial = 0
_loot_lock_until_ms = 0
def aa_enable():
global aa_enabled
aa_enabled = True
def aa_disable():
global aa_enabled, _aa_target_serial
aa_enabled = False
_aa_target_serial = 0
cancel_motion_and_target()
def af_enable():
global aa_auto_follow
aa_auto_follow = True
def af_disable():
global aa_auto_follow
aa_auto_follow = False
cancel_motion_and_target()
def hon_enable():
global aa_honor
aa_honor = True
def hon_disable():
global aa_honor
aa_honor = False
def abl_enable():
global aa_abilities
aa_abilities = True
def abl_disable():
global aa_abilities
aa_abilities = False
def aa_request_new_target():
global aa_new_target
aa_new_target = True
def _honor_target_if_fresh(mob):
try:
if mob and mob.HitsDiff == 0 and mob.Distance < 6:
API.Virtue("honor")
API.WaitForTarget()
API.Target(mob)
API.CancelTarget()
except:
pass
def _use_ability_if_ready():
if not aa_abilities:
return
mode = (AA_ABILITY_USE or "off").lower()
mana_req = int(AA_ABILITY_MANA_MIN or 0)
try:
mana = int(API.Player.Mana)
except:
mana = 0
try:
prim_active = bool(API.PrimaryAbilityActive())
except:
prim_active = False
try:
sec_active = bool(API.SecondaryAbilityActive())
except:
sec_active = False
if mode not in ("primary", "secondary"):
if prim_active:
_safe(API.ToggleAbility, "primary")
prim_active = False
if sec_active:
_safe(API.ToggleAbility, "secondary")
sec_active = False
return
if mode == "primary" and sec_active:
_safe(API.ToggleAbility, "secondary")
sec_active = False
if mode == "secondary" and prim_active:
_safe(API.ToggleAbility, "primary")
prim_active = False
if mana < mana_req:
if mode == "primary" and prim_active:
_safe(API.ToggleAbility, "primary")
elif mode == "secondary" and sec_active:
_safe(API.ToggleAbility, "secondary")
return
if api_busy():
return
if mode == "primary":
if not prim_active:
_safe(API.ToggleAbility, "primary")
else:
if not sec_active:
_safe(API.ToggleAbility, "secondary")
def _is_corpse(ent) -> bool:
try:
if getattr(ent, "IsCorpse", False):
return True
except:
pass
try:
nm = (getattr(ent, "Name", "") or "").lower()
return "corpse" in nm
except:
return False
def _opened(serial) -> bool:
try:
return API.Contents(serial) > 0
except:
return False
def _ensure_open(serial) -> bool:
if _opened(serial):
return True
for fn in ("UseObject", "DoubleClick"):
f = getattr(API, fn, None)
if f:
_safe(f, serial)
API.Pause(0.08)
if _opened(serial):
return True
return _opened(serial)
def _match_rule(it, rule, cached_name=None):
if not rule.get("enabled", True):
return False
try:
amt = int(getattr(it, "Amount", 0) or 0)
except:
amt = 0
if int(rule.get("min_amount", 0)) > 0 and amt < int(rule["min_amount"]):
return False
try:
g = int(getattr(it, "Graphic", -1) or -1)
except:
g = -1
if rule.get("graphics") and g in rule["graphics"]:
return True
if rule.get("names"):
if cached_name is not None:
nm = (cached_name or "").strip().lower()
else:
nm = (getattr(it, "Name", "") or "").strip().lower()
if not nm:
try:
tip = API.GetTooltipText(it)
if isinstance(tip, str) and tip.strip():
nm = tip.strip().lower()
except:
pass
if not nm:
try:
props = API.GetProperties(it)
if isinstance(props, str) and props.strip():
nm = props.strip().lower()
except:
pass
toks = rule.get("_names_lc") or [n.lower() for n in (rule.get("names") or [])]
return any(tok in nm for tok in toks) if nm else False
return False
def _container_has_rule_matches(serial) -> bool:
items = _safe(API.ItemsInContainer, serial, False) or []
for it in items:
try:
if int(it.Serial) == int(serial):
continue
except:
pass
nm = _item_name_lc(it)
for rule in AL_RULES:
if _match_rule(it, rule, nm):
return True
return False
def _loot_container(serial):
items = _safe(API.ItemsInContainer, serial, False) or []
budget_end = now_ms() + AL_PASS_BUDGET_MS
moved = {}
cnt = 0
for it in items:
if now_ms() >= budget_end or cnt >= 5:
break
try:
if int(it.Serial) == int(serial):
continue
except:
pass
nm = _item_name_lc(it)
for rule in AL_RULES:
if _match_rule(it, rule, nm):
dst = (
rule["dest"]()
if callable(rule.get("dest"))
else int(rule.get("dest", API.Backpack))
)
if _move_full_stack(it, dst):
lbl = rule.get("label", "Rule")
moved[lbl] = moved.get(lbl, 0) + 1
cnt += 1
break
return moved
def _wait_ready_ms(max_wait_ms: int = 500) -> bool:
if _bandaging_active:
API.ProcessCallbacks()
return True
deadline = now_ms() + max_wait_ms
slice_ms = AL_WAIT_READY_SLICE_MS
while now_ms() < deadline:
API.ProcessCallbacks()
if not api_busy():
return True
API.Pause(slice_ms / 1000.0)
return True
def _move_full_stack(it, dst) -> bool:
for _ in range(6):
if not _wait_ready_ms():
return False
try:
API.MoveItem(it, dst, 0)
API.Pause(AL_MOVE_PAUSE_MS / 1000.0)
return True
except:
try:
if API.InJournalAny(AL_FAIL_LINES):
API.ClearJournal()
except:
pass
API.Pause(0.02)
return False
def _nearest_openable_corpse(range_tiles: int):
_safe(API.ClearIgnoreList)
best = None
for _ in range(24):
c = _safe(API.NearestCorpse, max(range_tiles, 5))
if not c:
break
s = int(getattr(c, "Serial", 0) or 0)
if s:
best = c
_safe(API.IgnoreObject, s)
break
if s:
_safe(API.IgnoreObject, s)
_safe(API.ClearIgnoreList)
return best
def _loot_corpse_until_done_or_timeout():
global _loot_lock_until_ms
deadline = now_ms() + AA_POST_KILL_LOOT_MAX_HOLD_MS
_loot_lock_until_ms = deadline
c = _nearest_openable_corpse(AL_RANGE)
if not c:
_loot_lock_until_ms = 0
return
serial = int(getattr(c, "Serial", 0) or 0)
if not serial:
_loot_lock_until_ms = 0
return
_ensure_open(serial)
stagnant_passes = 0
while now_ms() < deadline:
_loot_lock_until_ms = deadline
API.ProcessCallbacks()
cancel_motion_and_target()
auto_bandage_step()
if not _opened(serial):
break
if not _container_has_rule_matches(serial):
break
moved = _loot_container(serial)
if moved:
stagnant_passes = 0
API.Pause(0.02)
continue
API.Pause(0.03)
if not _container_has_rule_matches(serial):
break
stagnant_passes += 1
if stagnant_passes >= 2:
break
_loot_lock_until_ms = 0
def _loot_hold_after_kill():
cancel_motion_and_target()
if AA_POST_KILL_COMPLETE_LOOT:
_loot_corpse_until_done_or_timeout()
else:
min_hold = AA_POST_KILL_LOOT_MIN_HOLD_MS
max_hold = AA_POST_KILL_LOOT_MAX_HOLD_MS
need_quiet = AA_POST_KILL_LOOT_QUIET_ROUNDS
start = now_ms()
quiet = 0
while True:
API.ProcessCallbacks()
cancel_motion_and_target()
auto_bandage_step()
_safe(auto_loot_tick)
has_targets = False
try:
if _scan_opened_containers(AL_RANGE):
has_targets = True
except:
pass
elapsed = now_ms() - start
quiet = 0 if has_targets else (quiet + 1)
if elapsed >= min_hold and quiet >= need_quiet:
break
if elapsed >= max_hold:
break
API.Pause(0.07)
def auto_attack_step():
if not aa_enabled:
return
if AA_POST_KILL_COMPLETE_LOOT:
c = _nearest_openable_corpse(AL_RANGE)
if c:
try:
s = int(getattr(c, "Serial", 0) or 0)
except:
s = 0
if s:
_ensure_open(s)
if _opened(s) and _container_has_rule_matches(s):
cancel_motion_and_target()
_loot_hold_after_kill()
return
global aa_new_target, _aa_target_serial
if aa_new_target:
aa_new_target = False
_aa_target_serial = 0
mob = None
if _aa_target_serial:
mob = _safe(API.FindMobile, _aa_target_serial)
try:
if (not mob) or (mob.Distance >= AA_MAX_DISTANCE):
_aa_target_serial = 0
mob = None
except:
_aa_target_serial = 0
mob = None
if not mob:
mob = _safe(
API.NearestMobile,
[API.Notoriety.Gray, API.Notoriety.Criminal, API.Notoriety.Murderer],
AA_MAX_DISTANCE,
)
if mob:
try:
_aa_target_serial = int(mob.Serial)
except:
_aa_target_serial = 0
if aa_honor:
_honor_target_if_fresh(mob)
if not mob:
return
died = False
try:
died = (
bool(getattr(mob, "IsDead", False))
or int(getattr(mob, "Hits", 1) or 1) <= 0
)
except:
pass
if died:
cancel_motion_and_target()
_loot_hold_after_kill()
_aa_target_serial = 0
return
if aa_auto_follow:
_safe(API.AutoFollow, mob)
else:
_safe(API.CancelAutoFollow)
_safe(API.CancelPathfinding)
_use_ability_if_ready()
_safe(API.Attack, mob)
try:
mob.Hue = 32
except:
pass
if not aa_auto_follow:
_safe(API.CancelPathfinding)
ab_enabled = True
al_enabled = AL_ENABLED_DEFAULT
_bandaging_active = False
_last_damage_ms = 0
_last_bandage_attempt_ms = 0
_bandage_until_ms = 0
def ab_enable():
global ab_enabled
ab_enabled = True
def ab_disable():
global ab_enabled
ab_enabled = False
def al_enable():
global al_enabled
al_enabled = True
def al_disable():
global al_enabled
al_enabled = False
def _set_bandage_lock(ms):
global _bandage_until_ms
_bandage_until_ms = now_ms() + int(ms)
def auto_bandage_step():
if not ab_enabled:
return
global _last_damage_ms, _last_bandage_attempt_ms, _bandage_until_ms, _bandaging_active
hp = player_hits()
mx = player_hits_max()
nowm = now_ms()
prev = getattr(auto_bandage_step, "_prev_hp", hp)
if hp < prev:
_last_damage_ms = nowm
auto_bandage_step._prev_hp = hp
missing = mx - hp
if missing < AB_HP_MISSING_MIN and not is_poisoned():
return
if is_hidden() and not AB_ALLOW_WHILE_HIDDEN:
return
if nowm < _bandage_until_ms:
return
if (nowm - _last_bandage_attempt_ms) < AB_MIN_RETRY_GAP_MS:
return
if (nowm - _last_damage_ms) < AB_AFTER_HIT_GRACE_MS and not is_poisoned():
return
if api_busy():
return
_safe(API.CancelTarget)
ok = False
try:
ok = bool(API.BandageSelf())
except:
ok = False
if not ok:
_safe(API.SysMsg, "WARNING: No bandages!", 32)
_last_bandage_attempt_ms = nowm
return
_set_bandage_lock(AB_ETA_MS + 150)
_bandaging_active = True
_last_bandage_attempt_ms = nowm
if now_ms() >= _bandage_until_ms and _bandaging_active:
_bandaging_active = False
_al_seen = {}
_last_target_serial = 0
def _looks_secure(ent) -> bool:
if not AL_SKIP_SECURE:
return False
try:
if getattr(ent, "IsLockedDown", False):
return True
except:
pass
try:
if getattr(ent, "IsSecure", False):
return True
except:
pass
try:
txt = None
try:
txt = API.GetTooltipText(ent)
except:
pass
try:
txt = txt or API.GetProperties(ent)
except:
pass
if txt and isinstance(txt, str):
lt = txt.lower()
for tok in AL_OBJECT_NAME_DENY:
if tok in lt:
return True
except:
pass
try:
nm = (getattr(ent, "Name", "") or "").lower()
for tok in AL_OBJECT_NAME_DENY:
if tok in nm:
return True
except:
pass
return False
def _item_name_lc(it) -> str:
try:
nm = (getattr(it, "Name", "") or "").strip()
if nm:
return nm.lower()
except:
pass
try:
tip = API.GetTooltipText(it)
if isinstance(tip, str) and tip.strip():
return tip.strip().lower()
except:
pass
try:
props = API.GetProperties(it)
if isinstance(props, str) and props.strip():
return props.strip().lower()
except:
pass
return ""
def _scan_opened_containers(range_tiles: int):
found = set()
_safe(API.ClearIgnoreList)
for _ in range(24):
c = _safe(API.NearestCorpse, range_tiles)
if not c:
break
s = int(getattr(c, "Serial", 0) or 0)
if s and _opened(s):
found.add(s)
_safe(API.IgnoreObject, s)
_safe(API.ClearIgnoreList)
if not AL_ONLY_CORPSES:
_safe(API.ClearIgnoreList)
for _ in range(36):
e = _safe(API.NearestEntity, API.ScanType.Objects, range_tiles)
if not e:
break
s = int(getattr(e, "Serial", 0) or 0)
if s:
if not _is_corpse(e) and not _looks_secure(e) and _opened(s):
found.add(s)
_safe(API.IgnoreObject, s)
_safe(API.ClearIgnoreList)
return list(found)
def _handle_container(serial):
t = now_ms()
last = _al_seen.get(serial, 0)
if (t - last) < AL_RECHECK_COOLDOWN_MS:
return
if not _opened(serial):
_al_seen[serial] = t
return
_loot_container(serial)
_al_seen[serial] = now_ms()
def auto_loot_tick():
if not al_enabled:
return
global _last_target_serial
cur = int(getattr(API, "LastTargetSerial", 0) or 0)
if cur and cur != _last_target_serial and _opened(cur):
_last_target_serial = cur
_handle_container(cur)
for s in _scan_opened_containers(AL_RANGE):
_handle_container(s)
gump = None
_rows = {}
_local_stop_requested = False
GUMP_W, GUMP_H = 200, 320
_ROW_H = 22
_CB_W = 22
_PAD_X = 10
_CB_OFFSET = 0
def _cleanup_on_stop():
try:
API.DisplayRange(0)
except:
pass
_radius_last.update({"shown": False, "dist": 0})
for fn in (
"CancelAutoFollow",
"CancelPathfinding",
"ClearMoveQueue",
"CancelTarget",
"ClearIgnoreList",
"ClearJournal",
):
_safe(getattr(API, fn))
try:
if API.PrimaryAbilityActive():
API.ToggleAbility("primary")
except:
pass
try:
if API.SecondaryAbilityActive():
API.ToggleAbility("secondary")
except:
pass
try:
gump.Dispose()
except:
pass
_release_lock()
def _set_radius(enabled: bool):
global AA_SHOW_RADIUS
AA_SHOW_RADIUS = bool(enabled)
radius_apply(force=True)
row = _rows.get("showradius")
if row:
try:
row["cb"].IsChecked = AA_SHOW_RADIUS
except:
pass
def _mk_row(g, y, name, text, on_enable, on_disable, checked=True, hue=0, indent=False):
lbl_x = _PAD_X + (18 if indent else 0)
cb_x = GUMP_W - _PAD_X - _CB_W - _CB_OFFSET
cb = API.CreateGumpCheckbox("", hue, checked)
cb.SetRect(cb_x, y, _CB_W, _ROW_H)
lbl = Label(text, DEFAULT_FONT_SIZE, "#FFFFFF", "left")
lbl.SetPos(lbl_x, y)
def _on_click():
if name == "showradius":
_set_radius(cb.IsChecked)
else:
(on_enable if cb.IsChecked else on_disable)()
API.AddControlOnClick(cb, _on_click)
g.Add(lbl)
g.Add(cb)
_rows[name] = {"lbl": lbl, "cb": cb}
return y + _ROW_H
def build_gump():
global gump
gump = API.CreateGump(True, True, True)
gump.SetRect(100, 100, GUMP_W, GUMP_H)
bg = API.CreateGumpColorBox(0.88, "#212121")
bg.SetRect(0, 0, GUMP_W, GUMP_H)
gump.Add(bg)
title = Label("MEGA-AUTO", DEFAULT_FONT_SIZE, "#FFB000", "center", GUMP_W)
title.SetRect(0, 8, GUMP_W, 24)
gump.Add(title)
y = 40
y = _mk_row(
gump, y, "autoattack", "Auto-Attack", aa_enable, aa_disable, checked=False
)
y = _mk_row(
gump, y, "follow", "- Follow", af_enable, af_disable, checked=False, indent=True
)
y = _mk_row(
gump, y, "honor", "- Honor", hon_enable, hon_disable, checked=True, indent=True
)
y = _mk_row(
gump,
y,
"abilities",
"- Abilities",
abl_enable,
abl_disable,
checked=True,
indent=True,
)
ability_options = ["primary", "secondary"]
ability_labels = ["Primary", "Secondary"]
group_id = 1
radio_x = _PAD_X + 36
radio_y = y
radio_w = 80
radio_h = _ROW_H
for idx, (opt, label) in enumerate(zip(ability_options, ability_labels)):
checked = (AA_ABILITY_USE == opt)
rb = API.CreateGumpRadioButton(label, group_id, 0x00D2, 0x00D3, 0xFFFF, checked)
rb.SetRect(radio_x, radio_y + idx * (radio_h + 2), radio_w, radio_h)
def make_onclick(opt_val):
def _on_click():
global AA_ABILITY_USE
AA_ABILITY_USE = opt_val
return _on_click
API.AddControlOnClick(rb, make_onclick(opt))
gump.Add(rb)
y += len(ability_options) * (radio_h + 2)
y = _mk_row(
gump,
y,
"showradius",
"- Radius",
lambda: _set_radius(True),
lambda: _set_radius(False),
checked=bool(AA_SHOW_RADIUS),
indent=True,
)
y = _mk_row(gump, y, "bandage", "Auto-Bandage", ab_enable, ab_disable, checked=True)
y = _mk_row(
gump,
y,
"autoloot",
"Auto-Loot",
al_enable,
al_disable,
checked=bool(al_enabled),
)
btn_new = API.CreateSimpleButton("[NEW TARGET]", 120, 22)
btn_new.SetPos((GUMP_W - 120) // 2, GUMP_H - 64)
API.AddControlOnClick(btn_new, aa_request_new_target)
gump.Add(btn_new)
stop_btn = API.CreateSimpleButton("[STOP]", 110, 24)
stop_btn.SetBackgroundHue(32)
stop_btn.SetPos((GUMP_W - 110) // 2, GUMP_H - 34)
def on_stop_button_click():
aa_disable()
API.SysMsg("Auto-attack stopped.", 32)
API.AddControlOnClick(stop_btn, on_stop_button_click)
gump.Add(stop_btn)
API.AddGump(gump)
MAIN_TICK_MS = 20
_last_run = {}
def run_every(name: str, interval_ms: int, fn):
nowm = now_ms()
last = _last_run.get(name, 0)
if (nowm - last) >= interval_ms:
_last_run[name] = nowm
fn()
def main():
global aa_new_target, _last_target_serial, _aa_target_serial
my_instance_ts = now_ms()
try:
API.SavePersistentVar(INSTANCE_KEY, str(my_instance_ts), API.PersistentVar.Global)
except:
pass
try:
_safe(API.SysMsg, "MEGA-AUTO loaded.", 32)
_cleanup_on_stop()
build_gump()
try:
current_char = int(API.Player.Serial)
except:
current_char = 0
try:
last_instance_check = now_ms()
while True:
API.ProcessCallbacks()
run_every("lock_heartbeat", 1000, _refresh_lock)
serial_now = player_serial()
if serial_now and serial_now != current_char:
_cleanup_on_stop()
aa_disable()
af_disable()
hon_disable()
abl_disable()
aa_new_target = False
_last_target_serial = 0
_aa_target_serial = 0
build_gump()
current_char = serial_now
run_every("attack", AA_ATTACK_STEP_MS, auto_attack_step)
run_every("bandage", AB_BANDAGE_POLL_MS, auto_bandage_step)
run_every("autoloot", AL_LOOT_TICK_MS, auto_loot_tick)
run_every("radius_apply", 300, lambda: radius_apply(False))
if now_ms() - last_instance_check >= INSTANCE_CHECK_MS:
last_instance_check = now_ms()
try:
latest_ts = int(API.GetPersistentVar(INSTANCE_KEY, "0", API.PersistentVar.Global))
except:
latest_ts = my_instance_ts
if latest_ts > my_instance_ts:
_cleanup_on_stop()
_safe(API.SysMsg, "MEGA-AUTO: Newer instance detected. Shutting down.", 33)
break
if _local_stop_requested:
_cleanup_on_stop()
_safe(API.SysMsg, "MEGA-AUTO stopped.", 33)
break
API.Pause(MAIN_TICK_MS / 1000.0)
finally:
_cleanup_on_stop()
_safe(API.SysMsg, "MEGA-AUTO stopped.", 33)
finally:
_release_lock()
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment