(Feat) Add Storage Chests + Item Details Panel

Coffres : AStorageContainer, un UInventoryComponent configure en conteneur,
affiche comme panneau de WBP_InventoryScreen plutot que dans un ecran a lui.
Deplacement unifie par UInventoryComponent::TransferSlot / TransferAllTo.

Panneau de detail : UItemDetailsWidget, pose DANS WBP_Inventory a droite de la
grille. Icone, nom, separation, description, barre d'usure. Il recoit un couple
(inventaire, index) et s'abonne a OnInventoryChanged, donc une charge consommee
sous le curseur se voit.

Une case ne connait plus sa grille : elle diffuse OnHoverChanged. La barre
rapide s'y abonne aussi et relaie par le PlayerController, seul a posseder a la
fois le HUD et l'ecran. Le panneau efface la case qu'on QUITTE et non lui-meme,
sinon passer de la barre a la grille viderait ce qui vient d'etre affiche.

L'ecran allume et eteint le panneau (SetItemDetailsEnabled) : WBP_Inventory
etant instancie deux fois en mode coffre, une case a cocher par instance serait
un bug qui attend qu'on oublie de la decocher.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 22:51:24 +02:00
parent d50ef564ba
commit 41d6e6a7f3
50 changed files with 2947 additions and 167 deletions
+83 -3
View File
@@ -115,10 +115,12 @@ qu'une compilation propre l'est vraiment.
| HUD, input d'interface, fondus, respawn | `FpsPlayerController.h/.cpp` |
| Interaction (trace + interface) | `Interactable.h`, `InteractionComponent`, `InteractionPromptWidget` |
| Objets | `ItemDataAsset`, `PickupItem` |
| Inventaire | `InventoryComponent`, `InventoryWidget`, `InventorySlotWidget`, `HotbarWidget`, `InventoryDragDropOperation` |
| Inventaire | `InventoryComponent`, `InventoryWidget`, `InventorySlotWidget`, `HotbarWidget`, `InventoryDragDropOperation`, `ItemDetailsWidget` |
| Survie | `SurvivalStatsComponent`, `SurvivalStatsWidget` |
| Menu principal | `MainMenuGameMode`, `MainMenuPlayerController`, `MainMenuWidget`, `MenuCameraSpot` |
| Fabrication | `CraftingRecipeDataAsset`, `CraftingRecipeBook`, `CraftingComponent`, `CraftingWidget`, `CraftingRecipeSlotWidget`, `CraftingIngredientRowWidget` |
| Cuisson | `CookingStation` + bloc *Cooking* / *Fuel* de `ItemDataAsset`**doc complète : [Docs/CookingSystem.md](../Docs/CookingSystem.md)** |
| Coffres | `StorageContainer`, `StorageScreenWidget` + `UInventoryComponent::TransferSlot` / `TransferAllTo` |
| Écran à onglets (inventaire / craft) | `InventoryScreenWidget` |
| Menu de pause | `PauseMenuWidget` + la partie pause de `FpsPlayerController` |
| Réglages | `SurvivalUserSettings`, `SettingsMenuWidget`, `SettingsTabWidget`, `SettingsGameplayWidget`, `SettingRowWidget` |
@@ -171,10 +173,88 @@ qu'une compilation propre l'est vraiment.
engendrer un acteur, et un établi déposerait son surplus sur sa propre table.
- Le résultat passe par `AddItem()`, donc **exactement comme un ramassage** : piles entamées
d'abord, puis barre rapide, puis grille.
- **Cuisson : aucune interface, tout dans le monde.** On dépose l'objet sur le feu, on regarde
le mesh changer. Le prompt n'affiche **que des actions réalisables** — jamais l'état du
poste, qui se lit sur les flammes, le nombre de bûches et le mesh de la nourriture. Cuire
n'est **pas une recette** mais une chaîne de DataAssets (cru → cuit → brûlé), donc le
changement de visuel est gratuit. Le carburant est une **pile d'objets**, pas une jauge de
secondes : une bûche visible = un exemplaire donné, et la capacité EST le nombre de bûches
posées dans le Blueprint. Le feu s'allume et s'arrête seul — il brûle si et seulement s'il a
du bois **et** du travail, la carbonisation comptant comme du travail pour que la
sur-cuisson reste punitive. Détail et pièges : [Docs/CookingSystem.md](../Docs/CookingSystem.md).
- **Un coffre EST un `UInventoryComponent`**, configuré par `ConfigureAsContainer()` : zéro
barre rapide, donc `GetBackpackStartIndex()` vaut 0 et la grille affiche tout. Aucun nouveau
modèle de données — d'où la durabilité qui traverse le coffre sans une ligne de code.
Le déplacement est désormais **unique** : `UInventoryComponent::TransferSlot(From, i, To, j, N)`
est statique, gère indifféremment un ou deux inventaires, et diffuse `OnInventoryChanged`
**des deux côtés** ; `MoveItem`/`MoveItemQuantity` ne sont plus que des appels avec
`From == To`. Oublier la seconde diffusion laisse la grille d'en face afficher un état périmé,
ce qui se lit comme un objet qui disparaît.
- **Le coffre n'a pas d'écran à lui : c'est un panneau de plus sur `WBP_InventoryScreen`**,
frère du WidgetSwitcher, `Collapsed` par défaut. La raison est du ressenti pur — le sac du
joueur ne bouge pas d'un pixel entre « j'ouvre mon sac » et « j'ouvre un coffre », alors
qu'un écran distinct le ferait sauter à chaque fois. Ce n'est **pas** un troisième onglet non
plus : les deux grilles doivent être visibles **ensemble**, ce qu'un WidgetSwitcher interdit.
Techniquement c'est une **deuxième instance du même `WBP_Inventory`** — elle ne peut pas
vivre *dans* `WBP_Inventory`, un Widget Blueprint qui se contient lui-même est une référence
circulaire qu'UMG refuse de compiler. D'où le flag `bAutoBindToPawn` : sans lui, la grille du
coffre s'accroche au pawn dans son `NativeConstruct` et affiche le sac des deux côtés.
Corollaire : l'onglet Fabrication est **grisé** tant qu'un coffre est ouvert (le panneau
resterait affiché à côté de la grille de recettes), et la touche de craft rend le coffre.
- **Clic droit = envoyer en face**, via `AddItem()` donc exactement comme un ramassage. La
destination est **poussée** par l'écran (`SetQuickTransferTarget`), jamais devinée : hors de
l'écran de coffre il n'existe aucun « autre côté », et deviner ferait disparaître des objets.
« Tout ranger » part de `GetBackpackStartIndex()` : la barre rapide porte les outils, les
ranger d'office serait une punition déguisée en confort.
- **Le coffre ne décide de rien.** Il appelle `OpenStorage(this)` ; c'est le contrôleur qui
referme le couvercle — par Échap, par la mort, ou parce que le joueur s'est éloigné
(`MaxStorageDistance`, surveillé par un timer à 0,25 s, jamais un Tick). Un coffre qui
déciderait seul finirait ouvert dans la moitié de ces cas.
- **`LidPivot`**, un `SceneComponent` vide entre la caisse et le couvercle : le pivot de
`SM_Chest_Top` est celui du pack, presque jamais la charnière. Sans lui le couvercle tourne
autour de son centre et traverse la caisse. Le coffre ne tick **que** pendant l'animation.
- **Toute la collision du coffre tient dans une `UBoxComponent`, les meshes n'en portent
aucune.** C'est la seule exception à la règle « le mesh EST la surface d'interaction » posée
par `ACraftingStation`, et elle vient de ce qu'un coffre est en **deux morceaux dont un
pivote** : compter sur leurs collisions imposerait que le pack en fournisse deux correctes,
et laisserait surtout un trou dans la surface visable dès que le couvercle se lève. La boîte
est fille de la **caisse**, jamais du pivot — le coffre se vise donc au même endroit ouvert
et fermé. Elle s'ajuste seule au volume des deux meshes dans `OnConstruction`
(`bAutoFitInteractionBox`), donc changer de variante de coffre ne demande aucun retaillage.
- **La barre rapide se masque sur l'onglet Fabrication.** `UInventoryScreenWidget` se contente
de diffuser `OnTabChanged` ; c'est le contrôleur qui décide, via une unique
`UpdateHotbarVisibility()`. Un seul point de décision, sinon fermer l'écran depuis l'onglet
craft laisse la barre cachée pour le reste de la partie.
- **Le détail de l'objet survolé vit *dans* `WBP_Inventory`**, à droite de la grille, et non sur
`WBP_InventoryScreen` : c'est un panneau de la grille, il doit la suivre — l'onglet
Fabrication l'emporte donc avec la page, sans une ligne de code. Contenu volontairement
réduit : icône, nom, trait de séparation, description, barre d'usure. Pas de fond, la largeur
est tenue par un Size Box — sans lui, un panneau vide ferait sauter la grille latéralement.
`UItemDetailsWidget` reçoit un couple **(inventaire, index)**, jamais une copie du
`FInventorySlot` : une charge consommée sous le curseur doit se voir, d'où son abonnement à
`OnInventoryChanged`. La grille masque `ContentRoot` en bloc quand rien n'est survolé, sinon
le trait de séparation resterait seul à l'écran.
- **Aucune case à cocher pour ce panneau : c'est l'écran qui l'allume et l'éteint**
(`SetItemDetailsEnabled`). `WBP_Inventory` étant instancié **deux fois** sur l'écran de coffre,
un réglage par instance serait un bug qui attend qu'on oublie de le décocher. La grille de
coffre n'en a donc jamais, et le sac perd le sien tant qu'un coffre est ouvert — trois
colonnes seraient illisibles. `HideStorage()` étant appelé dès `NativeConstruct`, l'état par
défaut est reposé à chaque construction plutôt que hérité de la Designer.
- **Une case ne connaît plus sa grille, elle diffuse `OnHoverChanged`.** La barre rapide pose
les mêmes `WBP_InventorySlot` sans être un `UInventoryWidget` : lui passer `nullptr` comme
parent était acceptable tant que personne n'écoutait le survol. La barre relaie vers le
**contrôleur**, seul à posséder à la fois le HUD et l'écran — un widget de HUD n'a pas à
connaître l'écran d'inventaire. Le panneau efface donc **la case qu'on quitte**
(`ClearSlot(inventaire, index)`), jamais « le panneau » : passer d'une case de la barre à une
case de la grille émet *entre dans B* puis *sort de A*, et un effacement inconditionnel
viderait ce qui vient d'être affiché.
- **`NativeOnMouseLeave` n'arrive jamais quand un widget s'efface sous le curseur.** C'est
exactement ce qui se passe en fermant un coffre, d'où le passage obligé par
`SetHoveredSlot()` dans `ClearSlotWidgets()` et `BindToInventory()` — cette dernière annonce
la sortie **avant** de changer d'inventaire, sinon on ne sait plus quel affichage périmer.
Le détail du système de craft — API, câblage éditeur, dépannage — est dans
[`Docs/Crafting.md`](../Docs/Crafting.md).
### Pièges UMG rencontrés sur l'écran de craft
@@ -215,8 +295,8 @@ qu'une compilation propre l'est vraiment.
## État actuel et suite
Boucle complète : récolter → **fabriquer** → ranger → consommer → survivre → mourir →
réapparaître. Autour : menu principal, menu de pause, et un écran de réglages dont **seul
Boucle complète : récolter → **fabriquer** **cuire** ranger → consommer → survivre →
mourir → réapparaître. Autour : menu principal, menu de pause, et un écran de réglages dont **seul
l'onglet « Jeu & Contrôles » est rempli**.
Le craft est en place de bout en bout : `DA_RecipeBook`, onglets Inventaire / Fabrication,