UO Unchained: ITEM_organize_backpack.py

Created: about 23 hours ago on 08/06/2025, 07:07:46 PM
Size: 17792
Category: ITEM

Description: Organizes items in the backpack with specific positioning and directional spacing

""" ITEM Organize Backpack - a Razor Enhanced Python Script for Ultima Online Organizes items in the backpack with specific positioning and directional spacing control: - Reagents: Lower left, left-to-right sorting, combines reagents - Potions: Above reagents, left-to-right sorting with offset spacing - Gems: Second Lowest middle, horizontal sorting left-to-right with offset spacing - Books: Top left, horizontal sorting left-to-right with offset spacing - Trap pouches: Bottom right, vertical spacing offset, NO stacking - Runes: Top right, vertical sorting stop-to-bottom with offset stacking - Tools: Top right middle, horizontal right-to-left with offset stacking These positions are tuned for a "150" container size without scaling items sizes the default is "100" container size so you may want to adjust the x and y values for your settings TODO: - offset the trap pouches vertically VERSION::20250805 """ ORGANIZE_UNKNOWN_ITEMS = True ORGANIZE_REAGENTS = True ORGANIZE_POTIONS = True ORGANIZE_GEMS = True ORGANIZE_TOOLS = True DEBUG_MODE = False # Set to True to enable debug/info messages SPACING_OFFSET = 7 # Pixels to offset spacing MAX_STACK_SIZE = 999 # Maximum items to stack in one location # Item Categories with properties ITEM_GROUPS = { "REAGENTS": { # start 40 offset x = +15 "items": [ {"name": "Black Pearl", "id": 0x0F7A, "x": 40, "y": 300}, {"name": "Blood Moss", "id": 0x0F7B, "x": 55, "y": 300}, {"name": "Garlic", "id": 0x0F84, "x": 70, "y": 300}, {"name": "Ginseng", "id": 0x0F85, "x": 85, "y": 300}, {"name": "Mandrake Root", "id": 0x0F86, "x": 100, "y": 300}, {"name": "Nightshade", "id": 0x0F88, "x": 115, "y": 300}, {"name": "Spider's Silk", "id": 0x0F8D, "x": 130, "y": 300}, {"name": "Sulfurous Ash", "id": 0x0F8C, "x": 145, "y": 300}, # Pagan Reagents {"name": "Bat Wing", "id": 0x0F78, "x": 160, "y": 300}, {"name": "Grave Dust", "id": 0x0F8F, "x": 175, "y": 300}, {"name": "Daemon Blood", "id": 0x0F7D, "x": 190, "y": 300}, {"name": "Nox Crystal", "id": 0x0F8E, "x": 215, "y": 300}, {"name": "Pig Iron", "id": 0x0F8A, "x": 230, "y": 300} ], "move_to_char": True # Special flag for reagents to combine them into one stack }, "POTIONS": { "items": [ {"name": "Heal", "id": 0x0F0C, "x": 0, "y": 100}, {"name": "Cure", "id": 0x0F07, "x": 55, "y": 100}, {"name": "Total Refresh", "id": 0x0F0B, "x": 65, "y": 100}, {"name": "Strength", "id": 0x0F09, "x": 75, "y": 100}, {"name": "Mana Potion", "id": 0x0F0D, "x": 85, "y": 100}, ], "move_to_char": False }, "GEMS": { # start 48 offset x = +12 "items": [ {"name": "Ruby", "id": 0x0F13, "x": 48, "y": 120}, # Red {"name": "Amber", "id": 0x0F25, "x": 60, "y": 120}, # Orange {"name": "Citrine", "id": 0x0F15, "x": 72, "y": 120}, # Yellow {"name": "Tourmaline", "id": 0x0F18, "x": 84, "y": 120}, # GreenPink {"name": "Diamond", "id": 0x0F26, "x": 96, "y": 120}, # White/Clear {"name": "Emerald", "id": 0x0F10, "x": 108, "y": 120}, # Green {"name": "Sapphire", "id": 0x0F11, "x": 120, "y": 120}, # Blue {"name": "Star Sapphire", "id": 0x0F0F, "x": 132, "y": 120}, # Blue {"name": "Amethyst", "id": 0x0F16, "x": 144, "y": 120}, # Violet # No Drop Gems Alternate IDs {"name": "sapphireA", "id": 0x0F19, "x": 156, "y": 120}, # Blue extras in world {"name": "saphireB", "id": 0x0F21, "x": 168, "y": 120}, # Blue extras in world {"name": "TourmalineB", "id": 0x0F2D, "x": 180, "y": 120}, # GreenPink extras in world ], "move_to_char": False }, "BOOKS": { "items": [ {"name": "Spellbook", "id": 0x0EFA, "x": 45, "y": 20} ], "move_to_char": False, "is_spellbook": True # Special flag for spellbook handling }, "TRAP_POUCHES": { "items": [ {"name": "Trap Pouch", "id": 0x0E79, "x": 300, "y": 200} ], "move_to_char": False }, "RUNES": { "items": [ {"name": "Recall Rune", "id": 0x1F14, "x": 300, "y": 0}, {"name": "Gate Rune", "id": 0x1F14, "x": 300, "y": 50} # Same ID, different hue ], "move_to_char": False }, "BANDAGES": { "items": [ {"name": "Bandages", "id": 0x0E21, "x": 300, "y": 75} ], "move_to_char": False }, "TOOLS": { # start 125 offset x = +10 "items": [ {"name": "Sewing Kit", "id": 0x0F9D, "x": 125, "y": 20}, {"name": "Lockpicks", "id": 0x14FC, "x": 135, "y": 20}, {"name": "Backpack", "id": 0x0E75, "x": 145, "y": 20}, {"name": "Bag", "id": 0x0E76, "x": 155, "y": 20}, {"name": "Pouch", "id": 0x0E79, "x": 165, "y": 20}, {"name": "Tinker Tools", "id": 0x1EB8, "x": 175, "y": 20}, {"name": "Scissors", "id": 0x0F9F, "x": 185, "y": 20}, {"name": "Mortar and Pestle", "id": 0x0E9B, "x": 195, "y": 20}, {"name": "Smith's Hammer", "id": 0x13E3, "x": 205, "y": 20}, {"name": "Tongs", "id": 0x0FBB, "x": 215, "y": 20}, {"name": "Saw", "id": 0x1034, "x": 225, "y": 20}, {"name": "Plane", "id": 0x102C, "x": 235, "y": 20}, {"name": "Draw Knife", "id": 0x10E4, "x": 245, "y": 20}, {"name": "Froe", "id": 0x10E5, "x": 255, "y": 20}, {"name": "Scorp", "id": 0x10E7, "x": 265, "y": 20}, {"name": "Inshave", "id": 0x10E6, "x": 275, "y": 20}, {"name": "Pickaxe", "id": 0x0E86, "x": 285, "y": 20}, {"name": "Shovel", "id": 0x0F39, "x": 295, "y": 20}, {"name": "Hatchet", "id": 0x0F43, "x": 305, "y": 20}, {"name": "Fishing Pole", "id": 0x0DC0, "x": 315, "y": 20} ], "move_to_char": False } } # Controls the unknown item box placement and size UNKNOWN_BOX = { 'center_x': 80, 'center_y': 80, 'width': 70, 'height': 40, } #//============================================================ def debug_message(message, color=68): """Send a message if DEBUG_MODE is enabled""" if DEBUG_MODE: Misc.SendMessage(f"[BackpackOrg] {message}", color) def find_items_by_id(item_id, sort_by_hue=False): """Find all items with matching ID in backpack.""" items = [] # Search through all items in backpack for item in Player.Backpack.Contains: if item.ItemID == item_id: items.append(item) if sort_by_hue: # Sort items by hue, handling default hue (0 or -1) specially items.sort(key=lambda x: x.Hue if x.Hue > 0 else 9999) return items def move_spellbooks(items, base_x, base_y): """Special handling for spellbooks - organize by hue.""" if not items: return # Group spellbooks by hue hue_groups = {} for item in items: hue = item.Hue if item.Hue > 0 else 0 if hue not in hue_groups: hue_groups[hue] = [] hue_groups[hue].append(item) # Sort hues for consistent ordering sorted_hues = sorted(hue_groups.keys()) # Position variables current_x = base_x current_y = base_y books_per_row = 5 book_spacing = 5 row_spacing = 10 # Place books by hue groups for hue in sorted_hues: books = hue_groups[hue] for i, book in enumerate(books): x = base_x + (i % books_per_row) * book_spacing y = base_y + (i // books_per_row) * row_spacing Items.Move(book.Serial, Player.Backpack.Serial, book.Amount, x, y) Misc.Pause(600) if i > 0 and i % books_per_row == books_per_row - 1: debug_message(f"Placed {i+1} spellbooks of hue {hue}", 65) # After each hue group, move starting y down for the next group if needed base_y += row_spacing def move_items(items, target_x, target_y, group_config): """Move items to target location with appropriate handling.""" if not items: return # Special handling for spellbooks if group_config.get("is_spellbook", False): move_spellbooks(items, target_x, target_y) return if group_config.get("move_to_char", False): # First move reagents to character for item in items: Items.Move(item.Serial, Player.Serial, item.Amount, 0, 0) Misc.Pause(600) # Then move them to their designated spot in backpack Misc.Pause(1200) # Extra pause items_on_char = find_items_by_id(items[0].ItemID) if items_on_char: stack_count = 0 current_x = target_x current_y = target_y for item in items_on_char: Items.Move(item.Serial, Player.Backpack.Serial, item.Amount, current_x, current_y) Misc.Pause(600) def get_known_item_ids(): """Return a set of all known item IDs from ITEM_GROUPS.""" ids = set() for group in ITEM_GROUPS.values(): for item_def in group["items"]: ids.add(item_def["id"]) return ids def move_unknown_items_to_center_box(): """Move all items not in any group to a center box, spreading them evenly to fill the box shape.""" center_x = UNKNOWN_BOX['center_x'] center_y = UNKNOWN_BOX['center_y'] box_width = UNKNOWN_BOX['width'] box_height = UNKNOWN_BOX['height'] known_ids = get_known_item_ids() unknown_items = [item for item in Player.Backpack.Contains if item.ItemID not in known_ids] n = len(unknown_items) if not unknown_items: debug_message("No unknown items to move to center box.", 65) return # Calculate best-fit grid (cols x rows) for n items in box max_cols = max(1, int(box_width // SPACING_OFFSET)) max_rows = max(1, int(box_height // SPACING_OFFSET)) # Try to make the grid as square as possible, but fit in the box best_cols = min(max_cols, int((n * box_width / box_height) ** 0.5 + 0.5)) best_cols = max(1, min(max_cols, best_cols)) best_rows = max(1, min(max_rows, (n + best_cols - 1) // best_cols)) # If too many rows, clamp cols/rows if best_rows > max_rows: best_rows = max_rows best_cols = max(1, (n + best_rows - 1) // best_rows) best_cols = min(best_cols, max_cols) debug_message(f"Moving {n} unknown items in a {best_cols}x{best_rows} grid in center box...", 65) for idx, item in enumerate(unknown_items): row = idx // best_cols col = idx % best_cols x = center_x + col * (box_width // max(1, best_cols - 1)) if best_cols > 1 else center_x y = center_y + row * (box_height // max(1, best_rows - 1)) if best_rows > 1 else center_y Items.Move(item.Serial, Player.Backpack.Serial, item.Amount, x, y) Misc.Pause(600) debug_message(f"Finished moving unknown items to center box.", 65) def organize_backpack(): """Organize items in backpack by type, honoring group enable globals.""" debug_message("Starting backpack organization...", 65) group_enabled = { "REAGENTS": ORGANIZE_REAGENTS, "POTIONS": ORGANIZE_POTIONS, "GEMS": ORGANIZE_GEMS, "TOOLS": ORGANIZE_TOOLS, } for group_name, group_config in ITEM_GROUPS.items(): # Only process group if enabled or not explicitly toggled if group_name in group_enabled and not group_enabled[group_name]: debug_message(f"Skipping {group_name} organization (disabled)", 65) continue debug_message(f"Processing {group_name}...", 65) total_items = 0 for item_def in group_config["items"]: items = find_items_by_id(item_def["id"], sort_by_hue=True) if items: total_items += len(items) debug_message(f"Found {len(items)} {item_def['name']}...", 65) # Each type gets its own stack at its own x/y stack_count = 0 current_x = item_def["x"] current_y = item_def["y"] for item in items: Items.Move(item.Serial, Player.Backpack.Serial, item.Amount, current_x, current_y) Misc.Pause(600) stack_count += 1 if stack_count >= MAX_STACK_SIZE: current_x += SPACING_OFFSET * MAX_STACK_SIZE stack_count = 0 else: current_x += SPACING_OFFSET current_y += SPACING_OFFSET if total_items > 0: debug_message(f"Finished {group_name}: {total_items} items organized", 65) else: debug_message(f"No {group_name} found to organize", 65) debug_message("Backpack organization complete!", 65) if ORGANIZE_UNKNOWN_ITEMS: move_unknown_items_to_center_box() def main(): organize_backpack() if __name__ == "__main__": main()

Version History

Original Version Saved - 8/6/2025, 7:07:46 PM - about 23 hours ago

ITEM_organize_backpack.py

No changes to display
View list of scripts
Disclaimer: This is a fan made site and is not directly associated with Ultima Online or UO staff.