LifePlay-Revival/Docs/Migration/etat/REL/MAPDL1/MAPDL1-E1-FICHE.md
2026-10-22 21:10:39 +02:00

14 KiB

Fiche MAPDL1-E1 — Niveau N3 — Modèle prévu sonnet (agent lp-impl ; escalade lp-impl-max) — Agent lp-impl — Skills sk-protocol, lp-city, lp-lot, ue-cpp-build

MAPDL1-E1 : états des villes et menu (C1 à C5 de MAPDL1-SPEC-05 v3), en C++, sans Blueprint

Décision de l'utilisateur, citée par Big Boss : « Oui : SPEC-05 v3 approuvée (S5-1 à S5-5, D10 = téléchargement HTTP dans le jeu avec navigateur en recours, D11 = un zip par pack). Lance la réalisation avec les modèles réduits ; l'hébergeur et le domaine, je les choisis plus tard. »

Hors moteur :

  • aucun build, aucun commandlet, aucun éditeur. La compilation est l'étape E2, sous jeton ;
  • aucun commit ;
  • rien sous Mods\ ;
  • aucun Blueprint modifié.

Le téléchargement HTTP (C6) n'est pas dans cette fiche : c'est E5.

Références à lire d'abord :

  • MAPDL1-SPEC-05.md (v1, § 1-2), MAPDL1-SPEC-05-v3.md (§ 1), MAPDL1-E0-INV.md ;
  • MAPDL1-L1-CART-02.md et MAPDL1-L4-CART-01.md (menu, lignes City_C, calcul « Installed ») ;
  • modèle de code : Source/LifePlay/LPModInfoSubsystem.h/.cpp, TickModManager. Il trouve un UScrollBox par TObjectIterator, lit des variables de Blueprint par FindPropertyByName et modifie les lignes.

0. Avant toute écriture

  1. Instantané, depuis pwsh, un chemin par appel :
    • .\Tools\Snapshot.ps1 -Lot MAPDL1-E1 -Path <chemin> pour chacun des fichiers du § 1 ;
    • les fichiers neufs sont notés absents.
  2. Relever les MD5 (Get-FileHash -Algorithm MD5) des fichiers modifiés, et des fichiers de ville de STM1 non commités. Ces derniers ne doivent pas changer :
    • Source/LifePlay/LPCityWaterSubsystem.*, LPCityWaterMask.h, LPCityNamesSubsystem.*, LPCityHouses.h, LPCityLandmarks.h, LPAssetRegistryHelpers.*.
  3. git status --short sur les fichiers du § 1 : aucune modification en cours attendue, sinon arrêt et rapport.

1. Fichiers (et seulement ceux-là)

Fichier État Contenu
Plugins/ModdingExtra/Source/ModdingExtra/Public/ModdingExtraBPLibrary.h M crochet du § 2.1
Plugins/ModdingExtra/Source/ModdingExtra/Private/ModdingExtraBPLibrary.cpp M crochet du § 2.1
Source/LifePlay/LifePlay.Build.cs M PrivateDependencyModuleNames : ajouter "ModdingExtra" et "Json", avec un commentaire « Lot MAPDL1 (SPEC-05) »
Source/LifePlay/LPCityMapsSubsystem.h et .cpp A § 2.2 à 2.5
Content/Data/LPCityMaps.json A § 2.6
Config/DefaultGame.ini M une ligne après la l. 74 (+DirectoriesToAlwaysStageAsNonUFS=(Path="Modules")) : +DirectoriesToAlwaysStageAsUFS=(Path="Data"), pour livrer le JSON dans le pak
Tools/IndexCartes.ps1 A § 2.7

Conventions :

  • code dans le style des fichiers voisins (commentaires en français sans accents, // Lot MAPDL1 (MAPDL1-SPEC-05) :) ;
  • Tools\*.ps1 en ASCII et LF ;
  • pas de python -c, pas de heredoc ;
  • outil Edit pour modifier (jamais sed ni -replace).

2. Contenu

2.1 Crochet dans ModdingExtra (C2)

Dans ModdingExtraBPLibrary.h, hors de la classe, ajouter :

/** Lot MAPDL1 (MAPDL1-SPEC-05) : filtre facultatif de la liste rendue par GetAllFilesFromFolder, pose par le module du jeu
 *  (ULPCityMapsSubsystem). Arguments : dossier demande et extension tels que recus, liste a modifier. Sans filtre : inchange. */
using FModdingExtraFileListFilter = TFunction<void(const FString& /*RootFolder*/, const FString& /*Ext*/, TArray<FString>& /*Files*/)>;
MODDINGEXTRA_API void ModdingExtraSetFileListFilter(FModdingExtraFileListFilter Filter);

Dans le .cpp :

  • une variable statique de fichier FModdingExtraFileListFilter GLPFileListFilter; et la définition de ModdingExtraSetFileListFilter ;
  • dans GetAllFilesFromFolder, garder une copie de Ext tel que reçu au début (la fonction le transforme en *.uasset) ;
  • juste avant return true; (l. 224 actuelle) : if (GLPFileListFilter) { GLPFileListFilter(RootFolderFullPath, ExtRecu, Files); }.

Rien d'autre ne change. Le plugin ne dépend toujours pas du jeu.

2.2 ULPCityMapsSubsystem : index et états (C1)

UCLASS() class LIFEPLAY_API ULPCityMapsSubsystem : public UGameInstanceSubsystem

Index. Dans Initialize, lire le premier fichier présent parmi :

  • (a) FPaths::ProjectSavedDir()/LPCityMaps.json : surcharge locale, pour les essais (MAPDL1-ESSAI-FJ F4) ;
  • (b) FPaths::ProjectContentDir()/Data/LPCityMaps.json : copie livrée.

Lecture par FFileHelper::LoadFileToString puis FJsonSerializer.

  • Champs (format 2, SPEC v3 § 1) : base[], licences{}, villes[] avec ville, pack, version, jeu_min, jeu_max, zip, octets, sha256, urls[], page, licences[], source.
  • Journal : [LPCity] CARTES index <chemin> : base <n>, villes <n> (catégorie LogLPCity, celle de LPAssetRegistryHelpers.cpp).
  • Index absent ou illisible : avertissement, et aucune ville téléchargeable. Les villes « base » prennent alors pour valeur par défaut CentralLondon et 00BathCC.

État d'une ville V, nom d'asset du jeu, calculé à la demande et mis en cache. Les packs sont montés au démarrage, et le cache de ResolveCityPath n'est jamais invalidé, ce qui est voulu :

  • Installed si V est dans base, ou si, pour les deux chemins /Game/Heavy/StreetMap/V.V et /Game/Heavy/StreetMap/Static/V.V, ULPAssetRegistryHelpers::ResolveCityPath(p) != p ;
  • sinon Downloadable si une entrée ville == V (casse ignorée) a au moins une URL, un sha256 de 64 caractères hexadécimaux, et jeu_min <= version du jeu <= jeu_max. La version du jeu est ProjectVersion (FLPModGuard::GetGameVersion()) ; comparaison par composantes séparées par des points, numérique quand les deux composantes sont des nombres, sinon textuelle ;
  • sinon Unavailable.

API (UENUM(BlueprintType) ELPCityMapState { Installed, Downloadable, Unavailable }) :

  • GetCityState(const FString& Ville) ;
  • GetDownloadBytes(Ville) ;
  • GetDownloadUrls(Ville) (C++).

Une ligne de journal par ville au premier calcul : [LPCity] ETAT <V> : <etat>.

Pose du crochet dans Initialize : ModdingExtraSetFileListFilter(...) avec un TWeakObjectPtr sur le sous-système. Le retirer dans Deinitialize (filtre vide). Le filtre n'agit que si :

  • le dossier, une fois normalisé (\ devient /, sans / au début ni à la fin), vaut Heavy/StreetMap (casse ignorée) ;
  • et l'extension reçue est uasset ou .uasset.

Il retire alors de Files toutes les entrées dont le nom de fichier sans extension est une ville dont l'état n'est pas Installed. Les entrées des villes installées ne sont pas touchées : le comptage d'origine du Blueprint UI/City, qui exige 2 entrées pour « Installed », doit rester intact. Journal, une fois par appel : [LPCity] LISTE Heavy/StreetMap : <n> entrees, <m> retirees.

E0 a établi que GetAllFilesFromFolder est appelé par 6 Blueprints (GI, Room, Builder, CharGen, StatMenu, CityMenu), mais que seul CityMenu porte le littéral Heavy/StreetMap. Le filtre ne touche que ce dossier.

2.3 Menu des villes (C3)

Un ticker FTSTicker de 0 s, comme TickModManager, actif tant que le sous-système existe. À chaque passage :

  1. Trouver un UScrollBox nommé CityScrollBox, dont l'objet extérieur est de classe CityMenu_C et qui est à l'écran (IsVisible() et GetCachedWidget().IsValid()).
  2. Pour chaque enfant de classe City_C, lire :
    • la variable City (FStructProperty), et dans cette structure le champ dont le nom commence par StreetMapName (FNameProperty ; les champs d'une structure Blueprint ont un suffixe). Le nom de ville est la partie après le dernier . du chemin, ou après le dernier / s'il n'y a pas de . ;
    • les variables InstalledText (UTextBlock) et CityButton (UButton).
  3. Selon l'état :
    • Installed : ne rien toucher (texte et clic d'origine) ;
    • Downloadable :
      • InstalledText->SetText("Download (%d MB)"), arrondi au Mo supérieur ;
      • SetColorAndOpacity bleu (0.2, 0.6, 1.0) ;
      • relais de clic (point 5) ;
    • Unavailable :
      • InstalledText->SetText("Not available"), gris (0.5, 0.5, 0.5) ;
      • CityButton->SetIsEnabled(false) ;
      • opacité de la ligne à 0,6.
    • Réappliquer à chaque passage, car le Blueprint écrit InstalledText après un Delay 0.2 (MAPDL1-L4-CART-01).
  4. Tri, une fois par instance de liste, et de nouveau si le nombre d'enfants change :
    • tri stable en trois groupes : Installed, puis Downloadable, puis Unavailable ; l'ordre d'origine est gardé dans chaque groupe ;
    • retrait de tous les enfants, puis réinsertion dans le nouvel ordre ;
    • relever le Padding, l'alignement horizontal et l'alignement vertical de chaque UScrollBoxSlot avant le retrait, et les remettre après.
    • GI.Cities n'est pas modifié.
    • Journal, une fois par instance : [LPCity] MENU tri : <i> installees, <d> telechargeables, <u> indisponibles.
  5. Relais de clic des lignes Downloadable :
    • un petit UCLASS() ULPCityRowClick : UObject (même fichier) avec FString Ville, un TWeakObjectPtr vers le sous-système et UFUNCTION() void OnClicked();. Lié par CityButton->OnClicked.AddUniqueDynamic ;
    • le sous-système garde ces objets (UPROPERTY() TArray<TObjectPtr<ULPCityRowClick>>) et les vide quand la liste change d'instance ;
    • dans cette fiche, OnClicked ouvre urls[0] (ou page si elle n'est pas vide) par FPlatformProcess::LaunchURL. C'est le recours (c), en attendant la fenêtre de C6 en E5. Journal : [LPCity] CLIC <V> : navigateur <url>.

2.4 Bandeau des parties sauvegardées (C4)

Dans Initialize, s'abonner à ULPAssetRegistryHelpers::OnCityMapResolved() (LPAssetRegistryHelpers.h:19, deux paramètres : demandé, résolu). Si :

  • le demandé est /Game/Heavy/StreetMap/V.V (pas Static/) ;
  • le résolu est égal au demandé (aucun pack) ;
  • l'état de V n'est pas Installed.

Alors afficher pendant 10 s, par GEngine->GameViewport->AddViewportWidgetContent (Slate, ordre Z élevé, en haut au centre), le texte :

  • Map not installed - download it from the city menu si l'état est Downloadable ;
  • Map not available sinon.

Une seule fois par ville et par session. Journal : [LPCity] BANDEAU <V> : <texte>. Se désabonner dans Deinitialize.

2.5 Ce qu'il ne faut pas faire

  • Ne pas modifier ResolveCityPath, LPCityWaterSubsystem, LPCityNamesSubsystem, ni aucun fichier de STM1.
  • Ne pas toucher GI.Cities, les sauvegardes, Content/Heavy.
  • Aucun texte de jeu en dur hors de ceux du § 2.3 et du § 2.4.

2.6 Content/Data/LPCityMaps.json livré

Format 2 :

  • "base": ["CentralLondon", "00BathCC"] ;
  • "licences" :
    • osm : titre « Map data (c) OpenStreetMap contributors, ODbL 1.0 », fichier « Licenses/OpenStreetMap_Geodata_License.txt » ;
    • lifeplay_ancienne : titre = le texte validé (S5-5) de MAPDL1-SPEC-05-v2.md § 4, mot pour mot, fichier « Licenses/Vinfamy_License.txt » ;
  • "villes": [] : aucun hébergeur n'est choisi (H1, H2). Une liste d'essai se pose dans Saved/LPCityMaps.json.

UTF-8 sans BOM, LF.

2.7 Tools/IndexCartes.ps1 (C5)

Il écrit un LPCityMaps.json format 2. Paramètres :

  • -Villes A,B ou -Toutes : toutes les archives lpm_city_*-*-for-LifePlay-<jeu>.zip de <Publication>\Mods, en prenant la plus haute version par pack ;
  • -Hebergeur Forgejo|GitHub|Modele, avec :
    • Forgejo : -ForgejoHote (défaut forgejo.edujiro5972.fr), -Owner, -Repo, -Tag, d'où https://<hote>/<owner>/<repo>/releases/download/<tag>/<zip> ;
    • GitHub : -Owner, -Repo, -Tag, d'où https://github.com/<owner>/<repo>/releases/download/<tag>/<zip> ;
    • Modele : -Modele "https://…/{zip}" ;
  • -Miroir (facultatif) : un second modèle {zip}, ajouté en 2e URL ;
  • -JeuMax (défaut 2026.12.99) ;
  • -Sortie (défaut <Racine>\Saved\LPCityMaps.json ; écrire Content\Data\LPCityMaps.json exige -Livre) ;
  • -ABlanc : affiche le JSON, n'écrit rien.

Pour chaque pack :

  • ville : le nom d'asset, tiré de ModSources\<pack>\Content\StreetMap\<V>.uasset ;
  • version : VersionName du .uplugin source ;
  • zip, octets, sha256 calculés sur l'archive ;
  • jeu_min : la version du jeu tirée du nom de l'archive ;
  • licences : ["osm","lifeplay_ancienne"] ;
  • source : la ligne - file … SHA-256 … du CREDITS source.

Racines lues par Get-LPProjet et Get-LPRacine. Codes : 0 écrit ; 2 usage, archive ou source manquante. ASCII et LF ; en-tête .SYNOPSIS.

Essai à faire (sans réseau), avec des valeurs fictives : -Villes Manila -Hebergeur Forgejo -Owner exemple -Repo exemple -Tag cartes-test-1 -ABlanc. Recopier la sortie.

3. Contrôles hors moteur à faire

  1. git diff --stat des fichiers M, et liste des fichiers A.
  2. MD5 avant et après de chaque fichier du § 1, et MD5 des fichiers de STM1 inchangés.
  3. Fins de ligne : les fichiers .cpp, .h et .cs gardent leur convention, vérifiée par git ls-files --eol pour les fichiers suivis (LF attendu).
  4. Analyse syntaxique PowerShell de IndexCartes.ps1 ([System.Management.Automation.Language.Parser]::ParseFile) : 0 erreur ; essai à blanc du § 2.7.
  5. python -I -X utf8 -c est interdit ; pour valider le JSON, lancer Test-Json sous pwsh.

4. Livrable

Docs\Migration\etat\REL\MAPDL1\MAPDL1-E1-IMPL.md :

  • en-tête sk-protocol, avec le modèle réellement utilisé ;
  • fichiers, MD5 avant et après, diff résumé ;
  • choix faits là où la fiche laisse une latitude ;
  • points non sûrs, à vérifier à la compilation (E2) ;
  • sorties des contrôles du § 3 ;
  • compte rendu.

Budget : 60 tours. En cas de blocage (API introuvable, compilation impossible à anticiper), arrêt et rapport : on ne contourne pas.