From 1eaa976ecf328c6c20afe5e4eb67c48118f87f95 Mon Sep 17 00:00:00 2001 From: koka Date: Thu, 17 Sep 2026 03:59:13 +0200 Subject: [PATCH] Use only the active hotbar in the clay workshop for beta.112 --- README.md | 19 ++++-- docs/backlog.md | 6 ++ docs/clay-workshop-hotbar-beta112.md | 66 +++++++++++++++++++ gradle.properties | 4 +- .../client/Statuary106ClientChecks.java | 48 +++++++++++--- .../sanctuary/client/ClayWorkshopScreen.java | 11 ++-- .../koka/sanctuary/client/HotbarClient.java | 2 + .../client/AbstractContainerScreenMixin.java | 4 +- .../sanctuary/plans/ClayWorkshopMenu.java | 12 +++- .../assets/sanctuary/lang/en_us.json | 2 +- .../assets/sanctuary/lang/fr_fr.json | 2 +- packwiz/pack.toml | 2 +- 12 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 docs/clay-workshop-hotbar-beta112.md diff --git a/README.md b/README.md index df870cc..8197423 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,9 @@ La [vision complète](docs/vision.md) conserve les intentions ; le L’économie, les claims et les dimensions décrits dans la vision restent à implémenter. -La version **beta.111** reprend ce socle et réorganise l’atelier d’argile avec un -cadre Minecraft nine-slice ; les statuaires suivent le regard à la pose. -Le [ticket](docs/clay-workshop-ui-beta111.md) décrit la compatibilité, les essais, -la publication et la mise à jour de l’instance **Sanctuary Beta**. +La version **beta.112** limite l’atelier d’argile à la hotbar active du joueur, +dans un cadre compact. Les statuaires suivent toujours le regard à la pose. +Le [ticket](docs/clay-workshop-hotbar-beta112.md) décrit le comportement et les essais. La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**. Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées. @@ -34,6 +33,14 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après l’étude de Xaero et des alternatives libres. +## beta.112 — Atelier d’argile et hotbar + +Neuf cases de hotbar et deux emplacements de fabrication, dans un cadre fixe. +Les transferts restent dans la hotbar ; une barre pleine ne consomme pas d’argile. +[Contrat et vérifications](docs/clay-workshop-hotbar-beta112.md) · +[Pack normal](build/Sanctuary-beta.112.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.112.mrpack). + ## beta.111 — Atelier d’argile et pose orientée Cadre nine-slice, aperçu, fabrication et inventaire dans un menu commun. @@ -2064,7 +2071,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr | Fabric API | 0.160.5+26.3 | | Fabric Loom | 1.17.20 | | Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 | -| Sanctuary / pack | beta.111 | +| Sanctuary / pack | beta.112 | Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise uniquement la bibliothèque standard et repère aussi une installation Python @@ -2093,7 +2100,7 @@ décrits dans [Validation](docs/testing.md). Résultats : -- `mods/sanctuary/build/libs/sanctuary-beta.111.jar` : mod à installer avec +- `mods/sanctuary/build/libs/sanctuary-beta.112.jar` : mod à installer avec Fabric API sur la version Minecraft indiquée. - `build/packwiz/` : pack de développement complet, avec le mod construit et l'index vérifié. Voir [Installation packwiz](packwiz/README.md). diff --git a/docs/backlog.md b/docs/backlog.md index 5307977..2150331 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -1,5 +1,11 @@ # Backlog Sanctuary +## STAT-03 — Atelier d’argile et hotbar — beta.112 + +Branche `codex/clay-workshop-hotbar-beta112`. [Contrat et vérifications](clay-workshop-hotbar-beta112.md). +Onze cases : argile, résultat et hotbar active ; hauteur fixe et transferts +limités aux neuf cases, même avec un inventaire agrandi. + ## STAT-02 — Atelier d’argile et orientation — beta.111 Branche `codex/clay-workshop-ui-beta111`. [Contrat et vérifications](clay-workshop-ui-beta111.md). diff --git a/docs/clay-workshop-hotbar-beta112.md b/docs/clay-workshop-hotbar-beta112.md new file mode 100644 index 0000000..3ac82cf --- /dev/null +++ b/docs/clay-workshop-hotbar-beta112.md @@ -0,0 +1,66 @@ +# STAT-03 — Atelier d’argile limité à la hotbar — beta.112 + +Contrat du 17 septembre 2026, branche `codex/clay-workshop-hotbar-beta112`. + +Le menu utilise uniquement les neuf cases de la hotbar active du joueur, +avec les deux emplacements de fabrication. Les rangées de réserve, y compris +les extensions Sanctuary, ne sont ni ajoutées au menu ni utilisées par ses +transferts. La rangée choisie avant l’ouverture reste la hotbar courante. +La touche Tab retrouve la navigation des boutons dans ce menu. + +Le cadre nine-slice tient dans une hauteur fixe de 202 pixels d’interface. +Il ne grandit plus avec la capacité de l’inventaire. Le Maj-clic du résultat +cherche uniquement une place dans la hotbar ; sans place, il ne consomme pas +d’argile. Les touches 1–9 restent utilisables, pas l’échange avec la seconde +main. À la fermeture, la restitution native des objets restants est conservée. + +Les identifiants, sauvegardes, données de sculpture et orientations beta.111 +restent inchangés. Aucun monde existant n’est ouvert ou réécrit. Client et +serveur doivent utiliser ensemble beta.112 pour les nouveaux indices du menu +temporaire (11 cases au total). + +## Vérifications natives + +Le parcours `Statuary106ClientChecks`, étendu à beta.112, passe en **1 min 13 s** +sur Minecraft 26.3 avec serveur intégré et monde plat jetable de graine 106 : + +- Menu FR/EN aux échelles 2 et 3 : hauteur fixe, neuf cases de hotbar native, + deux cases de fabrication et absence de panneau d’inventaire évolutif. +- Fabrication par Maj-clic depuis la hotbar, avec de l’argile aussi présente + dans la réserve, l’extension et la seconde main : ces stocks restent intacts. +- Rejet des indices de cases absents et de l’échange avec la seconde main. +- Hotbar remplie de neuf piles : Maj-clic sans fabrication ni consommation. +- Une seule place dans une pile de 63 statuaires : production d’un seul objet, + coût d’une argile, aucune statue transférée dans une case cachée. +- Restitution de l’argile restante à la fermeture, seize teintes, quatre + orientations, butin/repose et sauvegarde/reconnexion sans fichier GLB. +- Régression du Métabli et annulation d’import conservées. + +```sh +./gradlew :sanctuary:runClientGameTest \ + -PsanctuaryClientTests=true -PsanctuaryStatuary106ClientTests=true \ + -PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true +``` + +Marqueur `STATUARY112_PASS` dans `build/clay-hotbar112-client.log` ; captures +relues dans `build/clay-hotbar112-evidence/`. Le GameTest dédié reste exclu +conformément au refus antérieur de son EULA. Pas de test Windows ni depuis +deux ordinateurs ; aucun monde personnel n’a été ouvert. + +## Livraison vérifiée + +`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` +passe en **3 min 6 s**, 125 tâches. Les sources JAR correspondent aux sources +Java du dépôt. Les classes et ressources hors de ce ticket sont identiques +à beta.111, notamment les données et le rendu orienté des statuaires. +Les deux packs contiennent le même JAR, sans monde, GLB ni classes de tests. + +- Pack normal : 10 169 337 octets, SHA-256 + `55723a5d7cb99e350ad8fe150e92e0567430b5261ca4685abd900be34068658f`. +- Pack Test : 10 188 262 octets, SHA-256 + `987818bf817ad80fd055b97e84bf4775fca54820d7c3055d6fd4089dc7a5a3c0`. +- JAR : SHA-256 + `9007a770bbe69db5846f93140d910514f10839cc34ec7c6315a9381d8850adaa`. + +Reçu local : `build/clay-hotbar112-artifact.json`. Publication et mise à jour +de l’instance stable préparées après ces contrôles. diff --git a/gradle.properties b/gradle.properties index 504af02..7f8753e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,8 +9,8 @@ loom_version=1.17.20 fabric_api_version=0.160.5+26.3 # Release counter: beta.001, .002, .003, ... (see docs/versioning.md). -mod_version=beta.111 -pack_version=beta.111 +mod_version=beta.112 +pack_version=beta.112 resource_pack_version=beta.090 maven_group=fr.koka.sanctuary jei_version=30.32.0-sanctuary.3 diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java index bcd2b02..a9c64ad 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java @@ -23,6 +23,7 @@ import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.ContainerInput; import net.minecraft.world.item.*; import net.minecraft.world.level.GameType; @@ -71,19 +72,27 @@ public final class Statuary106ClientChecks implements FabricClientGameTest { c.runOnClient(m->{m.options.guiScale().set(2);m.getWindow().setWindowed(1280,800);m.resizeGui();}); open(c);c.runOnClient(m->((ClayWorkshopScreen)m.gui.screen()).onFilesDrop(List.of(file))); c.waitFor(m->!((ClayWorkshopScreen)m.gui.screen()).busy()&&((ClayWorkshopScreen)m.gui.screen()).selected()!=null,200);c.waitTicks(10); - server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu;check(menu.getSlot(1).getItem().isEmpty(),"No clay, no result");menu.getSlot(0).set(new ItemStack(Items.CLAY,3));menu.broadcastChanges();}); + server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu;check(menu.getSlot(1).getItem().isEmpty(),"No clay, no result"); + menu.getSlot(ClayWorkshopMenu.HOTBAR_START).set(new ItemStack(Items.CLAY,3)); + p.getInventory().setItem(18,new ItemStack(Items.CLAY,64));p.getInventory().setItem(Inventory.SLOT_OFFHAND,new ItemStack(Items.CLAY,12)); + fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p).setItem(17,new ItemStack(Items.CLAY,64));menu.broadcastChanges();}); + c.waitFor(m->m.player.containerMenu.getSlot(ClayWorkshopMenu.HOTBAR_START).getItem().getCount()==3,100); + c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,ClayWorkshopMenu.HOTBAR_START,0,ContainerInput.QUICK_MOVE,m.player)); c.waitFor(m->m.player.containerMenu.getSlot(1).getItem().is(Statuary.ITEM),100); c.takeScreenshot("statuary106-workshop-en"); checkWorkshopLayout(c); c.runOnClient(m->{m.options.guiScale().set(3);m.resizeGui();});c.waitTicks(3);checkWorkshopLayout(c);c.takeScreenshot("statuary111-workshop-en-scale3"); - // Six recovery rows still fit a small GUI and remain scrollable. - server.runOnServer(s->{var p=player(s);fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p).setItem(17,new ItemStack(Items.STONE));p.containerMenu.broadcastChanges();}); - c.waitFor(m->SanctuaryInventoryClient.visibleRows()==6,100);c.runOnClient(m->button(m.gui.screen(),"sanctuary.statuary.refresh").onPress(enter()));checkWorkshopLayout(c); - c.runOnClient(m->{var screen=(ClayWorkshopScreen)m.gui.screen();var geometry=(SanctuaryContainerScreenGeometry)(Object)screen;var refresh=button(screen,"sanctuary.statuary.refresh"); - check(screen.mouseScrolled(refresh.getX()-10+geometry.sanctuary$panelLeft()+30,refresh.getY()-127+geometry.sanctuary$panelTop()+35,0,1),"Extended workshop inventory can scroll");}); - c.takeScreenshot("statuary111-workshop-en-six-rows"); - server.runOnServer(s->{var p=player(s);fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p).setItem(17,ItemStack.EMPTY);p.containerMenu.broadcastChanges();}); - c.waitFor(m->SanctuaryInventoryClient.visibleRows()==1,100); + // Hidden inventory, overflow and offhand must never join this menu or feed its input. + c.runOnClient(m->check(!((SanctuaryContainerScreenGeometry)(Object)m.gui.screen()).sanctuary$hasIntegratedInventory(),"No expanding player-inventory panel")); + server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu; + check(menu.slots.size()==11&&menu.getSlot(0).getItem().getCount()==3,"Two crafting slots plus exactly nine hotbar slots"); + check(!((fr.koka.sanctuary.inventory.SanctuaryExtendedInventoryMenu)(Object)menu).sanctuary$hasExtendedInventory(),"No hidden overflow destination"); + menu.clicked(0,Inventory.SLOT_OFFHAND,ContainerInput.SWAP,p);menu.clicked(11,0,ContainerInput.PICKUP,p);menu.clicked(-2,0,ContainerInput.PICKUP,p); + check(menu.quickMoveStack(p,11).isEmpty()&&menu.getCarried().isEmpty(),"Out-of-menu slot rejected"); + check(menu.getSlot(0).getItem().getCount()==3&&p.getOffhandItem().getCount()==12,"Offhand swap refused"); + check(p.getInventory().getItem(18).getCount()==64&&fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p).getItem(17).getCount()==64,"Reserve clay untouched"); + p.getInventory().setItem(18,ItemStack.EMPTY);p.getInventory().setItem(Inventory.SLOT_OFFHAND,ItemStack.EMPTY);fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p).setItem(17,ItemStack.EMPTY); + }); c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();}); server.runOnServer(s->{var menu=(ClayWorkshopMenu)player(s).containerMenu;ORIGINAL.set(StatuaryEntity.sculpture(menu.getSlot(1).getItem()));check(ORIGINAL.get().equals(source),"Plain clay keeps exact colors and model ID"); for(var family:ColoredBricks.families()){var colored=ClayWorkshopMenu.material(source,new ItemStack(family.clay()));check(colored.modelId().equals(source.modelId())&&colored.cells().size()==source.cells().size()&&colored.cells().stream().map(VoxelSculpture.Cell::color).distinct().count()==1,"All 16 clays keep geometry and ID, recolor uniformly");}}); @@ -143,8 +152,24 @@ public final class Statuary106ClientChecks implements FabricClientGameTest { c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.generate").onPress(enter()));c.waitFor(m->!PlanClient.busy&&PlanClient.plan!=null&&m.gui.screen()==null,400); c.runOnClient(m->check(PlanClient.plan.height()==32&&PlanClient.plan.cells().size()>source.cells().size(),"Same source produces real-block statue at selected height")); server.runOnServer(s->check(((StatuaryEntity)player(s).level().getBlockEntity(SCULPTURE)).sculpture().equals(source),"Plan generation leaves existing miniature intact")); + // Saturated hotbar must not spill crafted statues into hidden inventory or consume unpaid clay. + c.runOnClient(m->PlanClient.clear());open(c);c.runOnClient(m->((ClayWorkshopScreen)m.gui.screen()).load(file)); + c.waitFor(m->!((ClayWorkshopScreen)m.gui.screen()).busy()&&((ClayWorkshopScreen)m.gui.screen()).selected()!=null,200);c.waitTicks(10); + var savedHotbar=new AtomicReference>(); + server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu;savedHotbar.set(java.util.stream.IntStream.range(0,9).mapToObj(i->p.getInventory().getItem(i).copy()).toList()); + for(int i=0;i<9;i++)p.getInventory().setItem(i,new ItemStack(Items.STONE,64));menu.getSlot(0).set(new ItemStack(Items.CLAY,2));menu.broadcastChanges();}); + c.waitFor(m->m.player.containerMenu.getSlot(1).getItem().is(Statuary.ITEM)&&m.player.containerMenu.getSlot(2).getItem().is(Items.STONE),100); + c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,0,ContainerInput.QUICK_MOVE,m.player));c.waitTicks(6); + server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu;check(menu.getSlot(0).getItem().getCount()==2&&count(p,Statuary.ITEM)==0,"Full hotbar consumes nothing and does not fill reserve"); + var nearlyFull=StatuaryEntity.item(source);nearlyFull.setCount(63);p.getInventory().setItem(8,nearlyFull);menu.broadcastChanges();}); + c.waitFor(m->m.player.containerMenu.getSlot(10).getItem().getCount()==63,100); + c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,0,ContainerInput.QUICK_MOVE,m.player));c.waitTicks(6); + server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu;check(p.getInventory().getItem(8).getCount()==64&&count(p,Statuary.ITEM)==64&&menu.getSlot(0).getItem().getCount()==1,"Partial merge crafts only the one statue that fits"); + p.getInventory().setItem(0,ItemStack.EMPTY);menu.broadcastChanges();}); + close(c);server.waitFor(s->player(s).containerMenu==player(s).inventoryMenu,100); + server.runOnServer(s->{var p=player(s);check(p.getInventory().getItem(0).is(Items.CLAY)&&p.getInventory().getItem(0).getCount()==1,"Closing returns the unused clay");for(int i=0;i<9;i++)p.getInventory().setItem(i,savedHotbar.get().get(i));p.inventoryMenu.broadcastFullState();}); // Cancel an import by closing the native menu; late worker completion cannot craft in the next session. - c.runOnClient(m->PlanClient.clear());open(c);c.runOnClient(m->{((ClayWorkshopScreen)m.gui.screen()).load(file);m.gui.screen().onClose();});c.waitTicks(12); + open(c);c.runOnClient(m->{((ClayWorkshopScreen)m.gui.screen()).load(file);m.gui.screen().onClose();});c.waitTicks(12); server.runOnServer(s->check(player(s).containerMenu==player(s).inventoryMenu,"Closed workshop releases crafting session")); check(Arrays.equals(Files.readAllBytes(file),fixture),"Original GLB never rewritten"); } @@ -162,11 +187,14 @@ public final class Statuary106ClientChecks implements FabricClientGameTest { } System.out.println("STATUARY106_PASS GLB textured scene, bounded import, shared folder filters, native paid crafting, 16 clay colors, model ID, shift-click, place/drop/re-place, Metabli plan, cancellation, server save/reconnect without source"); System.out.println("STATUARY111_PASS nine-slice GUI FR/EN scales 2/3, four native gaze placements, renderer directions, structure rotation/mirror, legacy state default, drop/re-place and persisted orientation"); + System.out.println("STATUARY112_PASS fixed compact hotbar-only menu, native input transfer, hidden slots/offhand rejected, full hotbar no consumption, partial merge exact cost, closing restitution"); }catch(IOException e){throw new AssertionError(e);}finally{try{Files.deleteIfExists(file);}catch(IOException ignored){}} } private static ServerPlayer player(net.minecraft.server.MinecraftServer s){return s.getPlayerList().getPlayers().getFirst();} private static void checkWorkshopLayout(ClientGameTestContext c){c.runOnClient(m->{var screen=(ClayWorkshopScreen)m.gui.screen();var refresh=button(screen,"sanctuary.statuary.refresh");int left=refresh.getX()-10,top=refresh.getY()-127; check(top>=0&&top+screen.panelHeight()<=screen.height,"Workshop frame fits GUI height"); + check(screen.panelHeight()==202&&screen.getMenu().slots.size()==11,"Compact height and nine player slots regardless of reserve capacity"); + for(int i=0;i<9;i++){var slot=screen.getMenu().getSlot(i+2);check(slot.container==m.player.getInventory()&&slot.getContainerSlot()==i,"Displayed player slot belongs to active native hotbar");} for(var slot:screen.getMenu().slots)if(slot.x>=0&&slot.y>=0)check(slot.x>3&&slot.x+17<310&&slot.y>3&&slot.y+17=left&&b.getRight()<=left+310&&b.getY()>=top&&b.getBottom()<=top+145,"Every action stays clear of explanations and inventory"); });} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java index bce1277..c2d9ad1 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java @@ -19,11 +19,12 @@ import net.minecraft.world.item.ItemStack; public final class ClayWorkshopScreen extends AbstractContainerScreen { private static final Identifier FRAME=fr.koka.sanctuary.SanctuaryMod.id("container/clay_workshop/frame"); private static final Identifier SLOT=Identifier.withDefaultNamespace("container/slot"); + private static final Identifier SELECTION=fr.koka.sanctuary.SanctuaryMod.id("hud/hotbar_selection"); private static final ExecutorService WORKER=Executors.newSingleThreadExecutor(r->{var t=new Thread(r,"Sanctuary clay models");t.setDaemon(true);return t;}); private List files=List.of();private int page,generation,ticks;private boolean busy,pending; private VoxelSculpture source,previewSource;private ItemStack preview=ItemStack.EMPTY,lastClay=ItemStack.EMPTY; private Component status=text("choose_model");private Button rotate; - public ClayWorkshopScreen(ClayWorkshopMenu menu,Inventory inv,Component title){super(menu,inv,title,310,286);inventoryLabelX=75;inventoryLabelY=186;} + public ClayWorkshopScreen(ClayWorkshopMenu menu,Inventory inv,Component title){super(menu,inv,title,310,202);} public static Component text(String key,Object...args){return Component.translatable("sanctuary.statuary."+key,args);} public static void register(){ net.minecraft.client.gui.screens.MenuScreens.register(Statuary.MENU,ClayWorkshopScreen::new);StatuaryRenderer.register(); @@ -65,15 +66,14 @@ public final class ClayWorkshopScreen extends AbstractContainerScreen files){if(!files.isEmpty())load(files.getFirst());} @Override public void removed(){generation++;super.removed();} - int panelHeight(){ - // The inventory mixin moves or hides rows according to the player's capacity. - return Math.max(177,menu.slots.stream().filter(s->s.x>=0&&s.y>=0).mapToInt(s->s.y+30).max().orElse(177)); - } + int panelHeight(){return imageHeight;} @Override public void extractBackground(GuiGraphicsExtractor g,int mx,int my,float partial){ g.blitSprite(RenderPipelines.GUI_TEXTURED,FRAME,leftPos,topPos,imageWidth,panelHeight()); g.fill(leftPos+148,topPos+23,leftPos+300,topPos+92,0xFF8B8B8B); g.fill(leftPos+149,topPos+24,leftPos+299,topPos+91,0xFF373737); for(var slot:menu.slots)if(slot.x>=0&&slot.y>=0)g.blitSprite(RenderPipelines.GUI_TEXTURED,SLOT,leftPos+slot.x-1,topPos+slot.y-1,18,18); + if(minecraft.player!=null)g.blitSprite(RenderPipelines.GUI_TEXTURED,SELECTION, + leftPos+ClayWorkshopMenu.HOTBAR_X-3+minecraft.player.getInventory().getSelectedSlot()*18,topPos+ClayWorkshopMenu.HOTBAR_Y-3,22,21); if(!preview.isEmpty()){ g.pose().pushMatrix();g.pose().translate(leftPos+194,topPos+27);g.pose().scale(3.75f,3.75f);g.item(preview,0,0);g.pose().popMatrix(); } @@ -85,7 +85,6 @@ public final class ClayWorkshopScreen extends AbstractContainerScreen=0)g.text(font,playerInventoryTitle,inventoryLabelX,inventoryLabelY,0xFF303030,false); } public boolean busy(){return busy;} public VoxelSculpture selected(){return source;} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/HotbarClient.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/HotbarClient.java index dec7a14..7a38d80 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/HotbarClient.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/HotbarClient.java @@ -90,6 +90,8 @@ public final class HotbarClient { } return true; }); + // This workshop exposes only the current hotbar; Tab remains native focus navigation. + if (screen instanceof ClayWorkshopScreen) return; ScreenKeyboardEvents.afterKeyRelease(screen).register((s,event) -> {if (key.matches(event)) held=false;}); ScreenKeyboardEvents.allowKeyPress(screen).register((s,event) -> { if (!ProgressionClient.active() || !key.matches(event) || event.isEscape() || editing(s.getFocused())) return true; diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/AbstractContainerScreenMixin.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/AbstractContainerScreenMixin.java index fc57edc..b1f9ed7 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/AbstractContainerScreenMixin.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/AbstractContainerScreenMixin.java @@ -190,8 +190,7 @@ abstract class AbstractContainerScreenMixin implements SanctuaryContainerScreenG if (!sanctuary$integratedInventory) return; Minecraft minecraft = Minecraft.getInstance(); int panelHeight = PANEL_BASE_HEIGHT + sanctuary$visibleRows * BANK_ROW_STEP; - // The workshop draws one nine-slice frame around its controls and all visible rows. - if (!((Object)this instanceof fr.koka.sanctuary.client.ClayWorkshopScreen)) graphics.blit(RenderPipelines.GUI_TEXTURED, + graphics.blit(RenderPipelines.GUI_TEXTURED, SanctuaryMod.id("textures/gui/container/" + sanctuary$textureProfile + "/inventory_" + sanctuary$visibleRows + ".png"), sanctuary$panelLeft, sanctuary$panelTop, @@ -332,7 +331,6 @@ abstract class AbstractContainerScreenMixin implements SanctuaryContainerScreenG // These screens paint previews or controls below their lowest machine // slot. Their explicit anchors keep the common panel from covering them. if (screen instanceof StonecutterScreen) return 70; - if (screen instanceof fr.koka.sanctuary.client.ClayWorkshopScreen) return 176; if (screen instanceof CartographyTableScreen) return 81; if (screen instanceof EnchantmentScreen) return 73; if (screen instanceof SmithingScreen) return 82; diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/ClayWorkshopMenu.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/ClayWorkshopMenu.java index efeac7a..adb98d3 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/ClayWorkshopMenu.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/ClayWorkshopMenu.java @@ -18,6 +18,7 @@ import net.minecraft.world.phys.*; /** Native input/result slots. Server recomputes the color from the actual clay and charges each take. */ public final class ClayWorkshopMenu extends AbstractContainerMenu { + public static final int HOTBAR_START=2,HOTBAR_END=HOTBAR_START+Inventory.SELECTION_SIZE,HOTBAR_X=75,HOTBAR_Y=176; public record View(BlockPos pos,UUID token){ public static final StreamCodec CODEC=StreamCodec.of((b,p)->{b.writeBlockPos(p.pos);b.writeUUID(p.token);},b->new View(b.readBlockPos(),b.readUUID())); } @@ -51,7 +52,8 @@ public final class ClayWorkshopMenu extends AbstractContainerMenu { @Override public boolean mayPickup(Player p){return mayCraft(p)&&clay(input.getItem(0))&&!getItem().isEmpty();} @Override public void onTake(Player p,ItemStack stack){input.removeItem(0,1);input.setChanged();super.onTake(p,stack);} }); - addStandardInventorySlots(inv,75,198); + // Use the nine carried slots without registering a full inventory/overflow range. + addInventoryHotbarSlots(inv,HOTBAR_X,HOTBAR_Y); } // Colors match the published clay palette (tools/colored-bricks-palette.json), in native dye order. private static final int[] CLAY_COLORS={0xC6C4C8,0xD3AA8D,0xD39FD0,0x91B0CA,0xC8BF98,0xA2C69A,0xDAC4DD,0x74767D,0xA9A8AC,0xA8C5C1,0xBE91E3,0xA0A7BC,0xA49A91,0xA0B499,0xB49A99,0x444541}; @@ -81,13 +83,17 @@ public final class ClayWorkshopMenu extends AbstractContainerMenu { var hit=new BlockHitResult(Vec3.atCenterOf(view.pos),Direction.UP,view.pos,false); return UseBlockCallback.EVENT.invoker().interact(p,level,InteractionHand.MAIN_HAND,hit)==InteractionResult.PASS&&stillValid(p); } - @Override public void clicked(int slot,int button,ContainerInput input,Player p){if(stillValid(p))super.clicked(slot,button,input,p);} + @Override public void clicked(int slot,int button,ContainerInput input,Player p){ + if(!stillValid(p)||slot>=slots.size()||(slot< -1&&slot!=-999))return; + if(input==ContainerInput.SWAP&&(button<0||button>=Inventory.SELECTION_SIZE))return; + super.clicked(slot,button,input,p); + } @Override public boolean canTakeItemForPickAll(ItemStack stack,Slot slot){return slot.container!=result&&super.canTakeItemForPickAll(stack,slot);} @Override public void removed(Player p){super.removed(p);if(level!=null)clearContainer(p,input);result.clearContent();selected=null;} @Override public ItemStack quickMoveStack(Player p,int index){ if(index<0||index>=slots.size()||!stillValid(p))return ItemStack.EMPTY;var slot=slots.get(index);if(!slot.mayPickup(p)||!slot.hasItem())return ItemStack.EMPTY; var stack=slot.getItem();var copy=stack.copy(); - if(index<2){if(!moveItemStackTo(stack,2,38,true))return ItemStack.EMPTY;} + if(index