Skip to content

Instantly share code, notes, and snippets.

@alivesay
Last active August 21, 2025 02:52
Show Gist options
  • Select an option

  • Save alivesay/b7d4056cda84e54c12685c13b27b6917 to your computer and use it in GitHub Desktop.

Select an option

Save alivesay/b7d4056cda84e54c12685c13b27b6917 to your computer and use it in GitHub Desktop.
Legion Script: Eat Everything!
# EatEverything.py
# -----------------------------------------------------------------------------
# Eats all the defined food in your bag and drops bowls to ground.
#-------------------------------------------------------
import API
import time
# ================================ Config ================================
# Main gump position/size
MAIN_X, MAIN_Y, MAIN_W, MAIN_H = 100, 100, 400, 260
# Persistence & behavior
PERSIST_KEY = "EatEverything_Foods" # per-character persistent var
FOOD_EAT_DELAY = 0.6 # seconds between uses
TOO_FULL_LINES = ["you are simply too full", # journal lines that end eating
"you are too full"]
# Drop-after-eat settings
TRASH_KEYWORDS = ["wooden bowl", "large wooden bowl", "pewter bowl", ] # case-insensitive contains
OSI_SHARD = False # True on OSI shards
MOVE_DELAY = 0.35 # seconds between item moves
RECURSIVE_SEARCH = False # search subcontainers inside backpack?
# Scatter config for ground drops (avoids item caps on a single tile)
SCATTER_RADIUS = 2 # how far from your tile (in tiles)
SCATTER_RANDOM = False # True = random scatter; False = spiral/round-robin
# Layout inside gump
HEADER_Y = 36
SA_X, SA_Y = 10, 58
SA_W, SA_H = 380, 150
BTN_Y = 214
# Grid sizing (with 2x icons)
CELL_W, CELL_H = 92, 96
ICON_W, ICON_H = 88, 88
# =============================== UI State ==============================
_gump = None
_list_area = None
_info_label = None
_ctx_controls = [] # inline confirm overlay controls
_food_graphics = set() # set[int] (graphics/types)
_stop_requested = False
# Double-click timing
_last_click_t = 0.0
_last_click_key = None
_DBL_WINDOW = 0.35 # seconds between clicks to count as double-click
# ============================== Font Helper ============================
def Label(text, size=16, color="#FFFFFF", align="left", maxW=0):
"""Create Avadonia TTF label everywhere for consistent visuals."""
return API.CreateGumpTTFLabel(
text, size, color,
font="Avadonia",
aligned=align,
maxWidth=maxW if maxW else 0
)
# =========================== Persistence I/O ===========================
def _load_foods():
saved = API.GetPersistentVar(PERSIST_KEY, "", API.PersistentVar.Char)
if not saved:
return
for p in (x.strip() for x in saved.split(",") if x.strip()):
try:
val = int(p, 16) if p.lower().startswith("0x") else int(p)
_food_graphics.add(val)
except Exception:
pass
def _save_foods():
s = ",".join([f"0x{g:04X}" for g in sorted(_food_graphics)]) if _food_graphics else ""
API.SavePersistentVar(PERSIST_KEY, s, API.PersistentVar.Char)
# =============================== Utilities =============================
def _set_info(text: str):
if _info_label:
_info_label.SetText(text)
def _close_context():
"""Remove/destroy the inline confirmation overlay controls."""
global _ctx_controls
if not _ctx_controls:
return
for ctl in _ctx_controls:
try:
ctl.Dispose()
except Exception:
pass
_ctx_controls = []
def _attach_double_click(control, key, on_double):
"""
Simulate double-click using single-click timing.
key: tuple identifying the same icon (e.g., ('food', gfx, idx))
"""
def _on_click():
global _last_click_t, _last_click_key
now = time.time()
if _last_click_key == key and (now - _last_click_t) <= _DBL_WINDOW:
_last_click_key = None
_last_click_t = 0.0
on_double()
else:
_last_click_key = key
_last_click_t = now
_set_info("Double-click an icon to delete…")
API.AddControlOnClick(control, _on_click, False) # any mouse button
def _first_line_or_name(item) -> str:
"""Return item.Name or the first line of ItemNameAndProps, or empty string."""
try:
n = (item.Name or "").strip()
if n:
return n
except Exception:
pass
try:
tip = API.ItemNameAndProps(item, True, 2) or ""
return tip.split("\n", 1)[0].strip()
except Exception:
return ""
def _is_trash(item) -> bool:
base = _first_line_or_name(item).lower()
return any(k in base for k in TRASH_KEYWORDS)
# ---- scatter helpers ----
def _build_scatter_offsets(radius: int):
offs = []
rmax = max(1, int(radius))
for r in range(1, rmax + 1):
for x in range(-r, r + 1): offs.append((x, -r))
for y in range(-r + 1, r + 1): offs.append((r, y))
for x in range(r - 1, -r - 1, -1): offs.append((x, r))
for y in range(r - 1, -r, -1): offs.append((-r, y))
return [(1, 0)] if not offs else offs
_SCATTER_OFFSETS = _build_scatter_offsets(SCATTER_RADIUS)
_scatter_idx = 0
def _next_scatter_offset():
global _scatter_idx
if SCATTER_RANDOM:
i = API.Random.Next(len(_SCATTER_OFFSETS))
return _SCATTER_OFFSETS[i]
off = _SCATTER_OFFSETS[_scatter_idx % len(_SCATTER_OFFSETS)]
_scatter_idx += 1
return off
def _drop_trash_to_ground() -> int:
"""Scan backpack and drop all matching trash items, scattered around your feet."""
items = API.ItemsInContainer(API.Backpack, RECURSIVE_SEARCH)
if not items:
return 0
dropped = 0
for it in items:
try:
if not _is_trash(it):
continue
while API.IsProcessingMoveQue() or API.IsGlobalCooldownActive():
API.Pause(0.15)
ox, oy = _next_scatter_offset()
API.MoveItemOffset(it, 0, ox, oy, 0, OSI_SHARD) # amt=0 => whole stack
API.Pause(MOVE_DELAY)
dropped += 1
except Exception:
pass
return dropped
# ============================ Confirm Overlay ==========================
def _open_delete_confirm_centered(gfx_hex: str):
"""Inline confirm overlay (centered within main gump)."""
global _ctx_controls
_close_context()
box_w, box_h = 220, 96
ox = (MAIN_W // 2) - (box_w // 2)
oy = (MAIN_H // 2) - (box_h // 2)
dim = API.CreateGumpColorBox(0.35, "#000000")
dim.SetRect(0, 0, MAIN_W, MAIN_H)
_gump.Add(dim); _ctx_controls.append(dim)
bg = API.CreateGumpColorBox(0.92, "#2E2E2E")
bg.SetRect(ox, oy, box_w, box_h)
_gump.Add(bg); _ctx_controls.append(bg)
title = Label(f"Delete {gfx_hex}?", 18, "#FF6666", align="center", maxW=box_w)
title.SetRect(ox, oy + 8, box_w, 22)
_gump.Add(title); _ctx_controls.append(title)
btn_yes = API.CreateSimpleButton("[Yes]", 80, 24)
btn_yes.SetPos(ox + 20, oy + box_h - 36)
def yes_cb():
try:
g = int(gfx_hex, 16)
if g in _food_graphics:
_food_graphics.remove(g)
_save_foods()
_rebuild_list_area()
_set_info(f"Removed {gfx_hex}.")
except Exception:
_set_info("Delete failed.")
_close_context()
API.AddControlOnClick(btn_yes, yes_cb)
_gump.Add(btn_yes); _ctx_controls.append(btn_yes)
btn_no = API.CreateSimpleButton("[No]", 80, 24)
btn_no.SetPos(ox + box_w - 100, oy + box_h - 36)
API.AddControlOnClick(btn_no, _close_context)
_gump.Add(btn_no); _ctx_controls.append(btn_no)
# =============================== Actions ===============================
def action_add_food():
_set_info("Target a food item in your backpack…")
targ = API.RequestAnyTarget(10)
if not targ:
_set_info("No target selected.")
return
try:
serial = getattr(targ, "Serial", 0)
except Exception:
serial = 0
if not serial:
_set_info("Could not read target serial.")
return
item = API.FindItem(serial)
if not item:
_set_info("Could not resolve item.")
return
try:
if item.Container != API.Backpack:
_set_info("Item is not in your backpack.")
return
except Exception:
pass
try:
g = int(item.Graphic)
except Exception:
_set_info("Item has no graphic.")
return
name = _first_line_or_name(item) or "(unknown)"
if g not in _food_graphics:
_food_graphics.add(g)
_save_foods()
_rebuild_list_area()
_set_info(f"Added {name} (0x{g:04X}).")
else:
_set_info(f"Already had {name} (0x{g:04X}).")
def action_clear_all():
_food_graphics.clear()
_save_foods()
_rebuild_list_area()
_set_info("Cleared all food types.")
_close_context()
def action_stop():
global _stop_requested
_stop_requested = True
_set_info("Stop requested…")
def _finish_and_dump(prefix: str, total: int):
"""Shared epilogue: scatter trash on the ground and report."""
dropped = _drop_trash_to_ground()
msg = f"{prefix} Ate {total}. Dumped {dropped} item(s) around your feet."
_set_info(msg)
API.SysMsg(msg, 32)
def action_eat_now():
global _stop_requested
_close_context()
if not _food_graphics:
_set_info("No food types saved.")
return
_stop_requested = False
API.ClearJournal()
total = 0
for gfx in sorted(_food_graphics):
if _stop_requested:
_finish_and_dump("Stopped.", total)
return
items = API.FindTypeAll(gfx, API.Backpack) # list[PyItem] or None
if not items:
continue
for it in items:
API.ProcessCallbacks()
if _stop_requested:
_finish_and_dump("Stopped.", total)
return
if API.IsGlobalCooldownActive() or API.IsProcessingUseItemQueue():
API.Pause(0.2)
continue
API.UseObject(it)
API.Pause(FOOD_EAT_DELAY)
total += 1
if API.InJournalAny(TOO_FULL_LINES):
_finish_and_dump("Stopped (too full).", total)
return
_finish_and_dump("Done.", total)
def action_close():
global _stop_requested
_stop_requested = True
_close_context()
API.CloseGumps()
API.Stop()
# ============================= Scroll Grid =============================
def _rebuild_list_area():
"""Rebuild the scroll area with 2x icons; double-click icon to delete."""
global _list_area
try:
if _list_area:
_list_area.Dispose()
except Exception:
pass
_list_area = API.CreateGumpScrollArea(SA_X, SA_Y, SA_W, SA_H)
_gump.Add(_list_area)
per_row = max(1, SA_W // CELL_W)
foods = sorted(_food_graphics)
if not foods:
none_lbl = Label("(none added yet)", 16, "#AAAAAA")
none_lbl.SetRect(0, 0, SA_W - 20, 20)
_list_area.Add(none_lbl)
return
for idx, g in enumerate(foods):
cx = (idx % per_row) * CELL_W
cy = (idx // per_row) * CELL_H
# Center the 2x icon in its cell (sprites anchor bottom-right)
cell_center_x = cx + (CELL_W // 2)
cell_center_y = cy + 44
icon_x = cell_center_x - (ICON_W // 2)
icon_y = cell_center_y - (ICON_H // 2)
pic = API.CreateGumpItemPic(g, ICON_W, ICON_H)
pic.SetRect(icon_x, icon_y, ICON_W, ICON_H)
_list_area.Add(pic)
hexstr = f"0x{g:04X}"
lbl = Label(hexstr, 14, "#FFD966", align="center", maxW=CELL_W)
lbl.SetRect(cx, cy + CELL_H - 16, CELL_W, 16)
_list_area.Add(lbl)
# Double-click detector to open confirm overlay
click_key = ("food", g, idx)
def open_confirm(hx=hexstr):
_open_delete_confirm_centered(hx)
_attach_double_click(pic, click_key, open_confirm)
# ============================== Build Gump =============================
def build_gump():
global _gump, _list_area, _info_label
_gump = API.CreateGump(True, True, True)
_gump.SetRect(MAIN_X, MAIN_Y, MAIN_W, MAIN_H)
# Background
bg = API.CreateGumpColorBox(0.85, "#1E1E1E")
bg.SetRect(0, 0, MAIN_W, MAIN_H)
_gump.Add(bg)
# Title
title = Label("Eat Everything!", 22, "#FFB000", align="center", maxW=MAIN_W)
title.SetRect(0, 8, MAIN_W, 28)
_gump.Add(title)
# Static header (does not scroll)
header = Label("Food Types (double-click to delete)", 16, "#FFFFFF")
header.SetRect(10, HEADER_Y, SA_W, 20)
_gump.Add(header)
# Scroll grid
_list_area = None
_rebuild_list_area()
# Buttons row
W, H, GAP = 70, 24, 6
X0 = 10
x1 = X0 + (W + GAP) * 1
x2 = X0 + (W + GAP) * 2
x3 = X0 + (W + GAP) * 3
x4 = X0 + (W + GAP) * 4
btn_add = API.CreateSimpleButton("[Add]", W, H)
btn_add.SetPos(X0, BTN_Y)
API.AddControlOnClick(btn_add, action_add_food)
_gump.Add(btn_add)
btn_clear = API.CreateSimpleButton("[Clear]", W, H)
btn_clear.SetPos(x1, BTN_Y)
API.AddControlOnClick(btn_clear, action_clear_all)
_gump.Add(btn_clear)
btn_eat = API.CreateSimpleButton("[EAT!]", W, H)
btn_eat.SetPos(x2, BTN_Y)
API.AddControlOnClick(btn_eat, action_eat_now)
_gump.Add(btn_eat)
btn_stop = API.CreateSimpleButton("[STOP!]", W, H)
btn_stop.SetPos(x3, BTN_Y)
API.AddControlOnClick(btn_stop, action_stop)
_gump.Add(btn_stop)
btn_close = API.CreateSimpleButton("[Close]", W, H)
btn_close.SetPos(x4, BTN_Y)
API.AddControlOnClick(btn_close, action_close)
_gump.Add(btn_close)
# Status line
_info_label = Label("", 14, "#C0C0C0")
_info_label.SetRect(10, 240, 380, 16)
_gump.Add(_info_label)
API.AddGump(_gump)
# ================================ Entry ================================
_load_foods()
build_gump()
_set_info("Add types, then EAT!")
while True:
API.ProcessCallbacks()
API.Pause(0.1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment