Use only the active hotbar in the clay workshop for beta.112
Build Sanctuary / build (push) Canceled after 0s

This commit is contained in:
koka
2026-09-17 03:59:13 +02:00
parent 8c0020c774
commit 1eaa976ecf
12 changed files with 145 additions and 33 deletions
+13 -6
View File
@@ -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 latelier dargile 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 linstance **Sanctuary Beta**.
La version **beta.112** limite latelier dargile à 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à
latelier, les œufs et la neige saisonnière dans linstance **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 dargile 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 dargile.
[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 dargile 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).
+6
View File
@@ -1,5 +1,11 @@
# Backlog Sanctuary
## STAT-03 — Atelier dargile 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 dargile et orientation — beta.111
Branche `codex/clay-workshop-ui-beta111`. [Contrat et vérifications](clay-workshop-ui-beta111.md).
+66
View File
@@ -0,0 +1,66 @@
# STAT-03 — Atelier dargile 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 louverture 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 dinterface.
Il ne grandit plus avec la capacité de linventaire. Le Maj-clic du résultat
cherche uniquement une place dans la hotbar ; sans place, il ne consomme pas
dargile. Les touches 19 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 nest 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 dinventaire évolutif.
- Fabrication par Maj-clic depuis la hotbar, avec de largile aussi présente
dans la réserve, lextension 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 dun seul objet,
coût dune argile, aucune statue transférée dans une case cachée.
- Restitution de largile restante à la fermeture, seize teintes, quatre
orientations, butin/repose et sauvegarde/reconnexion sans fichier GLB.
- Régression du Métabli et annulation dimport 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 na é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 linstance stable préparées après ces contrôles.
+2 -2
View File
@@ -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
@@ -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<List<ItemStack>>();
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<screen.panelHeight(),"Every visible slot fits inside nine-slice frame");
for(var widget:screen.children())if(widget instanceof Button b)check(b.getX()>=left&&b.getRight()<=left+310&&b.getY()>=top&&b.getBottom()<=top+145,"Every action stays clear of explanations and inventory");
});}
@@ -19,11 +19,12 @@ import net.minecraft.world.item.ItemStack;
public final class ClayWorkshopScreen extends AbstractContainerScreen<ClayWorkshopMenu> {
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<Path> 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<ClayWorksh
}
@Override public void onFilesDrop(List<Path> 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<ClayWorksh
@Override protected void extractLabels(GuiGraphicsExtractor g,int mx,int my){
g.text(font,title,10,8,0xFF303030,false);g.text(font,text("models"),10,22,0xFF404040,false);
g.text(font,text("input"),230-font.width(text("input"))/2,95,0xFF303030,false);g.text(font,text("output"),imageWidth-10-font.width(text("output")),95,0xFF303030,false);
if(inventoryLabelY>=0)g.text(font,playerInventoryTitle,inventoryLabelX,inventoryLabelY,0xFF303030,false);
}
public boolean busy(){return busy;}
public VoxelSculpture selected(){return source;}
@@ -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;
@@ -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;
@@ -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<RegistryFriendlyByteBuf,View> 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<HOTBAR_START){if(!moveItemStackTo(stack,HOTBAR_START,HOTBAR_END,true))return ItemStack.EMPTY;}
else if(clay(stack)){if(!moveItemStackTo(stack,0,1,false))return ItemStack.EMPTY;}
else return ItemStack.EMPTY;
if(stack.isEmpty())slot.set(ItemStack.EMPTY);else slot.setChanged();
@@ -2157,7 +2157,7 @@
"sanctuary.plans.imported_model": "3D model → block statue",
"sanctuary.plans.model_files_help": "GLB models in schematics/sanctuary/, alongside your schematics. Create a full-block statue here. Craft 16³ miniatures at the clay workshop.",
"sanctuary.plans.empty_models": "Place a .glb file in the shared folder, then refresh.",
"sanctuary.statuary.choose_model": "Choose a GLB model. 1 clay block = 1 statue. Plain clay keeps model colors; colored clay gives a solid color.",
"sanctuary.statuary.choose_model": "Choose a GLB model, then add clay from your hotbar.",
"sanctuary.statuary.folder": "Shared folder",
"sanctuary.statuary.refresh": "Refresh",
"sanctuary.statuary.models": "GLB models",
@@ -2157,7 +2157,7 @@
"sanctuary.plans.imported_model": "Modèle 3D → statue en blocs",
"sanctuary.plans.model_files_help": "Modèles GLB dans schematics/sanctuary/, avec vos schémas. Ici, créez une grande statue en blocs. Les miniatures 16³ se fabriquent à latelier dargile.",
"sanctuary.plans.empty_models": "Déposez un fichier .glb dans le dossier commun, puis actualisez.",
"sanctuary.statuary.choose_model": "Choisissez un modèle GLB. 1 bloc dargile = 1 statue. Argile normale : couleurs du modèle ; argile colorée : teinte unie.",
"sanctuary.statuary.choose_model": "Choisissez un modèle GLB, puis déposez de largile depuis votre barre rapide.",
"sanctuary.statuary.folder": "Dossier commun",
"sanctuary.statuary.refresh": "Actualiser",
"sanctuary.statuary.models": "Modèles GLB",
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "beta.111"
version = "beta.112"
pack-format = "packwiz:1.1.0"
[index]