LifePlay-Revival/Source/LifePlay/LPCityMapsSubsystem.h
2026-10-22 21:10:39 +02:00

178 lines
7 KiB
C++

// 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/<V>.<V> et /Game/Heavy/StreetMap/Static/<V>.<V> (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: <ville>" (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<FString> Urls;
FString Page;
TArray<FString> 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<ULPCityMapsSubsystem> 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<FString> 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: <ville>" (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<FString>& 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<FString> BaseCities;
TMap<FString, FLPCityMapLicence> Licences;
TArray<FLPCityMapEntry> Entries;
FString IndexPath;
/** Nom de ville en minuscules -> etat. */
TMap<FString, FLPCityStateInfo> StateCache;
FTSTicker::FDelegateHandle TickHandle;
FDelegateHandle ResolvedHandle;
/** Liste du menu vue en dernier (une instance par ouverture) et prochaine recherche (secondes, horloge de PlatformTime). */
TWeakObjectPtr<UScrollBox> 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<TObjectPtr<ULPCityRowClick>> RowClicks;
TSet<TWeakObjectPtr<UObject>> BoundButtons;
TSet<FString> BannerShown;
TSharedPtr<SWidget> 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<FLPCityMapsWindow> Window;
/** Villes (minuscules) dont le pack a ete pose dans cette session. */
TSet<FString> RestartCities;
};