H2b : pilosite (barbe, torse, pubis) dans les fichiers .lphair
Listes GI.FHairstyles, BHairstyles_M et BHairstyles reconstruites depuis vin_Base.lphair, completees par les modules ; verification : identiques au Blueprint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
991d1999f7
commit
f8bdf90bb2
4 changed files with 136 additions and 4 deletions
|
|
@ -655,6 +655,23 @@ générés normaux, Continue.
|
|||
**Usage pour un moddeur** : ajouter une coiffure = un fichier `.lphair` dans son module ; modifier la liste de base
|
||||
(retirer une coiffure du tirage, changer son sexe, son morph) = éditer `vin_Base.lphair`, sans éditeur Unreal.
|
||||
|
||||
### 16.10 H2b : pilosité (barbe, torse, pubis) en fichier de données (2026-10-02)
|
||||
|
||||
**Constat** : la pilosité n'est pas faite de maillages mais de **textures** appliquées à la peau par
|
||||
`Person.setBHairstyle` (paramètre de matériau `BHairstyle`), cherchées dans un dossier fixe + nom : hommes
|
||||
`/Game/Heavy/Daz/Michael/hairs/other/`, femmes `/Game/Heavy/Daz/Bridget/Hairs/Pubic/`. Les noms tirés viennent de trois
|
||||
listes de `GI` : `FHairstyles` (barbe, 29 noms dont `NoHair`), `BHairstyles_M` (torse masculin, 6) et `BHairstyles`
|
||||
(pubis féminin, 9). `GI.OtherTable` (13 barbes et pilosités en maillages sous `/Game/Heavy/Characters/Hair/Other`) est
|
||||
un reste inutilisable : ces maillages sont absents du projet, déjà en 4.27 ; elle n'est pas reprise.
|
||||
|
||||
**Réalisation** : trois clés de plus dans les fichiers `.lphair` — `FACIAL_HAIR`, `BODY_HAIR_M`, `BODY_HAIR_F` (un nom
|
||||
de texture par ligne). Le fichier de base `vin_Base.lphair` reconstruit chaque liste dans l'ordre du Blueprint ; les
|
||||
autres modules y ajoutent des noms, après contrôle qu'une texture de ce nom existe dans le dossier imposé (la texture
|
||||
d'un mod doit donc être cuite dans ce dossier).
|
||||
|
||||
**Vérification** (`-LPVerifyHairstyles`, build `LifePlay_Build_5.5_H2`) : `FHairstyles`, `BHairstyles_M` et
|
||||
`BHairstyles` « IDENTIQUE au Blueprint » ; coiffures et morphs toujours identiques.
|
||||
|
||||
## 17. Couche de données : stratégie (2026-10-02)
|
||||
|
||||
Proposition utilisateur : convertir les fichiers texte de `Modules` en fichiers JSON groupés par type, avec un calque de
|
||||
|
|
|
|||
|
|
@ -934,3 +934,4 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol
|
|||
| CHG-087 | 2026-10-02 | O7e | `Pocolov17_Cap` (hors dépôt) : `MapOpacity` = texture blanche du moteur ; `11_Audit_Optimisation.md` §16.8 | second essai en jeu correct (crâne couvert, couleur appliquée, profil bien placé) ; trous au-dessus de l'oreille dus à la lisière découpée de la calotte ; retouche choisie par l'utilisateur ; build `LifePlay_Build_5.5_O7e` reconstruit | remettre `Pocolov17_CapOpacity` dans `MapOpacity` |
|
||||
| CHG-088 | 2026-10-02 | O7e | — | O7e validé en jeu par l'utilisateur (profil sans trou, mouvement suivi) ; défaut mineur accepté : bas de la calotte visible sous le lobe de l'oreille | — |
|
||||
| CHG-089 | 2026-10-02 | H2 | `Source/LifePlay/LPHairstyleSubsystem.h/.cpp` (mode base, plusieurs coiffures par fichier, `NONE`, `MORPHABLE`, vérification `-LPVerifyHairstyles`) ; nouveau `Content/Modules/vin_Base/Hairstyles/vin_Base.lphair` (126 coiffures, 89 morphs) ; `11_Audit_Optimisation.md` §16.9 | liste de base des coiffures sortie du Blueprint (demande utilisateur) ; vérifié : table et morphs reconstruits identiques au Blueprint ; build `LifePlay_Build_5.5_H2` ; validé en jeu le 2026-10-02 (nouvelle partie, liste du menu inchangée avec Pocolov17 en dernier, personnages générés, Continue) | supprimer `vin_Base.lphair` (retour à la table du Blueprint) ; `git checkout H2-avant -- Source/LifePlay` |
|
||||
| CHG-090 | 2026-10-02 | H2b | `Source/LifePlay/LPHairstyleSubsystem.h/.cpp` (clés `FACIAL_HAIR`, `BODY_HAIR_M`, `BODY_HAIR_F`) ; `Content/Modules/vin_Base/Hairstyles/vin_Base.lphair` (+ 44 noms) ; `11_Audit_Optimisation.md` §16.10 | pilosité sortie du Blueprint (demande utilisateur) : listes `GI.FHairstyles`, `BHairstyles_M`, `BHairstyles` ; `GI.OtherTable` laissée (maillages absents depuis 4.27) ; vérifié : 3 listes identiques au Blueprint ; build `LifePlay_Build_5.5_H2` ; test en jeu en attente | retirer les lignes `*_HAIR` de `vin_Base.lphair` ; `git checkout H2b-avant -- Source/LifePlay` |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
#include "AssetRegistry/IAssetRegistry.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Algo/Find.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
#include "LifePlay.h"
|
||||
|
|
@ -20,6 +22,19 @@ namespace
|
|||
const FName ModEnabledsName(TEXT("ModEnableds"));
|
||||
constexpr double CheckInterval = 2.0;
|
||||
|
||||
/** Listes de pilosite : cle du fichier, propriete de GI, dossier des textures (prefixe de Person.setBHairstyle). */
|
||||
struct FTextureList
|
||||
{
|
||||
const TCHAR* Key;
|
||||
const TCHAR* Property;
|
||||
const TCHAR* Folder;
|
||||
};
|
||||
const FTextureList TextureLists[] = {
|
||||
{ TEXT("FACIAL_HAIR"), TEXT("FHairstyles"), TEXT("/Game/Heavy/Daz/Michael/hairs/other/") },
|
||||
{ TEXT("BODY_HAIR_M"), TEXT("BHairstyles_M"), TEXT("/Game/Heavy/Daz/Michael/hairs/other/") },
|
||||
{ TEXT("BODY_HAIR_F"), TEXT("BHairstyles"), TEXT("/Game/Heavy/Daz/Bridget/Hairs/Pubic/") },
|
||||
};
|
||||
|
||||
/** Champ d'une structure Blueprint par son nom d'auteur (les noms internes portent un suffixe GUID). */
|
||||
const FProperty* FindField(const UStruct* Struct, const TCHAR* AuthoredName)
|
||||
{
|
||||
|
|
@ -131,7 +146,7 @@ void ULPHairstyleSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|||
{
|
||||
Super::Initialize(Collection);
|
||||
ScanFiles();
|
||||
if (Defs.Num() == 0 && BaseDefs.Num() == 0)
|
||||
if (Defs.Num() == 0 && BaseDefs.Num() == 0 && Textures.Num() == 0 && BaseTextures.Num() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -190,8 +205,8 @@ void ULPHairstyleSubsystem::ScanFiles()
|
|||
}
|
||||
if (Files.Num() > 0)
|
||||
{
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Coiffures : %d fichier(s) .lphair, base %s (%d coiffure(s), %d morph(s)), %d coiffure(s) de modules"),
|
||||
Files.Num(), BaseFile.IsEmpty() ? TEXT("du Blueprint") : *BaseFile, BaseDefs.Num(), BaseMorphs.Num(), Defs.Num());
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Coiffures : %d fichier(s) .lphair, base %s (%d coiffure(s), %d morph(s), %d pilosite(s)), modules : %d coiffure(s), %d pilosite(s)"),
|
||||
Files.Num(), BaseFile.IsEmpty() ? TEXT("du Blueprint") : *BaseFile, BaseDefs.Num(), BaseMorphs.Num(), BaseTextures.Num(), Defs.Num(), Textures.Num());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +214,7 @@ void ULPHairstyleSubsystem::ParseFile(const FString& Relative, const FString& Mo
|
|||
{
|
||||
TArray<FHairDef> FileDefs;
|
||||
TArray<FMorphDef> FileMorphs;
|
||||
TArray<FTextureDef> FileTextures;
|
||||
bool bBase = false;
|
||||
TArray<FString> Lines;
|
||||
Text.ParseIntoArrayLines(Lines, true);
|
||||
|
|
@ -238,6 +254,10 @@ void ULPHairstyleSubsystem::ParseFile(const FString& Relative, const FString& Mo
|
|||
FileMorphs.Add({ Parts[0], Parts.IsValidIndex(1) ? Parts[1] : FString(), Parts.IsValidIndex(2) ? Parts[2] : FString() });
|
||||
}
|
||||
}
|
||||
else if (const FTextureList* List = Algo::FindByPredicate(TextureLists, [&Key](const FTextureList& L) { return Key == L.Key; }))
|
||||
{
|
||||
FileTextures.Add({ int32(List - TextureLists), Value, Relative, Module });
|
||||
}
|
||||
else if (FileDefs.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogLifePlay, Warning, TEXT("Coiffures : %s, ligne '%s' avant toute ligne HAIRSTYLE, ignoree"), *Relative, *Line);
|
||||
|
|
@ -266,13 +286,15 @@ void ULPHairstyleSubsystem::ParseFile(const FString& Relative, const FString& Mo
|
|||
BaseFile = Relative;
|
||||
BaseDefs = MoveTemp(FileDefs);
|
||||
BaseMorphs = MoveTemp(FileMorphs);
|
||||
BaseTextures = MoveTemp(FileTextures);
|
||||
return;
|
||||
}
|
||||
if (FileDefs.Num() == 0)
|
||||
if (FileDefs.Num() == 0 && FileTextures.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogLifePlay, Warning, TEXT("Coiffures : %s sans ligne HAIRSTYLE, ignore"), *Relative);
|
||||
}
|
||||
Defs.Append(MoveTemp(FileDefs));
|
||||
Textures.Append(MoveTemp(FileTextures));
|
||||
}
|
||||
|
||||
FString ULPHairstyleSubsystem::EnabledSignature() const
|
||||
|
|
@ -433,4 +455,75 @@ void ULPHairstyleSubsystem::Sync()
|
|||
}
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Coiffures : base %d entree(s) (%s), %d ajoutee(s) par les modules, %d ignoree(s), table de %d entree(s), %d morph(s)"),
|
||||
BaseCount, BaseFile.IsEmpty() ? TEXT("Blueprint") : *BaseFile, Added, Skipped, Table.Num(), Map.Num());
|
||||
SyncTextureLists(GI);
|
||||
}
|
||||
|
||||
void ULPHairstyleSubsystem::SyncTextureLists(UObject* GI)
|
||||
{
|
||||
static const bool bVerify = FParse::Param(FCommandLine::Get(), TEXT("LPVerifyHairstyles"));
|
||||
IAssetRegistry& Registry = IAssetRegistry::GetChecked();
|
||||
const bool bFirst = BlueprintListTexts.Num() == 0;
|
||||
for (int32 ListIndex = 0; ListIndex < UE_ARRAY_COUNT(TextureLists); ++ListIndex)
|
||||
{
|
||||
const FTextureList& Spec = TextureLists[ListIndex];
|
||||
const FArrayProperty* Prop = CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(Spec.Property));
|
||||
if (!Prop || !CastField<FStrProperty>(Prop->Inner))
|
||||
{
|
||||
UE_LOG(LogLifePlay, Error, TEXT("Pilosite : GI.%s introuvable ou de type inattendu"), Spec.Property);
|
||||
if (bFirst)
|
||||
{
|
||||
BlueprintListTexts.Add(FString());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
TArray<FString>& List = *Prop->ContainerPtrToValuePtr<TArray<FString>>(GI);
|
||||
if (bFirst)
|
||||
{
|
||||
BlueprintListTexts.Add(ExportProperty(Prop, GI));
|
||||
}
|
||||
|
||||
// 1. Base : noms du fichier de base pour cette liste, sinon valeur du Blueprint.
|
||||
TArray<FString> Base;
|
||||
for (const FTextureDef& Def : BaseTextures)
|
||||
{
|
||||
if (Def.List == ListIndex)
|
||||
{
|
||||
Base.Add(Def.Name);
|
||||
}
|
||||
}
|
||||
if (Base.Num() > 0)
|
||||
{
|
||||
List = Base;
|
||||
if (bVerify)
|
||||
{
|
||||
const bool bSame = ExportProperty(Prop, GI) == BlueprintListTexts[ListIndex];
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Pilosite, verification de la base : GI.%s %s (%d noms)"), Spec.Property,
|
||||
bSame ? TEXT("IDENTIQUE au Blueprint") : TEXT("DIFFERENTE du Blueprint"), List.Num());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Prop->ImportText_Direct(*BlueprintListTexts[ListIndex], &List, GI, PPF_None);
|
||||
}
|
||||
const int32 BaseCount = List.Num();
|
||||
|
||||
// 2. Noms des modules actifs : texture presente dans le dossier impose, pas de doublon.
|
||||
for (const FTextureDef& Def : Textures)
|
||||
{
|
||||
if (Def.List != ListIndex || !IsModuleEnabled(Def.Module) || List.Contains(Def.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FString Path = FString(Spec.Folder) + Def.Name + TEXT(".") + Def.Name;
|
||||
const FAssetData Asset = Registry.GetAssetByObjectPath(FSoftObjectPath(Path));
|
||||
if (!Asset.IsValid() || !Asset.IsInstanceOf(UTexture2D::StaticClass()))
|
||||
{
|
||||
UE_LOG(LogLifePlay, Warning, TEXT("Pilosite : %s introuvable ou n'est pas une texture (%s), ignore"), *Path, *Def.File);
|
||||
continue;
|
||||
}
|
||||
List.Add(Def.Name);
|
||||
UE_LOG(LogLifePlay, Log, TEXT("Pilosite : %s ajoute a GI.%s (module %s)"), *Def.Name, Spec.Property, *Def.Module);
|
||||
}
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Pilosite : GI.%s, base %d nom(s), %d ajoute(s) par les modules"), Spec.Property, BaseCount, List.Num() - BaseCount);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@
|
|||
// MODE: BASE fichier de base (H2) : la table est reconstruite a partir de
|
||||
// ce fichier, dans son ordre, avant l'ajout des autres modules
|
||||
//
|
||||
// Pilosite (textures appliquees a la peau par Person.setBHairstyle, lot H2b) : listes de noms de textures, dans le
|
||||
// dossier impose par le Blueprint (prefixe + nom) :
|
||||
// FACIAL_HAIR: Facial_Hair__1_ GI.FHairstyles (barbe ; /Game/Heavy/Daz/Michael/hairs/other/)
|
||||
// BODY_HAIR_M: JPBH78-TorsoNG-01 GI.BHairstyles_M (torse masculin ; meme dossier)
|
||||
// BODY_HAIR_F: Pubes01 GI.BHairstyles (pubis feminin ; /Game/Heavy/Daz/Bridget/Hairs/Pubic/)
|
||||
// Le fichier de base reconstruit chaque liste qu'il decrit ; les autres modules y ajoutent des noms (texture verifiee).
|
||||
//
|
||||
// Sans fichier de base, les coiffures sont ajoutees en fin de la table du Blueprint (H1). Les coiffures des modules
|
||||
// sont controlees (SkeletalMesh present, pas de doublon) ; celles du fichier de base sont reprises telles quelles,
|
||||
// comme dans le Blueprint (35 coiffures "legacy" absentes du projet depuis 4.27 y figurent).
|
||||
|
|
@ -51,6 +58,14 @@ private:
|
|||
FString FitValue;
|
||||
};
|
||||
|
||||
struct FTextureDef
|
||||
{
|
||||
int32 List = 0;
|
||||
FString Name;
|
||||
FString File;
|
||||
FString Module;
|
||||
};
|
||||
|
||||
struct FMorphDef
|
||||
{
|
||||
FString Key;
|
||||
|
|
@ -64,6 +79,8 @@ private:
|
|||
void ParseFile(const FString& Relative, const FString& Module, const FString& Text);
|
||||
/** Reconstruit la table : base (fichier MODE: BASE ou Blueprint) puis coiffures des modules actifs. */
|
||||
void Sync();
|
||||
/** Listes de pilosite : base (fichier ou Blueprint) puis noms des modules actifs. */
|
||||
void SyncTextureLists(UObject* GI);
|
||||
FString EnabledSignature() const;
|
||||
bool IsModuleEnabled(const FString& Module) const;
|
||||
|
||||
|
|
@ -76,6 +93,10 @@ private:
|
|||
TArray<FMorphDef> BaseMorphs;
|
||||
/** Coiffures des autres fichiers. */
|
||||
TArray<FHairDef> Defs;
|
||||
/** Noms de textures de pilosite : fichier de base et autres fichiers ; listes du Blueprint (texte exporte). */
|
||||
TArray<FTextureDef> BaseTextures;
|
||||
TArray<FTextureDef> Textures;
|
||||
TArray<FString> BlueprintListTexts;
|
||||
/** Table et morphs du Blueprint, captures au premier passage (texte exporte, pour revenir a la base sans fichier). */
|
||||
FString BlueprintTableText;
|
||||
FString BlueprintMorphsText;
|
||||
|
|
|
|||
Loading…
Reference in a new issue