pin bags
This commit is contained in:
@@ -22,7 +22,7 @@ public final class BagAccess {
|
||||
}
|
||||
BagCompat.BagHandler handler = BagCompat.findHandler(stack);
|
||||
if (handler != null) {
|
||||
bags.add(new BagEntry(slot, stack.copy(), handler));
|
||||
bags.add(new BagEntry(slot, stack.copy(), handler, BagCompat.getIdentity(stack)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ package com.trunksbomb.bagtabs.bag;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public record BagEntry(int slot, ItemStack stack, BagCompat.BagHandler handler) {
|
||||
public record BagEntry(int slot, ItemStack stack, BagCompat.BagHandler handler, BagIdentity identity) {
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -68,19 +70,24 @@ public final class BagTabOverlay {
|
||||
|
||||
for (RenderedTab tab : tabs) {
|
||||
renderTab(guiGraphics, tab, mouseX, mouseY, carriedStack);
|
||||
guiGraphics.renderItem(tab.entry().stack(), tab.x() + 3, tab.y() + 3);
|
||||
guiGraphics.renderItem(tab.entry().stack(), tab.x() + 3, tab.y() + 2);
|
||||
}
|
||||
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (tab.isHovered(mouseX, mouseY)) {
|
||||
guiGraphics.renderTooltip(Minecraft.getInstance().font, tab.entry().stack().getHoverName(), mouseX, mouseY);
|
||||
guiGraphics.renderTooltip(
|
||||
Minecraft.getInstance().font,
|
||||
getTooltipLines(tab, tabs).stream().map(Component::getVisualOrderText).toList(),
|
||||
mouseX,
|
||||
mouseY
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void mouseClicked(ScreenEvent.MouseButtonPressed.Pre event) {
|
||||
if (!(event.getScreen() instanceof AbstractContainerScreen<?> screen) || !supportsTabs(screen) || event.getButton() != 0) {
|
||||
if (!(event.getScreen() instanceof AbstractContainerScreen<?> screen) || !supportsTabs(screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,11 +96,31 @@ public final class BagTabOverlay {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!screen.getMenu().getCarried().isEmpty()) {
|
||||
List<RenderedTab> tabs = getRenderedTabs(screen, player);
|
||||
|
||||
if (event.getButton() == 1) {
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (!tab.isHovered(event.getMouseX(), event.getMouseY())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TabPinManager.ToggleResult result = TabPinManager.togglePin(tab.entry(), tabs.stream().map(RenderedTab::entry).toList());
|
||||
if (result.changed()) {
|
||||
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
} else {
|
||||
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.VILLAGER_NO, 0.8F));
|
||||
}
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (RenderedTab tab : getRenderedTabs(screen, player)) {
|
||||
if (event.getButton() != 0 || !screen.getMenu().getCarried().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (tab.isHovered(event.getMouseX(), event.getMouseY())) {
|
||||
PacketDistributor.sendToServer(new OpenBagPayload(tab.entry().slot()));
|
||||
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
@@ -146,7 +173,7 @@ public final class BagTabOverlay {
|
||||
}
|
||||
|
||||
private static List<RenderedTab> getRenderedTabs(AbstractContainerScreen<?> screen, Player player) {
|
||||
List<BagEntry> bags = BagAccess.findBags(player);
|
||||
List<BagEntry> bags = TabPinManager.sortTabs(BagAccess.findBags(player));
|
||||
List<RenderedTab> renderedTabs = new ArrayList<>();
|
||||
int activeBagSlot = getActiveBagSlot(screen);
|
||||
int leftBound = getInventoryLeftBound(screen, player);
|
||||
@@ -160,7 +187,8 @@ public final class BagTabOverlay {
|
||||
break;
|
||||
}
|
||||
|
||||
renderedTabs.add(new RenderedTab(bag, x, y, bag.slot() == activeBagSlot));
|
||||
boolean pinned = TabPinManager.isPinned(bag, bags);
|
||||
renderedTabs.add(new RenderedTab(bag, x, y, bag.slot() == activeBagSlot, pinned));
|
||||
x += TAB_WIDTH + TAB_GAP;
|
||||
}
|
||||
|
||||
@@ -206,6 +234,12 @@ public final class BagTabOverlay {
|
||||
guiGraphics.fill(tab.x() + 2, tab.y() + 2, tab.x() + TAB_WIDTH - 2, tab.y() + 4, 0x90FFFFFF);
|
||||
}
|
||||
|
||||
if (tab.pinned()) {
|
||||
guiGraphics.fill(tab.x() + 2, tab.y() + 4, tab.x() + 6, tab.y() + 5, 0xFFFFDA6B);
|
||||
guiGraphics.fill(tab.x() + 3, tab.y() + 5, tab.x() + 5, tab.y() + 8, 0xFFFFDA6B);
|
||||
guiGraphics.fill(tab.x() + 4, tab.y() + 8, tab.x() + 5, tab.y() + 9, 0xFFD94A4A);
|
||||
}
|
||||
|
||||
if (!carriedStack.isEmpty()) {
|
||||
if (INSERTABLE_SLOTS.contains(tab.entry().slot())) {
|
||||
renderPlusIndicator(guiGraphics, tab.x() + 15, tab.y() + 3);
|
||||
@@ -264,7 +298,24 @@ public final class BagTabOverlay {
|
||||
guiGraphics.fill(x + 4, y + 4, x + 5, y + 5, 0xFFFF6C6C);
|
||||
}
|
||||
|
||||
private record RenderedTab(BagEntry entry, int x, int y, boolean selected) {
|
||||
private static List<Component> getTooltipLines(RenderedTab tab, List<RenderedTab> tabs) {
|
||||
List<Component> lines = new ArrayList<>();
|
||||
lines.add(tab.entry().stack().getHoverName());
|
||||
lines.add(Component.literal("Left-click: open"));
|
||||
if (tab.pinned()) {
|
||||
lines.add(Component.literal("Right-click: unpin"));
|
||||
} else {
|
||||
String failureReason = TabPinManager.getPinFailureReason(tab.entry(), tabs.stream().map(RenderedTab::entry).toList());
|
||||
if (failureReason == null) {
|
||||
lines.add(Component.literal("Right-click: pin"));
|
||||
} else {
|
||||
lines.add(Component.literal("Rename bag to pin").withStyle(ChatFormatting.ITALIC));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private record RenderedTab(BagEntry entry, int x, int y, boolean selected, boolean pinned) {
|
||||
private boolean isHovered(double mouseX, double mouseY) {
|
||||
return mouseX >= this.x && mouseX < this.x + TAB_WIDTH && mouseY >= this.y && mouseY < this.y + TAB_HEIGHT;
|
||||
}
|
||||
|
||||
178
src/main/java/com/trunksbomb/bagtabs/client/TabPinManager.java
Normal file
178
src/main/java/com/trunksbomb/bagtabs/client/TabPinManager.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package com.trunksbomb.bagtabs.client;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.trunksbomb.bagtabs.BagTabs;
|
||||
import com.trunksbomb.bagtabs.bag.BagEntry;
|
||||
import com.trunksbomb.bagtabs.bag.BagIdentity;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
public final class TabPinManager {
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final Type PINNED_TAB_LIST_TYPE = new TypeToken<List<PinnedTab>>() {}.getType();
|
||||
|
||||
private static boolean loaded = false;
|
||||
private static final List<PinnedTab> PINNED_TABS = new ArrayList<>();
|
||||
|
||||
private TabPinManager() {
|
||||
}
|
||||
|
||||
public static List<BagEntry> sortTabs(List<BagEntry> bags) {
|
||||
ensureLoaded();
|
||||
return bags.stream()
|
||||
.sorted(Comparator
|
||||
.comparingInt((BagEntry bag) -> isPinned(bag, bags) ? 0 : 1)
|
||||
.thenComparingInt(bag -> getPinnedOrder(bag, bags))
|
||||
.thenComparingInt(BagEntry::slot))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static boolean isPinned(BagEntry bag, List<BagEntry> allBags) {
|
||||
return resolvePinnedBag(bag.identity(), allBags)
|
||||
.map(resolved -> resolved.slot() == bag.slot())
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
public static String getPinFailureReason(BagEntry bag, List<BagEntry> allBags) {
|
||||
BagIdentity identity = bag.identity();
|
||||
if (identity == null || identity.stable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (PinnedTab pinnedTab : PINNED_TABS) {
|
||||
if (!pinnedTab.identityKey().equals(identity.key())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BagEntry resolved = resolvePinnedBag(identity, allBags).orElse(null);
|
||||
if (resolved != null && resolved.slot() != bag.slot()) {
|
||||
return "Only one fallback-identified bag with this name can be pinned.";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ToggleResult togglePin(BagEntry bag, List<BagEntry> allBags) {
|
||||
ensureLoaded();
|
||||
BagIdentity identity = bag.identity();
|
||||
if (identity == null) {
|
||||
return new ToggleResult(false, "This bag can't be pinned yet.");
|
||||
}
|
||||
|
||||
if (isPinned(bag, allBags)) {
|
||||
PINNED_TABS.removeIf(pinnedTab -> pinnedTab.identityKey().equals(identity.key()));
|
||||
save();
|
||||
return new ToggleResult(true, null);
|
||||
}
|
||||
|
||||
String failureReason = getPinFailureReason(bag, allBags);
|
||||
if (failureReason != null) {
|
||||
return new ToggleResult(false, failureReason);
|
||||
}
|
||||
|
||||
PINNED_TABS.add(new PinnedTab(identity.key(), identity.stable(), bag.slot()));
|
||||
save();
|
||||
return new ToggleResult(true, null);
|
||||
}
|
||||
|
||||
private static int getPinnedOrder(BagEntry bag, List<BagEntry> allBags) {
|
||||
for (int i = 0; i < PINNED_TABS.size(); i++) {
|
||||
BagEntry resolved = resolvePinnedBag(PINNED_TABS.get(i).identityKey(), allBags).orElse(null);
|
||||
if (resolved != null && resolved.slot() == bag.slot()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
private static java.util.Optional<BagEntry> resolvePinnedBag(BagIdentity identity, List<BagEntry> allBags) {
|
||||
return identity == null ? java.util.Optional.empty() : resolvePinnedBag(identity.key(), allBags);
|
||||
}
|
||||
|
||||
private static java.util.Optional<BagEntry> resolvePinnedBag(String identityKey, List<BagEntry> allBags) {
|
||||
PinnedTab pinnedTab = PINNED_TABS.stream()
|
||||
.filter(tab -> tab.identityKey().equals(identityKey))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (pinnedTab == null) {
|
||||
return java.util.Optional.empty();
|
||||
}
|
||||
|
||||
List<BagEntry> matches = allBags.stream()
|
||||
.filter(bag -> bag.identity() != null && Objects.equals(bag.identity().key(), identityKey))
|
||||
.sorted(Comparator.comparingInt(BagEntry::slot))
|
||||
.toList();
|
||||
if (matches.isEmpty()) {
|
||||
return java.util.Optional.empty();
|
||||
}
|
||||
|
||||
return matches.stream()
|
||||
.filter(match -> match.slot() == pinnedTab.preferredSlot())
|
||||
.findFirst()
|
||||
.or(() -> java.util.Optional.of(matches.getFirst()));
|
||||
}
|
||||
|
||||
private static void ensureLoaded() {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
Path path = getConfigPath();
|
||||
if (!Files.exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Reader reader = Files.newBufferedReader(path)) {
|
||||
List<PinnedTab> loadedTabs = GSON.fromJson(reader, PINNED_TAB_LIST_TYPE);
|
||||
if (loadedTabs != null) {
|
||||
PINNED_TABS.clear();
|
||||
Set<String> seenKeys = new HashSet<>();
|
||||
for (PinnedTab pinnedTab : loadedTabs) {
|
||||
if (seenKeys.add(pinnedTab.identityKey())) {
|
||||
PINNED_TABS.add(pinnedTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
BagTabs.LOGGER.warn("Failed to load pinned bag tab state", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void save() {
|
||||
Path path = getConfigPath();
|
||||
try {
|
||||
Files.createDirectories(path.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(path)) {
|
||||
GSON.toJson(PINNED_TABS, PINNED_TAB_LIST_TYPE, writer);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
BagTabs.LOGGER.warn("Failed to save pinned bag tab state", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getConfigPath() {
|
||||
return Minecraft.getInstance().gameDirectory.toPath().resolve("config").resolve("bagtabs-pinned-tabs.json");
|
||||
}
|
||||
|
||||
private record PinnedTab(String identityKey, boolean stable, int preferredSlot) {
|
||||
}
|
||||
|
||||
public record ToggleResult(boolean changed, String failureReason) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user