244 lines
14 KiB
Markdown
244 lines
14 KiB
Markdown
# SB1-SPEC — Registre de mods et barre latérale (modes enveloppés)
|
||
|
||
- **Fiche** : SB1-T1 — **Agent** : A-ARCH — **Modèle** : prévu opus (M4) | utilisé opus
|
||
- **Dépend de** : SB0 (`D:\Game\LifePlay-Sandbox\Docs\SB0-conception.md`, commit `3b21fd1`, tag `lot-SB0-valide`)
|
||
- **Dépôt** : addon `D:\Game\LifePlay-Sandbox`, branche `lot/SB1-registre` créée depuis `lot-SB0-valide`, tag `lot-SB1-avant`
|
||
- **Principe SB1** : aucun code de mode n'est déplacé. Les trois modes actuels deviennent trois mods **logiques**
|
||
(descripteur + enveloppe qui appelle `SelectMode` existant). La liste déroulante des modes est remplacée par la
|
||
barre latérale. Comportement de chaque mode strictement inchangé.
|
||
|
||
## 1. Fichiers
|
||
|
||
| Fichier (sous `Plugins/LPSandbox/Source/LPSandbox/`) | Fiche | Contenu |
|
||
|---|---|---|
|
||
| `LPSandboxMod.h` (nouveau) | T2 | descripteur, états, contexte, interface `ILPSandboxMod` |
|
||
| `LPSandboxModRegistry.h/.cpp` (nouveaux) | T2 | registre (§ 3), persistance (§ 4), journal |
|
||
| `LPSandboxModWrappers.cpp` (nouveau) | T2 | 3 enveloppes + enregistrement (§ 5) |
|
||
| `LPSandbox.cpp` | T2 | `StartupModule` : enregistrement des fabriques, `Scan()` |
|
||
| `LPPairSandbox.h/.cpp` | T2 | `OpenModeLabel(const FString&)` public ; choix du mod initial (§ 6) ; emplacement de la barre (§ 7) |
|
||
| `LPSandbox.Build.cs` | T2 | `"Projects"` en dépendance privée (`IPluginManager`) |
|
||
| `LPSandboxSidebar.h/.cpp` (nouveaux) | T3 | widget Slate `SLPSandboxSidebar` (§ 7) |
|
||
| `Plugins/LPSandbox/Modules/**/<id>.lpmod` (nouveaux) | T3 | 7 descripteurs (§ 8) |
|
||
|
||
## 2. Types (`LPSandboxMod.h`)
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include "CoreMinimal.h"
|
||
|
||
class SWidget;
|
||
class ALPPairSandboxCharacter;
|
||
class ALPPairSandboxStage;
|
||
|
||
enum class ELPSandboxModKind : uint8 { System, Library, Mod, Data }; // Data = .lpmod du jeu, lecture seule
|
||
enum class ELPSandboxModSource : uint8 { Sandbox, Game };
|
||
enum class ELPSandboxModState : uint8
|
||
{
|
||
Ready, // utilisable (mod activé, prérequis satisfaits)
|
||
Disabled, // case décochée
|
||
Missing, // prérequis absent ou désactivé (Reason renseignée)
|
||
Refused, // règle violée : doublon, mod requis par un mod, cycle (Reason renseignée)
|
||
NoFactory, // descripteur de la sandbox sans code enregistré
|
||
DataOnly // descripteur du jeu
|
||
};
|
||
|
||
struct FLPSandboxModDescriptor
|
||
{
|
||
FString Id; // MODULE_UNIQUEID
|
||
FString Name; // MODULE_NAME
|
||
FString Author; // MODULE_AUTHOR
|
||
FString Description, Version, License, Link, AuthorLink;
|
||
TArray<FString> Requirements; // MODULE_REQUIREMENTS, séparés par ',' ; espaces retirés ; vides ignorés
|
||
ELPSandboxModKind Kind = ELPSandboxModKind::Mod;
|
||
ELPSandboxModSource Source = ELPSandboxModSource::Sandbox;
|
||
bool bSystem = false, bHidden = false, bLocked = false, bDefaultEnabled = false;
|
||
int32 Index = -1; // index hexadécimal du dossier (0001_…), -1 sans index
|
||
FString Folder; // chemin complet du dossier
|
||
FString File; // chemin complet du .lpmod
|
||
TArray<FString> Warnings; // lecture : clé manquante, UNIQUEID ≠ nom du fichier, 3 premières lignes dans le désordre…
|
||
TMap<FString, FString> Keys; // toutes les clés lues (majuscules)
|
||
};
|
||
|
||
struct FLPSandboxContext
|
||
{
|
||
ALPPairSandboxCharacter* Player = nullptr; // SB1 : les enveloppes passent par le personnage joueur
|
||
ALPPairSandboxStage* Stage = nullptr;
|
||
};
|
||
|
||
class ILPSandboxMod
|
||
{
|
||
public:
|
||
virtual ~ILPSandboxMod() = default;
|
||
virtual FString GetId() const = 0;
|
||
virtual void Activate(const FLPSandboxContext& Context) = 0; // le mod prend la scène
|
||
virtual void Deactivate(const FLPSandboxContext& Context) = 0; // il la rend (SB1 : rien, SelectMode gère la transition)
|
||
virtual TSharedPtr<SWidget> BuildPanel() { return nullptr; } // SB1 : nul (les rangées actuelles du menu restent)
|
||
virtual void Tick(float DeltaSeconds) {}
|
||
};
|
||
```
|
||
|
||
## 3. Registre (`FLPSandboxModRegistry`, singleton `Get()`)
|
||
|
||
### 3.1 API (figée : la barre latérale de T3 n'utilise que celle-ci)
|
||
|
||
```cpp
|
||
struct FLPSandboxModEntry
|
||
{
|
||
FLPSandboxModDescriptor Desc;
|
||
ELPSandboxModState State = ELPSandboxModState::Disabled;
|
||
FString Reason; // texte affichable (infobulle ⚠)
|
||
bool bEnabled = false;
|
||
bool bOpen = false;
|
||
};
|
||
|
||
class LPSANDBOX_API FLPSandboxModRegistry
|
||
{
|
||
public:
|
||
static FLPSandboxModRegistry& Get();
|
||
void RegisterFactory(const FString& Id, TFunction<TSharedRef<ILPSandboxMod>()> Factory);
|
||
void Scan(); // découverte + lecture + contrôles + état persisté (§ 3.2-3.4)
|
||
const TArray<FLPSandboxModEntry>& GetEntries() const; // ordre d'affichage : système/bibliothèques, mods, jeu
|
||
const FLPSandboxModEntry* Find(const FString& Id) const;
|
||
bool SetEnabled(const FString& Id, bool bEnabled); // refus si verrouillé ou Data ; recalcul des états ; Save()
|
||
bool Move(const FString& Id, int32 Delta); // mods seulement (-1 monte, +1 descend) ; Save()
|
||
bool Open(const FString& Id, const FLPSandboxContext& Context); // Ready + activé exigé ; Deactivate du mod ouvert, Activate du nouveau ; Save()
|
||
FString GetOpenId() const;
|
||
FString IdForModeLabel(const FString& Label) const; // "Pair"→lps_couple, "Interaction"→lps_interaction, "Mesure"→lps_asset_integration
|
||
FSimpleMulticastDelegate OnChanged; // diffusé après tout changement
|
||
};
|
||
```
|
||
|
||
### 3.2 Découverte
|
||
|
||
- Sandbox : `IPluginManager::Get().FindPlugin(TEXT("LPSandbox"))->GetBaseDir()` + `/Modules/System/*/` et
|
||
`/Modules/Mods/*/` ; dans chaque dossier, le premier `*.lpmod`.
|
||
- Jeu (lecture seule) : `FPaths::ProjectContentDir() / TEXT("Modules")` ; `<dossier>/<dossier>.lpmod` (même règle
|
||
que `ULPModInfoSubsystem` du jeu). Source = Game, Kind = Data, état DataOnly. Absent (projet `LPSandbox.uproject`
|
||
seul) : section Jeu vide, sans erreur.
|
||
- Index : si le nom du dossier commence par 4 chiffres hexadécimaux suivis de `_`, `Index` = valeur ; sinon -1.
|
||
|
||
### 3.3 Lecture d'un `.lpmod`
|
||
|
||
- Ligne `CLÉ: valeur` : clé = texte avant le premier `:`, sans espaces, mise en majuscules ; valeur coupée aux
|
||
extrémités ; lignes vides et commençant par `//` ignorées ; première occurrence retenue.
|
||
- Clés lues : `MODULE_UNIQUEID`, `MODULE_NAME`, `MODULE_AUTHOR`, `MODULE_DESCRIPTION`, `MODULE_VERSION`,
|
||
`MODULE_LICENSE`, `MODULE_LINK`, `MODULE_AUTHOR_LINK`, `MODULE_REQUIREMENTS`, `MODULE_SYSTEM`, `MODULE_HIDDEN`,
|
||
`MODULE_LOCKED`, `MODULE_KIND`, `MODULE_DEFAULT_ENABLED` (booléens : `true` insensible à la casse, sinon faux).
|
||
- Défauts : `Kind` = `system` si `MODULE_SYSTEM: true`, `library` si l'identifiant commence par `lps_lib_`, sinon `mod` ;
|
||
`MODULE_KIND` explicite prime. `bHidden` et `bLocked` valent `bSystem || Kind==Library` si la clé est absente.
|
||
`bDefaultEnabled` vaut vrai pour système/bibliothèque, faux pour un mod si la clé est absente.
|
||
- Avertissements (sans refus) : `MODULE_UNIQUEID` absent (identifiant = nom du fichier) ; identifiant ≠ nom du fichier ;
|
||
les 3 premières lignes utiles ne sont pas, dans l'ordre, `MODULE_UNIQUEID`, `MODULE_NAME`, `MODULE_AUTHOR`
|
||
(rétrocompatibilité avec la lecture par position de `GI`).
|
||
|
||
### 3.4 Contrôles et états (dans cet ordre, recalculés après chaque changement)
|
||
|
||
1. Identifiant en double (sandbox) : le second est `Refused` (« identifiant déjà utilisé par <dossier> »).
|
||
2. Kind Mod qui requiert un module de Kind Mod : `Refused` (« un mod ne peut pas requérir un autre mod (<id>) :
|
||
partager le composant dans une bibliothèque »).
|
||
3. Cycle dans les prérequis : tous les modules du cycle `Refused` (« cycle de prérequis : a → b → a »).
|
||
4. Prérequis introuvable : `Missing` (« prérequis absent : <id> ») ; prérequis présent mais non `Ready` ou désactivé :
|
||
`Missing` (« prérequis désactivé : <id> »). Un prérequis peut être un module du jeu (Data) : présent = satisfait.
|
||
5. Pas de fabrique (descripteur sandbox sans code) : `NoFactory` pour un mod ; une bibliothèque ou un système sans
|
||
fabrique est `Ready` (SB1 : services non encore extraits).
|
||
6. Sinon : `Ready` si activé, `Disabled` sinon. Système et bibliothèques sont toujours activés.
|
||
7. Si le mod ouvert n'est plus `Ready` : `Deactivate`, plus aucun mod ouvert (journal).
|
||
|
||
### 3.5 Ordre de `GetEntries()`
|
||
|
||
Système et bibliothèques par `Index` croissant (sans index : après, par identifiant) ; puis mods dans l'ordre persisté
|
||
(nouveaux en fin, par identifiant) ; puis modules du jeu par identifiant.
|
||
|
||
### 3.6 Journal `LogLPSandboxMods` (catégorie déclarée dans `LPSandboxModRegistry.h`, `LPSANDBOX_API`)
|
||
|
||
- `Display` au `Scan` : `Modules : <n> sandbox, <m> jeu ; ordre : <id>, <id>, …`
|
||
- `Display` par module : `<id> [<Kind>] <État> <raison>` ; `Warning` pour chaque avertissement de lecture et chaque refus.
|
||
- `Display` à chaque `SetEnabled`, `Move`, `Open` : `Ouvert : <id>` (preuve de chargement du nouveau code).
|
||
|
||
## 4. Persistance : `Saved/sandbox_mods.ini` (projet hôte)
|
||
|
||
```ini
|
||
[Mods]
|
||
Order=lps_couple,lps_interaction,lps_asset_integration
|
||
Open=lps_couple
|
||
[Enabled]
|
||
lps_couple=true
|
||
lps_interaction=true
|
||
lps_asset_integration=true
|
||
```
|
||
|
||
Lecture au `Scan` (`FConfigFile::Read`, comme `FLPSandboxConfig::LoadFile`), écriture après chaque changement
|
||
(`FConfigFile::Write`). Fichier absent : ordre par identifiant, cases = `bDefaultEnabled`, aucun mod ouvert mémorisé.
|
||
|
||
## 5. Enveloppes (`LPSandboxModWrappers.cpp`)
|
||
|
||
Trois classes `FLPSandboxModeWrapper : ILPSandboxMod` paramétrées par (Id, ModeLabel) :
|
||
|
||
| Id | ModeLabel |
|
||
|---|---|
|
||
| `lps_couple` | `Pair` |
|
||
| `lps_interaction` | `Interaction` |
|
||
| `lps_asset_integration` | `Mesure` |
|
||
|
||
- `Activate(Context)` : `Context.Player->OpenModeLabel(ModeLabel)`.
|
||
- `Deactivate` : rien (la transition, y compris `EndMeasure`, `SetAutoRun(false)`, `SetFreeView(false)` et la
|
||
restauration du bras caméra, est déjà faite par `SelectMode` quand le mode suivant s'ouvre).
|
||
- Fermeture sans mod suivant (mod ouvert désactivé) : `OpenModeLabel("Pair")` puis rangée d'animations masquée
|
||
(`Menu->AnimRow` réduit) : scène neutre.
|
||
- Enregistrement dans `FLPSandboxModule::StartupModule` : `RegisterFactory` ×3 puis `Scan()`.
|
||
|
||
## 6. Personnage (`ALPPairSandboxCharacter`)
|
||
|
||
- Ajouter `void OpenModeLabel(const FString& Label);` public : cherche `Label` dans `Menu->Modes` et appelle
|
||
`SelectMode` (privé, inchangé) ; journalise si introuvable.
|
||
- Le menu garde la liste interne `Menu->Modes` (Pair, Interaction, Mesure) mais **n'affiche plus** `ModeCombo`.
|
||
- Mod initial, à la fin de `ShowAnimMenu` (remplace le bloc actuel `sandbox_mode.txt` / `[Sandbox.Mode]`, même priorité
|
||
et même effacement de `sandbox_mode.txt`) :
|
||
1. `Saved/sandbox_mode.txt` présent : `Registry.Open(IdForModeLabel(contenu))` (pilote `AnimPipeline` : `Mesure`) ;
|
||
2. sinon `Open` persisté s'il est `Ready` ;
|
||
3. sinon `[Sandbox.Mode]` de `LPSandbox.ini` (défaut `Pair`) via `IdForModeLabel` ;
|
||
4. si le mod visé n'est pas `Ready` : premier mod `Ready` dans l'ordre ; aucun : scène neutre (§ 5).
|
||
- `ModeLabel` reste la vérité interne du mode ; le registre ne le modifie que par `OpenModeLabel`.
|
||
|
||
## 7. Barre latérale (`SLPSandboxSidebar`, T3)
|
||
|
||
- Remplace, dans `ShowAnimMenu`, le slot de `ModeCombo` (premier slot du `SVerticalBox`) ; le reste du panneau
|
||
(Mesure, MC, PNJ, animation, vue, filtres, aide) reste en dessous, inchangé.
|
||
- `SLATE_BEGIN_ARGS` : `_OnOpen` (`TFunction<void(const FString&)>`, le personnage appelle `Registry.Open(Id, Context)`).
|
||
- S'abonne à `Registry.OnChanged` et reconstruit ses lignes (désabonnement au destructeur).
|
||
- Section **Mods** (Kind Mod, non masqués) — une ligne : `SCheckBox` (désactivée si verrouillé) → `SetEnabled` ;
|
||
nom (`Name`) ; boutons ▲ ▼ → `Move(Id, -1/+1)` ; bouton ▶ (activé si `Ready`, libellé « ouvert » si `bOpen`) →
|
||
`OnOpen(Id)` ; si `Missing`/`Refused`/`NoFactory` : « ⚠ » avec infobulle `Reason`. Ligne du mod ouvert surlignée.
|
||
- Case **Afficher les modules masqués** (non persistée).
|
||
- Section **Système** (visible si la case est cochée) : `Index` en hexadécimal, `Id`, `Kind`, `Version`, état ; lecture seule.
|
||
- Section **Jeu (lecture)** (repliée par défaut, en-tête « Jeu : <n> modules ») : `Id`, `Name`, `Author`,
|
||
`Requirements`, avertissements de lecture en orange.
|
||
- Bouton **◀ / ▶** en tête du panneau : replie / déplie tout le panneau du menu (`Menu->Widget` réduit à la barre de titre).
|
||
- Style : polices et couleurs du menu actuel (`FCoreStyle::GetDefaultFontStyle("Regular", 14)` / 11 pour le détail),
|
||
largeur 300 comme les listes existantes.
|
||
|
||
## 8. Descripteurs (T3)
|
||
|
||
Sous `Plugins/LPSandbox/Modules/` ; trois premières lignes dans l'ordre `MODULE_UNIQUEID`, `MODULE_NAME`, `MODULE_AUTHOR`.
|
||
|
||
| Dossier | Id | Nom | Kind / clés | Prérequis |
|
||
|---|---|---|---|---|
|
||
| `System/0001_lps_system_core` | `lps_system_core` | Socle sandbox | `MODULE_SYSTEM: true` | — |
|
||
| `System/0002_lps_system_animmeasure` | `lps_system_animmeasure` | Chaîne de mesure (LPAnimMeasure) | `MODULE_SYSTEM: true` | `lps_system_core` |
|
||
| `System/0010_lps_lib_scene` | `lps_lib_scene` | Bibliothèque scène | (library par préfixe) | `lps_system_core` |
|
||
| `System/0011_lps_lib_actors` | `lps_lib_actors` | Bibliothèque personnages | (library) | `lps_system_core, lps_system_animmeasure` |
|
||
| `Mods/lps_couple` | `lps_couple` | Couple | `MODULE_DEFAULT_ENABLED: true` | `lps_system_core, lps_lib_scene, lps_lib_actors, lps_system_animmeasure` |
|
||
| `Mods/lps_interaction` | `lps_interaction` | Interaction | `MODULE_DEFAULT_ENABLED: true` | idem |
|
||
| `Mods/lps_asset_integration` | `lps_asset_integration` | Asset integration | `MODULE_DEFAULT_ENABLED: true` | `lps_system_core, lps_system_animmeasure` |
|
||
|
||
`MODULE_AUTHOR: LifePlay Revival`, `MODULE_VERSION: 0.1`, `MODULE_DESCRIPTION` d'une ligne.
|
||
|
||
## 9. Critères (repris par SB1-T5)
|
||
|
||
(a) chaque mod s'ouvre et se comporte comme l'ancien mode ; (b) couple → interaction → asset integration → couple ;
|
||
(c) mod décoché non ouvrable, et décocher le mod ouvert ramène la scène neutre ; (d) `Mods/lps_test_bad` (créé
|
||
pour l'essai puis retiré) requérant `lps_couple` → `Refused` avec raison ; (e) prérequis absent (renommer
|
||
temporairement `lps_lib_scene`) → `Missing` sur couple et interaction ; (f) ordre, cases et mod ouvert persistés après
|
||
relance ; (g) section Jeu : `.lpmod` du jeu lus, avertissements éventuels listés ; (h) pilote : `Saved/sandbox_mode.txt`
|
||
= `Mesure` + `Saved/sandbox_tour.txt` = `vignettes planches quitter` → tournée identique à AP7 (mesures, vignettes,
|
||
planches) ; (i) journal `LogLPSandboxMods` : 7 modules sandbox, ordre attendu, 0 refus hors essais.
|