245 lines
14 KiB
Markdown
245 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.
|