fix(sanctuary): trier coffres et barrels et garder la hotbar en bas #120
+4
-2
@@ -7062,8 +7062,10 @@ tasks.register("verifyAlpha223Release") {
|
||||
|| !sortClient.contains("instanceof CreativeModeInventoryScreen")
|
||||
|| !sortClient.contains("playButtonClickSound")
|
||||
|| !sortPayload.contains('SanctuaryMod.id("c2s/sort_inventory")')
|
||||
|| !sortPayload.contains("Intentionally empty")
|
||||
|| !networking.contains("SanctuaryInventorySort.sort(context.player())")
|
||||
|| !sortPayload.contains("buffer.writeBoolean(container)")
|
||||
|| !inventorySort.contains("sortOpenContainer")
|
||||
|| !inventorySort.contains("instanceof ChestMenu")
|
||||
|| !networking.contains("SanctuaryInventorySort.sort(context.player(), payload.container())")
|
||||
|| !networking.contains("PROTOCOL_VERSION = 22")
|
||||
|| !displayMode.contains('TEXT("text")')
|
||||
|| !displayMode.contains('PANEL("panel")')
|
||||
|
||||
@@ -639,6 +639,14 @@ tasks.register("verifySanctuary") {
|
||||
,'matchesMouse'
|
||||
,'int bankIndex(int visualRow, int visibleRows)'
|
||||
,'playButtonClickSound'
|
||||
,'requestVisualRow(client, 0, currentRows)'
|
||||
,'isSortableStorageMenu'
|
||||
,'instanceof ChestMenu'
|
||||
,'instanceof ShulkerBoxMenu'
|
||||
,'instanceof HopperMenu'
|
||||
,'instanceof DispenserMenu'
|
||||
,'buffer.writeBoolean(container)'
|
||||
,'sortOpenContainer'
|
||||
,'GLFW.GLFW_KEY_TAB'
|
||||
,'HOTBAR_TEXTURE'
|
||||
,'0.0F, 0.0F, 182, 22, 182, 22'
|
||||
|
||||
+3
-1
@@ -33,7 +33,9 @@ public final class SanctuaryClientProgress {
|
||||
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (currentRows > 1) {
|
||||
SanctuaryHotbarRowsClient.requestVisualRow(client, currentRows - 1, currentRows);
|
||||
// Visual 0 is the bottom bank line after bankIndex inversion. Keep the
|
||||
// live hotbar there on login and after an inventory upgrade.
|
||||
SanctuaryHotbarRowsClient.requestVisualRow(client, 0, currentRows);
|
||||
}
|
||||
if (client.gui.screen() instanceof AbstractContainerScreen<?> screen) {
|
||||
screen.resize(client.getWindow().getGuiScaledWidth(),
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public final class SanctuaryHotbarRowsClient {
|
||||
return requestVisualRow(client, targetVisualRow, unlockedRows);
|
||||
}
|
||||
|
||||
/** Makes the requested visible row the real hotbar, notably after an inventory upgrade. */
|
||||
/** Makes the requested visible row the real hotbar. Visual 0 is the bottom line. */
|
||||
public static boolean requestVisualRow(Minecraft client, int targetVisualRow, int unlockedRows) {
|
||||
unlockedRows = Math.clamp(unlockedRows, 1, SanctuaryInventoryCapacity.MAX_ROW_COUNT);
|
||||
if (pending || unlockedRows <= 1 || client.player == null || client.getConnection() == null
|
||||
|
||||
+25
-4
@@ -1,5 +1,7 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.client;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryInventorySort;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.mixin.client.AbstractContainerScreenAccessor;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.network.SortInventoryPayload;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -7,6 +9,8 @@ import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
|
||||
/** Sends a sort request while a survival container is open. */
|
||||
public final class SanctuaryInventorySortClient {
|
||||
@@ -17,18 +21,35 @@ public final class SanctuaryInventorySortClient {
|
||||
if (client.gui.screen() != null && client.gui.screen().getFocused() instanceof EditBox) {
|
||||
return false;
|
||||
}
|
||||
return request(client);
|
||||
return request(client, null);
|
||||
}
|
||||
|
||||
public static boolean request(Minecraft client) {
|
||||
return request(client, null);
|
||||
}
|
||||
|
||||
public static boolean request(Minecraft client, Slot underCursor) {
|
||||
if (client.player == null || client.level == null
|
||||
|| !(client.gui.screen() instanceof AbstractContainerScreen<?>)
|
||||
|| client.gui.screen() instanceof CreativeModeInventoryScreen
|
||||
|| !(client.gui.screen() instanceof AbstractContainerScreen<?> screen)
|
||||
|| screen instanceof CreativeModeInventoryScreen
|
||||
|| !ClientPlayNetworking.canSend(SortInventoryPayload.ID)) {
|
||||
return false;
|
||||
}
|
||||
boolean container = resolveContainerTarget(screen, underCursor);
|
||||
AbstractWidget.playButtonClickSound(client.getSoundManager());
|
||||
ClientPlayNetworking.send(new SortInventoryPayload());
|
||||
ClientPlayNetworking.send(new SortInventoryPayload(container));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean resolveContainerTarget(AbstractContainerScreen<?> screen,
|
||||
Slot underCursor) {
|
||||
if (screen instanceof InventoryScreen) return false;
|
||||
Slot hovered = underCursor != null ? underCursor
|
||||
: ((AbstractContainerScreenAccessor) screen).sanctuary$getHoveredSlot();
|
||||
if (hovered != null) {
|
||||
return SanctuaryInventorySort.isSortableStorageMenu(screen.getMenu())
|
||||
&& !SanctuaryInventorySort.isPlayerInventorySlot(hovered);
|
||||
}
|
||||
return SanctuaryInventorySort.isSortableStorageMenu(screen.getMenu());
|
||||
}
|
||||
}
|
||||
|
||||
+52
-3
@@ -10,11 +10,18 @@ import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ChestMenu;
|
||||
import net.minecraft.world.inventory.DispenserMenu;
|
||||
import net.minecraft.world.inventory.HopperMenu;
|
||||
import net.minecraft.world.inventory.ShulkerBoxMenu;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Server-authoritative sort of every unlocked inventory row except the live hotbar.
|
||||
* Armor, off-hand, crafting, cape and companion slots stay untouched.
|
||||
* Server-authoritative sort of player storage or an open chest-like container.
|
||||
* Armor, off-hand, crafting, cape, companion slots and the live hotbar stay
|
||||
* untouched. Furnaces, shops and the storage terminal are never rewritten.
|
||||
*/
|
||||
public final class SanctuaryInventorySort {
|
||||
private static final int COOLDOWN_TICKS = 8;
|
||||
@@ -85,11 +92,31 @@ public final class SanctuaryInventorySort {
|
||||
ServerPlayerEvents.LEAVE.register(player -> LAST_SORT_TICK.remove(player.getUUID()));
|
||||
}
|
||||
|
||||
public static void sort(ServerPlayer player) {
|
||||
public static boolean isSortableStorageMenu(AbstractContainerMenu menu) {
|
||||
return menu instanceof ChestMenu
|
||||
|| menu instanceof ShulkerBoxMenu
|
||||
|| menu instanceof HopperMenu
|
||||
|| menu instanceof DispenserMenu;
|
||||
}
|
||||
|
||||
public static boolean isPlayerInventorySlot(Slot slot) {
|
||||
if (slot == null) return false;
|
||||
Container container = slot.container;
|
||||
return container instanceof Inventory
|
||||
|| container instanceof SanctuaryOverflowContainer
|
||||
|| container instanceof SanctuaryCapeContainer
|
||||
|| container instanceof SanctuaryCompanionEggContainer;
|
||||
}
|
||||
|
||||
public static void sort(ServerPlayer player, boolean container) {
|
||||
long now = player.level().getGameTime();
|
||||
Long previous = LAST_SORT_TICK.get(player.getUUID());
|
||||
if (previous != null && now - previous < COOLDOWN_TICKS) return;
|
||||
LAST_SORT_TICK.put(player.getUUID(), now);
|
||||
if (container) {
|
||||
sortOpenContainer(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int unlockedRows = SanctuaryInventoryCapacity.unlockedRows(player);
|
||||
if (unlockedRows <= 1) return;
|
||||
@@ -123,6 +150,28 @@ public final class SanctuaryInventorySort {
|
||||
if (player.containerMenu != player.inventoryMenu) player.containerMenu.broadcastChanges();
|
||||
}
|
||||
|
||||
private static void sortOpenContainer(ServerPlayer player) {
|
||||
AbstractContainerMenu menu = player.containerMenu;
|
||||
if (menu == null || menu == player.inventoryMenu || !isSortableStorageMenu(menu)
|
||||
|| !menu.stillValid(player)) {
|
||||
return;
|
||||
}
|
||||
List<Slot> sortable = new ArrayList<>();
|
||||
for (Slot slot : menu.slots) {
|
||||
if (!slot.isActive() || isPlayerInventorySlot(slot)) continue;
|
||||
sortable.add(slot);
|
||||
}
|
||||
if (sortable.isEmpty()) return;
|
||||
List<ItemStack> items = new ArrayList<>(sortable.size());
|
||||
for (Slot slot : sortable) items.add(slot.getItem().copy());
|
||||
SanctuaryInventorySorter.compactAndSort(items, STACKS);
|
||||
for (int index = 0; index < sortable.size(); index++) {
|
||||
Slot slot = sortable.get(index);
|
||||
slot.container.setItem(slot.getContainerSlot(), items.get(index));
|
||||
}
|
||||
menu.broadcastChanges();
|
||||
}
|
||||
|
||||
private static ItemStack getRowItem(Inventory inventory, Container overflow,
|
||||
int logicalRow, int column) {
|
||||
if (logicalRow < SanctuaryInventoryCapacity.VANILLA_ROW_COUNT) {
|
||||
|
||||
+4
@@ -1,6 +1,7 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
|
||||
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
@@ -21,4 +22,7 @@ public interface AbstractContainerScreenAccessor {
|
||||
@Mutable
|
||||
@Accessor("imageHeight")
|
||||
void sanctuary$setImageHeight(int imageHeight);
|
||||
|
||||
@Accessor("hoveredSlot")
|
||||
Slot sanctuary$getHoveredSlot();
|
||||
}
|
||||
|
||||
+2
-1
@@ -251,7 +251,8 @@ abstract class AbstractContainerScreenMixin implements SanctuaryContainerScreenG
|
||||
private void sanctuary$sortInventory(MouseButtonEvent event, boolean doubleClick,
|
||||
CallbackInfoReturnable<Boolean> cir) {
|
||||
if (!SanctuaryClient.isSortInventoryMouse(event)) return;
|
||||
if (SanctuaryInventorySortClient.request(Minecraft.getInstance())) {
|
||||
if (SanctuaryInventorySortClient.request(Minecraft.getInstance(),
|
||||
sanctuary$slotAt(event.x(), event.y()))) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -6,6 +6,7 @@ import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryInventorySortClient;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryZoom;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.MouseHandler;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.input.MouseButtonEvent;
|
||||
import net.minecraft.client.input.MouseButtonInfo;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
@@ -32,6 +33,9 @@ public abstract class MouseHandlerMixin {
|
||||
if (action != GLFW.GLFW_PRESS) return;
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (windowPointer != client.getWindow().handle()) return;
|
||||
if (client.gui.screen() instanceof AbstractContainerScreen<?>) {
|
||||
return;
|
||||
}
|
||||
MouseButtonEvent event = new MouseButtonEvent(0.0, 0.0, buttonInfo);
|
||||
if (!SanctuaryClient.isSortInventoryMouse(event)) return;
|
||||
if (SanctuaryInventorySortClient.request(client)) {
|
||||
|
||||
+2
-1
@@ -75,7 +75,8 @@ public final class SanctuaryNetworking {
|
||||
}
|
||||
}));
|
||||
ServerPlayNetworking.registerGlobalReceiver(SortInventoryPayload.ID, (payload, context) ->
|
||||
context.server().execute(() -> SanctuaryInventorySort.sort(context.player())));
|
||||
context.server().execute(() ->
|
||||
SanctuaryInventorySort.sort(context.player(), payload.container())));
|
||||
ServerPlayNetworking.registerGlobalReceiver(CompanionPowerUsePayload.ID, (payload, context) ->
|
||||
context.server().execute(() -> SanctuaryCompanionPowers.use(context.player())));
|
||||
ServerPlayNetworking.registerGlobalReceiver(SitRequestPayload.ID, (payload, context) ->
|
||||
|
||||
+7
-4
@@ -5,19 +5,22 @@ import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
|
||||
/** A parameterless client intent. The server owns which unlocked rows are sorted. */
|
||||
public record SortInventoryPayload() implements CustomPacketPayload {
|
||||
/**
|
||||
* Bounded client intent: player storage or the open storage container.
|
||||
* The server still decides which slots exist and ignores non-storage menus.
|
||||
*/
|
||||
public record SortInventoryPayload(boolean container) implements CustomPacketPayload {
|
||||
public static final Type<SortInventoryPayload> ID =
|
||||
new Type<>(SanctuaryMod.id("c2s/sort_inventory"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, SortInventoryPayload> CODEC =
|
||||
CustomPacketPayload.codec(SortInventoryPayload::write, SortInventoryPayload::new);
|
||||
|
||||
private SortInventoryPayload(RegistryFriendlyByteBuf buffer) {
|
||||
this();
|
||||
this(buffer.readBoolean());
|
||||
}
|
||||
|
||||
private void write(RegistryFriendlyByteBuf buffer) {
|
||||
// Intentionally empty: no client-supplied slot list or item identity can be trusted.
|
||||
buffer.writeBoolean(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
@@ -43,6 +43,21 @@ public final class SanctuaryInventorySortSmoke {
|
||||
"Dirt remainder must keep the leftover count");
|
||||
require(storage.get(6).isEmpty(), "Unused storage slots must end empty");
|
||||
|
||||
List<FakeStack> chest = new ArrayList<>(List.of(
|
||||
FakeStack.EMPTY,
|
||||
new FakeStack("minecraft:oak_log", "", 16, 64),
|
||||
new FakeStack("minecraft:oak_log", "", 50, 64),
|
||||
new FakeStack("minecraft:arrow", "", 32, 64)
|
||||
));
|
||||
SanctuaryInventorySorter.compactAndSort(chest, access);
|
||||
require(chest.get(0).id.equals("minecraft:arrow") && chest.get(0).count == 32,
|
||||
"A chest-like list must compact from the first slot");
|
||||
require(chest.get(1).id.equals("minecraft:oak_log") && chest.get(1).count == 64,
|
||||
"Identical chest stacks must merge up to max size");
|
||||
require(chest.get(2).id.equals("minecraft:oak_log") && chest.get(2).count == 2,
|
||||
"Chest remainder must follow the merged stack");
|
||||
require(chest.get(3).isEmpty(), "Unused chest slots must end empty");
|
||||
|
||||
List<FakeStack> hotbar = new ArrayList<>(List.of(
|
||||
new FakeStack("minecraft:torch", "", 8, 64),
|
||||
new FakeStack("minecraft:bread", "", 3, 64)
|
||||
|
||||
Reference in New Issue
Block a user