| Contenu | `.lpaction`, `.lpscene`, `.lpstat`, `.lpquest`, `.lptalk`, `.lpdesc`, `.lpai`, `.lpcharacter`, `.lpworld`, `.lphair`, tables, crédits, catalogue | `Content/Paks/Windows/<id>LifePlay-Windows.pak/.utoc/.ucas` (IoStore, `AssetRegistry.bin` du mod dans le `.pak`) |
| Lu par | `ModdingExtra` (`LoadTxt`, `LPDataCache`), Blueprint `GI`, sous-systèmes C++ | Gestionnaire de plugins du moteur (montage), registre d'assets |
| Activation | case du gestionnaire de mods (`GI.ModEnableds`) | toujours monté s'il est présent (plugin `Mod` activé par défaut) |
| Existe depuis | jeu d'origine (Vinfamy) | lot H3 (2026-10-03) |
| Édition | `Tools/EditMod.ps1`, `Tools/ModSource.ps1` | le mod est déplacé dans `<projet>/Mods/` le temps du travail, puis remis dans `ModSources/` : le jeu de base n'en contient jamais rien |
| Cuisson | `Tools/BuildMod.ps1 -Name <id> -Version <jeu>` | cuisson DLC du seul plugin contre `Releases/<jeu>` ; refus si un autre plugin est dans `Mods/`, si une entrée du conteneur sort de `Mods/<id>/`, si un mod payant cite une licence non commerciale |
| Archive | `BuildMod.ps1` | `<id>-<version>-for-LifePlay-<jeu>.zip` testée ; le `.lpmod` livré reçoit `MODULE_PACKAGED` et `MODULE_GAME_VERSION`, le `.uplugin` livré `VersionName` et `CreatedByURL` |
Contrainte de compatibilité : un conteneur de mod est cuit **contre une version de référence précise** du jeu
(`Releases/<version>`, produite par `-createreleaseversion` à chaque publication). Les assets de base qu'il référence
(squelettes Genesis, matériau `DazHair`, paramètres de compression du projet) ne doivent plus être renommés ni déplacés.
Un mod ne doit référencer aucun contenu `/Engine/` non cuit par le jeu (cas des clips UE5 en AP7 : compression remise
au défaut du projet par `DeliverAnims.py`).
### 2.2 Installation et désinstallation
Le joueur extrait l'archive dans le dossier du jeu (celui de `LifePlay.exe`), en fusionnant le dossier `LifePlay`.
Pour désinstaller : supprimer `Content/Modules/<id>`**et**`Mods/<id>`. Aucun outil ne vérifie aujourd'hui qu'une
moitié n'a pas été laissée seule (voir § 6).
### 2.3 Démarrage du jeu
| Ordre | Qui | Ce qui se passe |
|---|---|---|
| 1 | Moteur, `FPluginManager::ReadAllPlugins` | lit **toujours**`<jeu>/Mods/` ; chaque `Mods/<id>/<id>.uplugin` devient un plugin de type `Mod`, activé par défaut (`FPlugin::IsEnabledByDefault` : chargé « depuis le projet ») |
| 2 | Moteur | `CanContainContent: true` : le point de montage `/<id>/` est ajouté, les conteneurs `Content/Paks/Windows/*` sont montés |
| 3 | Registre d'assets | ajoute le `AssetRegistry.bin` de chaque plugin de contenu : les assets du mod sont trouvables par chemin et par classe sans être chargés |
| 4 | `ULPModInfoSubsystem::Initialize` (C++) | lit chaque `Modules/<id>/<id>.lpmod` (toutes les clés), retient `MODULE_GAME_VERSION` ; journal `Modules : N .lpmod lu(s)` |
| 5 | `ULPModDailiesSubsystem::Initialize` (C++, AP5) | relève les `UAnimSequence` sous `/<plugin>/Anims` de chaque plugin `Mod` monté ; clé = nom sans suffixe `_F`/`_M` |
| 6 | `GI.LoadSettings` (Blueprint) | `GameSettings.sav` → `ModEnableds` ; `ReadModLauncher` (`Modules/ModLauncher.config`, absent en pratique) ; `removeDeletedMods` retire de la liste les modules dont le `.lpmod` n'existe plus |
| 7 | `ULPHairstyleSubsystem` (C++) | après `LoadSettings`, ajoute cochés les modules `MODULE_DEFAULT_ENABLED: true` absents de la liste ; relève les `.lphair` |
| 9 | `ULPModDailiesSubsystem::Tick` | dès que `GI.Room` existe : ajoute les clips de mod à `Room.Dailies` (une fois par pièce) |
| 10 | `ULPModInfoSubsystem::Tick` (toutes les 2 s) | module coché dont `MODULE_GAME_VERSION` ≠ `ProjectVersion` : fenêtre « Mod version warning », une fois par module et par session |
### 2.4 Activation et désactivation
- État : tableau `GI.ModEnableds` de structures `ModEnabled` (`UniqueID`, `IsEnabled`). Un module **absent** de la
liste est inactif (`GI.IsModEnabled`, `ULPModInfoSubsystem::IsModuleEnabled`, `ULPHairstyleSubsystem::IsModuleEnabled`).
- Gestionnaire de mods (`UI/ModManager`) : en partie (entrée d'origine) ou depuis l'écran titre (lot UI15). À la
fermeture : `PopulateRaws` (données relues avec les modules choisis) ; depuis l'écran titre, le choix est écrit dans
`GameSettings.sav` par `ULPModSettingsLibrary::SaveModSelection` (seul `ModEnableds` est remplacé).
- Effet de la case selon la nature du contenu :
| Contenu | Décoché → |
|---|---|
| Données texte (actions, scènes, stats…) | non lues par `PopulateRaws` : absentes du jeu |
| Coiffures (`.lphair`) | retirées des listes (`LPHairstyleSubsystem`) ; paquet toujours monté, inutilisé |
| Animations (`/<id>/Anims`) | **toujours ajoutées** à `Room.Dailies` : `LPModDailies` ne consulte pas la case (§ 6, E1) |
| Avertissement de version | non affiché |
| Plugin Unreal | toujours monté : le moteur ignore `ModEnableds` |
| `Plugins` : `[{ "Name": "...", "Enabled": true, "Optional": false }]` | oui, appliqué | dépendance obligatoire absente : la configuration des plugins échoue et le jeu ne démarre pas (`LaunchEngineLoop.cpp` renvoie `false` après `LoadModulesForEnabledPlugins`) ; `"Optional": true` : ignorée si absente |
| `Hidden` | oui | utilisé seulement par le navigateur de plugins de l'éditeur ; sans effet en jeu |
| `Sealed` | oui | interdit qu'un autre plugin en dépende |
| `DisallowedPlugins` | oui | interdit une dépendance donnée |
| `SupportedTargetPlatforms`, `EngineVersion` | oui si présentes | plateformes ; `EngineVersion` ne fait qu'avertir |
| `FriendlyName`, `Description`, `Category`, `CreatedBy`, `CreatedByURL`, `Version`, `VersionName`, `IsBetaVersion`, `Installed` | non en jeu | descriptifs (navigateur de plugins de l'éditeur) |
### 3.2 `<id>.lpmod` (jeu)
| Clé | Lue par | Effet |
|---|---|---|
| ligne 1 `MODULE_UNIQUEID` | `GI` (**par position**), sous-systèmes C++ (par clé) | identifiant du module |
| ligne 2 `MODULE_NAME` | `GI` (par position), `LPModInfoSubsystem` | nom dans le gestionnaire, dans l'avertissement |
| ligne 3 `MODULE_AUTHOR` | `GI` (par position) | auteur dans le gestionnaire |
| `MODULE_DEFAULT_ENABLED` | `LPHairstyleSubsystem` | coché au premier démarrage où il est présent |
| `MODULE_GAME_VERSION` | `LPModInfoSubsystem` | avertissement de version |
| `MODULE_LINK`, `MODULE_DESCRIPTION`, `MODULE_REQUIREMENTS`, `MODULE_VERSION`, `MODULE_CREATED`, `MODULE_LICENSE`, `MODULE_AUTHOR_LINK`, `MODULE_PRICE`, `MODULE_PACKAGED` | aucun code | affichées telles quelles : le gestionnaire montre le fichier entier ; `GetModuleMetadata` les rend accessibles aux Blueprints |
| Visible / masqué | aucune notion : tout `.lpmod` présent est listé ; le gestionnaire affiche le fichier entier dans le panneau de détail (`FullString`) | `Hidden` : lu, utilisé seulement par le navigateur de plugins de l'éditeur ; sans effet en jeu | pas de masquage possible |
| Verrouillé / non désactivable | `vin_Base` en dur dans `UI/ModuleTick` (case sans effet, boutons d'ordre masqués) ; aucune clé `.lpmod` | aucun verrouillage d'un plugin `Mod` : monté dès qu'il est présent | seul `vin_Base` est verrouillé |
| Module système chargé avant les autres (dossier distinct) | aucun : tous les modules sont dans `Content/Modules/<id>` | les plugins du jeu (`Plugins/`, cuits avec lui) sont configurés avant ceux de `Mods/` | existe côté moteur seulement, hors de portée d'un mod |
| Ordre de chargement / priorité | `GI.ModEnableds` ordonné, `GI.SortLoadOrder` ; le plus bas dans la liste affichée l'emporte, par nom de fichier (§ 2.4) ; fichiers texte seulement | seul ordre réglable : `LoadingPhase` des modules de code (un mod n'en a pas) ; conteneurs montés avec la même priorité (`FCoreDelegates::MountPak.Execute(PakPath, 0)`, `PluginManager.cpp`) | ordre réel pour les textes, aucun pour les assets |
| Dépendances | `MODULE_REQUIREMENTS` non vérifié | `Plugins` appliqué ; dépendance obligatoire absente : le jeu ne démarre pas ; `Optional`, `Sealed`, `DisallowedPlugins` (§ 3.1) | côté jeu : aucune ; côté moteur : bloquante |
Conséquence pour les mods : ne jamais déclarer de dépendance obligatoire entre mods dans le `.uplugin` (un mod
manquant empêcherait le jeu de démarrer). Un prérequis entre mods se gère côté jeu (§ 6, E2).
### 3.4 Doublons entre descripteurs
| Information | `.uplugin` | `.lpmod` | `pack.json` / config | Autres | Source de vérité | Écrit par |
|---|---|---|---|---|---|---|
| Identifiant | nom du fichier | `MODULE_UNIQUEID` | `chaine.mod` | dossiers | aucune désignée | `NewMod.ps1`, `DeliverAnims.ps1` |
| Titre | `FriendlyName` | `MODULE_NAME` | `source` | 1re ligne de `CREDITS.txt` et de `README.txt` | aucune désignée | `NewMod.ps1`, `DeliverAnims.ps1` ; README par `BuildMod.ps1` |
| `UI/ModManager`, `UI/MainMenu` | gestionnaire de mods ; entrée de l'écran titre (`LPTitleModManager`) |
| `ULPModInfoSubsystem` | lecture des `.lpmod`, `GetModuleMetadata`, `IsModuleEnabled`, avertissement de version |
| `ULPModSettingsLibrary::SaveModSelection` | enregistre `ModEnableds` seul dans `GameSettings.sav` |
| `ULPHairstyleSubsystem` | `MODULE_DEFAULT_ENABLED`, coiffures `.lphair` des modules cochés |
| `ULPModDailiesSubsystem`, `ULPModDailiesLibrary::IsModDaily` | clips de mod → `Room.Dailies` ; reconnaissance des clés de mod dans `Person::PlayDaily` |
| `ULPSessionLogSubsystem` | inventaire des modules au démarrage (journal) |
| E1 | `LPModDailies` : ne garder que les clips des modules cochés (même règle que `IsModuleEnabled`), au remplissage de `Room.Dailies` | un seul interrupteur pour tous les types de contenu ; clé « inconnue » quand le mod est décoché | petit lot C++, essai empaqueté coché / décoché |
| E2 | Vérifier `MODULE_REQUIREMENTS` au démarrage et à la fermeture du gestionnaire (avertissement comme pour la version) | prérequis manquant signalé au lieu d'un mod à moitié fonctionnel | petit lot C++ |
| E3 | Contrôle de cohérence des deux moitiés : `Mods/<id>` sans `Modules/<id>` (assets orphelins) ou l'inverse (`.lphair` / clés vers des assets absents) | désinstallation partielle détectée | petit lot C++ |
| E4 | Avertissement de version élargi aux mods d'assets décochés (un conteneur incompatible est monté même décoché) | prévenir avant un échec de chargement | très petit |
| E5 | Détection des collisions : deux mods qui déclarent la même clé d'animation, la même coiffure, le même identifiant d'action | comportement déterministe, message clair | moyen |
| E6 | Lecture des trois premières lignes du `.lpmod` par clé plutôt que par position (Blueprint `GI` ou relais C++) | un `.lpmod` écrit à la main dans le désordre ne casse plus le gestionnaire | moyen (graphe de `GI`) |
| E7 | Ne pas monter les plugins des mods décochés (liste lue avant le montage, `ExplicitlyLoaded` + montage par `IPluginManager::MountExplicitlyLoadedPlugin`) | mémoire et temps de démarrage ; mod réellement inerte | moyen, à prototyper (ordre de démarrage) |
| E8 | Déclaration explicite des animations (`.lpanim` : clé, clips F/M, catégorie, usage, posture) plutôt que par convention de nom | catalogue en jeu, filtres par posture, alias, plusieurs variantes par clé | moyen |
| E9 | Autres types d'assets par le même schéma registre + fichier texte : vêtements, meubles, décors de pièce (`.lpworld` vers `/<id>/…`) | extension naturelle de H3 | un lot par type |
| E10 | Empreintes dans l'archive (SHA-256 par fichier, vérifiées par un outil d'installation) | intégrité, mods modifiés détectés | petit (outillage) |
| E11 | Gestionnaire enrichi : description, licence, lien, version, état (cuit pour, prérequis, conflits), catalogue d'animations | information du joueur, aujourd'hui texte brut | moyen (UI) |
| E12 | Ordre étendu aux mods d'assets et clés d'animation (aujourd'hui seuls les fichiers texte suivent l'ordre) | surcharge contrôlée aussi pour les animations et les coiffures | moyen ; dépend de `PopulateRaws` et de `LPModDailies` |
| E13 | Notions déclarées dans le `.lpmod` : `MODULE_LOCKED`, `MODULE_HIDDEN`, `MODULE_PRIORITY` ou rang système, en remplacement du verrouillage en dur de `vin_Base` | verrouillage, masquage et priorité par module, sans Blueprint codé en dur | moyen (graphe de `UI/ModuleTick` et de `GI`) |
| E14 | Descripteur unique : `.lpmod` (ou `pack.json`) source de vérité, `.uplugin` et README générés à chaque construction, `New-IfMissing` remplacé par une régénération | fin des divergences entre descripteurs (§ 3.4) | petit à moyen (outillage) |
| E15 | Relevé `LPDumpFunction` : afficher le nom des événements personnalisés et des macros | confirmer le sens de la boucle de `PopulateRaws` (§ 2.4) | petit (outillage) |