// Lot MAPDL1 (MAPDL1-SPEC-05 v3, etape E1) : etats des villes dans le jeu et menu des villes. C++ seulement, aucun Blueprint modifie. // // Trois etats par ville (nom d'asset du jeu, ex. "Manila") : // - Installed : ville de base (liste "base" de l'index), ou ville dont un pack lpm_city_* monte redirige a la fois // /Game/Heavy/StreetMap/. et /Game/Heavy/StreetMap/Static/. (ULPAssetRegistryHelpers::ResolveCityPath) ; // - Downloadable : pas installee, mais l'index (Saved/LPCityMaps.json, sinon Content/Data/LPCityMaps.json) a une entree // avec au moins une URL, un sha256 de 64 chiffres hexadecimaux et une plage de versions du jeu qui contient la version ; // - Unavailable : ni l'un ni l'autre. // Les packs sont montes au demarrage et le cache de ResolveCityPath n'est jamais invalide : l'etat est calcule a la demande // et garde pour la session (un pack pose en cours de session ne change rien avant le lancement suivant). // // Ce que le sous-systeme fait : // - C2 : pose un filtre dans ModdingExtra (ModdingExtraSetFileListFilter). GetAllFilesFromFolder("Heavy/StreetMap","uasset"), // que lit UI/CityMenu pour compter les deux .uasset d'une ville "Installed", perd les entrees des villes non installees : // le calcul d'origine du Blueprint affiche alors "Not Installed" et bloque le clic (decision D4) ; // - C3 : tant que CityMenu.CityScrollBox est a l'ecran, tri stable des lignes City_C (installees, telechargeables, // indisponibles), texte et couleur de InstalledText, bouton desactive pour les indisponibles, relais du clic des lignes // telechargeables (ULPCityRowClick). GI.Cities n'est pas modifie ; // - C4 : bandeau de 10 s quand une partie sauvegardee charge le gabarit d'une ville non installee (OnCityMapResolved). // Etape E5 (C6) : le clic d'une ligne telechargeable ouvre la fenetre "City map: " (LPCityMapsWindow), qui pilote // le telechargement HTTP, le controle SHA-256 et la pose du pack (LPCityMapsDownload). Apres une pose reussie, la ville passe a // RestartRequired pour le reste de la session ("Restart required", clic sans effet). OpenCityPage (navigateur) ne sert plus // que de recours, depuis la fenetre. #pragma once #include "CoreMinimal.h" #include "Containers/Ticker.h" #include "Subsystems/GameInstanceSubsystem.h" #include "LPCityMapsSubsystem.generated.h" class SWidget; class UScrollBox; class UUserWidget; class ULPCityMapsSubsystem; class FLPCityMapsWindow; UENUM(BlueprintType) enum class ELPCityMapState : uint8 { Installed, Downloadable, Unavailable, /** Pack pose dans cette session : actif au prochain lancement du jeu (E5). */ RestartRequired }; /** Un renvoi "licences" de l'index : titre court affiche, fichier du texte complet (sous Licenses/). */ struct FLPCityMapLicence { FString Titre; FString Fichier; }; /** Une entree "villes" de l'index (format 2). Les URL non HTTPS sont ecartees a la lecture. */ struct FLPCityMapEntry { FString Ville; FString Pack; FString Version; FString JeuMin; FString JeuMax; FString Zip; int64 Octets = 0; FString Sha256; TArray Urls; FString Page; TArray Licences; FString Source; }; /** Etat calcule d'une ville : EntryIndex designe l'entree de l'index retenue (Downloadable seulement). */ struct FLPCityStateInfo { ELPCityMapState State = ELPCityMapState::Unavailable; int32 EntryIndex = INDEX_NONE; }; /** Relais du clic d'une ligne telechargeable du menu des villes (un objet par bouton, garde par le sous-systeme). */ UCLASS() class LIFEPLAY_API ULPCityRowClick : public UObject { GENERATED_BODY() public: /** Nom d'asset de la ville de la ligne. */ FString Ville; TWeakObjectPtr Owner; UFUNCTION() void OnClicked(); }; UCLASS() class LIFEPLAY_API ULPCityMapsSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() public: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; /** Etat d'une ville (nom d'asset, casse ignoree), calcule au premier appel puis garde pour la session. */ UFUNCTION(BlueprintCallable, Category = "LifePlay|Cities") ELPCityMapState GetCityState(const FString& Ville); /** Taille en octets du pack de la ville (champ "octets" de l'entree retenue) ; 0 si la ville n'est pas telechargeable. */ UFUNCTION(BlueprintCallable, Category = "LifePlay|Cities") int64 GetDownloadBytes(const FString& Ville); /** URL HTTPS du pack, dans l'ordre de l'index (vide si la ville n'est pas telechargeable). C++ seulement. */ TArray GetDownloadUrls(const FString& Ville); /** Entree de l'index retenue pour une ville telechargeable (nulle sinon). Valable jusqu'au prochain appel qui change l'index. */ const FLPCityMapEntry* FindDownloadEntry(const FString& Ville); /** Clic d'une ligne telechargeable : ouvre la fenetre "City map: " (une seule a la fois). */ void OpenCityWindow(const FString& Ville); /** Une ville dont le pack vient d'etre pose : "Restart required" jusqu'a la fin de la session. */ void MarkRestartRequired(const FString& Ville); /** Ouvre "page" (sinon urls[0]) dans le navigateur. Recours de la fenetre (la fenetre l'appelle elle-meme par LaunchURL). */ void OpenCityPage(const FString& Ville); private: /** Lit l'index (Saved/LPCityMaps.json, sinon Content/Data/LPCityMaps.json). */ void LoadIndex(); const FLPCityStateInfo& EvaluateCity(const FString& Ville); bool IsGameVersionInRange(const FLPCityMapEntry& Entry) const; /** Filtre pose dans ModdingExtra (C2). */ void FilterFileList(const FString& RootFolder, const FString& Ext, TArray& Files); /** Chaque image : liste des villes (C3) et fin du bandeau (C4). */ bool Tick(float DeltaTime); UScrollBox* FindCityList() const; bool IsListOnScreen(const UScrollBox* List) const; void SortRows(UScrollBox* List); void DecorateRows(UScrollBox* List); /** Bandeau des parties sauvegardees (C4). */ void HandleCityMapResolved(const FString& Requested, const FString& Resolved); bool ShowBanner(const FString& Text); void CloseBanner(); TArray BaseCities; TMap Licences; TArray Entries; FString IndexPath; /** Nom de ville en minuscules -> etat. */ TMap StateCache; FTSTicker::FDelegateHandle TickHandle; FDelegateHandle ResolvedHandle; /** Liste du menu vue en dernier (une instance par ouverture) et prochaine recherche (secondes, horloge de PlatformTime). */ TWeakObjectPtr ActiveList; double NextListSearch = 0.0; int32 SortedChildCount = -1; bool bSortLogged = false; /** Relais de clic de la liste courante, et boutons deja relies (objets faibles : une adresse reutilisee ne trompe pas). */ UPROPERTY() TArray> RowClicks; TSet> BoundButtons; TSet BannerShown; TSharedPtr Banner; double BannerExpiry = 0.0; /** Fenetre de telechargement (E5) : une seule a la fois. Liberee par Tick une fois fermee (jamais depuis son propre Close). */ TSharedPtr Window; /** Villes (minuscules) dont le pack a ete pose dans cette session. */ TSet RestartCities; };