Sophisticated Tab

A simple mod which integrates Sophisticated Backpacks and Legendary Tabs for convenience; access your backpack from the inventory with the simple click of a tab, returning to your inventory the same way.

File Details

Sophisticated Tab 0.3.0 (Forge 1.20.1)

  • R
  • May 11, 2026
  • 17.39 KB
  • 48
  • 1.20.1
  • Forge

File Name

sophisticatedtab-0.3.0.jar

Supported Versions

  • 1.20.1

Curse Maven Snippet

Forge

implementation fg.deobf("curse.maven:sophisticated-tab-1540042:8075075")
Curse Maven does not yet support mods that have disabled 3rd party sharing

Learn more about Curse Maven

[1.20.1-0.3.0] - 2026-05-11

Fixed

  • Backpack tab chrome no longer bleeds the LegendaryTabs backpack silhouette behind the real ItemStack icon. v0.2.0 used the LegendaryTabs atlas backpack sprite at (27, 46) as the chrome background, then overlaid the player's actual backpack item on top. The two icons visibly overlapped — the LT atlas backpack outline was visible around the edges of each player item, producing a "brown halo" on every tab. v0.3.0 ships its own backpack_tab.png at assets/sophisticatedtab/textures/gui/ — a 52×22 atlas (normal at U=0, hover/active at U=26) derived pixel-for-pixel from LegendaryTabs' own Inventory tab with the central 16×16 icon rectangle overwritten with the chrome's body-fill color. The result has the exact LT tab shape (rounded top corners, dark border, top/left bevel highlight, right/bottom shadow, flat bottom) but no baked-in icon — so the real ItemStack overlays cleanly and the tab visually matches the rest of the LT strip.
  • Active backpack tab now stays highlighted while its BackpackScreen is open. v0.2.0's BackpackTab.isCurrentlyUsed(Screen) returned a flat false, so the tab corresponding to the currently-open backpack rendered identically to inactive tabs — unlike BackToInventoryTab, which highlights when the inventory screen is open. v0.3.0 implements the highlight by reading BackpackScreen.getMenu()BackpackContainer.getBackpackContext()IBackpackWrapper.getContentsUuid() and comparing against this tab's descriptor's UUID. Because each Sophisticated Backpack stores its own UUID in NBT (IStorageWrapper.getContentsUuid()), the match survives slot moves, identical-tier backpacks, and dye/rename changes. When isCurrentlyUsed returns true, LegendaryTabs sets the TabButton's isDisabled=true, which both renders the tab in its hover state and no-ops the click (LegendaryTabs' TabButton.onPress already early-returns when disabled).

Added

Changed

  • BackpackTab.javaTEXTURE now points at our own sophisticatedtab:textures/gui/backpack_tab.png (52×22) instead of legendarytabs:textures/gui/tab_menu_buttons.png (256×256). HOVER_DX reduced from 54 to 26. The blit uses the 9-arg GuiGraphics.blit(rl, x, y, u, v, w, h, textureWidth, textureHeight) overload so the explicit texture-sheet dimensions are passed (the 6-arg overload would assume 256×256 and miscompute UVs).
  • BackpackTab.isCurrentlyUsed — implemented; see Fixed.

Design notes for future contributors

  • Why a UUID instead of (handlerName, identifier, slot) for the active-tab match? BackpackContext$Item keeps handlerName and identifier as protected fields with no public accessors. Reaching them from this addon's package would require either reflection or an access transformer — both fragile across upstream patch updates. IStorageWrapper.getContentsUuid() is a public interface method and gives a stable identity that survives the stack moving between slots (which would invalidate slot-based matching).
  • What if the player opens a BackpackContext$ItemSubBackpack (nested backpack)? That context's wrapper has its own UUID, distinct from any top-level descriptor's UUID, so isCurrentlyUsed correctly returns false for every top-level tab. Sub-backpack tabs aren't enumerated yet (still on the Planned list).
  • Why ship the chrome PNG instead of drawing the chrome programmatically? Resource-pack authors should be able to restyle the chrome alongside other GUI textures, and a tuneable PNG asset gives the tightest control over how the button reads at GUI scales 1–4. The PNG is regenerated at build time by .tools/generate_tab_chrome.py: copy LT's Inventory tab verbatim, overwrite the central 16×16 rectangle (where the ItemStack will sit) with the chrome's body-fill color. If LT updates their tab atlas styling in a future release, run python .tools/generate_tab_chrome.py to refresh.
  • Why no BackpackTab.isCurrentlyUsed short-circuit when no player is online? TabBase.isCurrentlyUsed(Screen) is only invoked by LT's TabButton.setTabBase(...), which is called from TabsMenu.initScreenButtons(...) — a screen-init event. On a client, screens only init when a player is present. Defensive null-checks are kept anyway because LT's API doesn't guarantee the call site won't broaden.

[1.20.1-0.2.0] - 2026-05-11

Changed — One tab per backpack

  • Per-backpack tabs replace the single "open first backpack" tab. The previous SophisticatedBackpacksTab.java registered a single TabBase that called BackpackOpenMessage() with no arguments — server-side, that opened whichever backpack PlayerInventoryProvider.runOnBackpacks(...) yielded first. v0.2.0 pre-registers eight indexed BackpackTab.java instances at LegendaryTabsCompat.register(); each instance resolves its own BackpackDescriptor.java (handlerName + identifier + slot + iconStack + wrapper) from a fresh findAllBackpacks(player) call at render time. Disabled tabs collapse out of LT's enabledTabs row, so users with 0 backpacks see no tabs, users with 3 see 3, users with 10+ see 8 (the pool cap).

  • Click sends the slot-aware BackpackOpenMessage(int slot, String identifier, String handlerName). Sophisticated Backpacks 1.20.1-3.24.x already ships this constructor; verified by decompiling sophisticatedbackpacks-1.20.1-3.24.38.1738.jar. The server handler at BackpackOpenMessage.handleMessage(...) checks handlerName.isEmpty() — if non-empty, it builds a BackpackContext$Item(handlerName, identifier, slotIndex, wasOpenFromInventory) and opens that exact backpack (with built-in slot fixup for offhand 38→2 and chest armor 40→0). No custom packet, no server-side code, no validation layer needed in this addon.

  • Real ItemStack icons via GuiGraphics.renderItem. Each tab blits LegendaryTabs' backpack-shaped chrome at (27, 46) as the button background, then draws the descriptor's live ItemStack at (x+5, y+3) on top. Dye colors, anvil renames, upgrade decorations, resource-pack model overrides, and any custom item renderer flow through automatically because rendering uses the vanilla item-rendering pipeline. The tooltip is iconStack.getHoverName() — so renamed backpacks show their custom name on hover.

Added

  • BackpackDescriptor.java — immutable record carrying (handlerName, identifier, slot, iconStack, tooltip, wrapper). The wrapper is resolved once at construction so screen-sizing math doesn't have to re-walk the capability chain.
  • BackpackTab.javaTabBase constructed with an index 0..7; isEnabled(player) returns true only when findAllBackpacks(player).size() > index. Priority is 20 + index, putting backpack tabs immediately after BackToInventoryTab (priority 10).

Changed

  • SophisticatedBackpacksLocator.javafindAllBackpacks(Player) now walks the entire PlayerInventoryProvider.runOnBackpacks(...) iteration (callback returns false to keep going); findFirstBackpack and hasOpenableBackpack now wrap that list rather than short-circuiting on the first hit.
  • SophisticatedBackpacksSizing.javagetWidth / getHeight map through BackpackDescriptor::wrapper to reach the IBackpackWrapper (signature change from the locator).
  • LegendaryTabsCompat.java — registers BackToInventoryTab + 8 × BackpackTab(i) in a loop. Pool size is BACKPACK_TAB_POOL = 8.
  • en_us.json — removed unused tooltip.sophisticatedtab.tab.sophisticatedbackpacks; added tooltip.sophisticatedtab.tab.backpack_empty ("No backpack") as the fallback tooltip when a tab's descriptor is briefly unresolvable.

Removed

  • SophisticatedBackpacksTab.java — replaced wholesale by BackpackTab.
  • tooltip.sophisticatedtab.tab.sophisticatedbackpacks lang key — each tab now uses iconStack.getHoverName() for its tooltip.