namespace AlienRace
{
    using HarmonyLib;
    using RimWorld;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using UnityEngine;
    using Verse;
    using System.Xml;
    using ExtendedGraphics;
    using JetBrains.Annotations;

    public class ThingDef_AlienRace : ThingDef
    {
        public AlienSettings alienRace;

        public override void ResolveReferences()
        {
            if(!this.HasComp<AlienPartGenerator.AlienComp>())
                this.comps.Add(new CompProperties(typeof(AlienPartGenerator.AlienComp)));
            base.ResolveReferences();

            if (this.alienRace.generalSettings.alienPartGenerator.customHeadDrawSize.Equals(Vector2.zero))
                this.alienRace.generalSettings.alienPartGenerator.customHeadDrawSize = this.alienRace.generalSettings.alienPartGenerator.customDrawSize;
            if (this.alienRace.generalSettings.alienPartGenerator.customPortraitHeadDrawSize.Equals(Vector2.zero))
                this.alienRace.generalSettings.alienPartGenerator.customPortraitHeadDrawSize = this.alienRace.generalSettings.alienPartGenerator.customPortraitDrawSize;
            if (this.alienRace.generalSettings.alienPartGenerator.headFemaleOffset.Equals(Vector2.negativeInfinity))
                this.alienRace.generalSettings.alienPartGenerator.headFemaleOffset = this.alienRace.generalSettings.alienPartGenerator.headOffset;
            this.alienRace.generalSettings.alienPartGenerator.headFemaleOffsetDirectional ??= this.alienRace.generalSettings.alienPartGenerator.headOffsetDirectional;

            this.alienRace.generalSettings.alienPartGenerator.alienProps = this;

            foreach (Type type in typeof(StyleItemDef).AllSubclassesNonAbstract())
                if(!this.alienRace.styleSettings.ContainsKey(type))
                    this.alienRace.styleSettings.Add(type, new StyleSettings());

            foreach (AlienPartGenerator.BodyAddon bodyAddon in this.alienRace.generalSettings.alienPartGenerator.bodyAddons)
                bodyAddon.offsets.west ??= bodyAddon.offsets.east;

            if (this.alienRace.generalSettings.minAgeForAdulthood < 0)
                this.alienRace.generalSettings.minAgeForAdulthood = (float) AccessTools.Field(typeof(PawnBioAndNameGenerator), name: "MinAgeForAdulthood").GetValue(obj: null);

            foreach (StatPartAgeOverride spao in this.alienRace.generalSettings.ageStatOverrides)
                this.alienRace.generalSettings.ageStatOverride[spao.stat] = spao.overridePart;

            for (int i = 0; i < this.race.lifeStageAges.Count; i++)
            {
                LifeStageAge lsa = this.race.lifeStageAges[i];

                if (lsa is not LifeStageAgeAlien lsaa)
                {
                    lsaa = new LifeStageAgeAlien
                           {
                               def           = lsa.def,
                               minAge        = lsa.minAge,
                               soundAmbience = lsa.soundAmbience,
                               soundAngry    = lsa.soundAngry,
                               soundCall     = lsa.soundCall,
                               soundDeath    = lsa.soundDeath,
                               soundWounded  = lsa.soundWounded
                           };

                    this.race.lifeStageAges[i] = lsaa;
                }

                if (lsaa.customDrawSize.Equals(Vector2.zero))
                    lsaa.customDrawSize = this.alienRace.generalSettings.alienPartGenerator.customDrawSize;

                if (lsaa.customPortraitDrawSize.Equals(Vector2.zero))
                    lsaa.customPortraitDrawSize = this.alienRace.generalSettings.alienPartGenerator.customPortraitDrawSize;

                if (lsaa.customHeadDrawSize.Equals(Vector2.zero))
                    lsaa.customHeadDrawSize = this.alienRace.generalSettings.alienPartGenerator.customHeadDrawSize;

                if (lsaa.customPortraitHeadDrawSize.Equals(Vector2.zero))
                    lsaa.customPortraitHeadDrawSize = this.alienRace.generalSettings.alienPartGenerator.customPortraitHeadDrawSize;

                if (lsaa.customFemaleDrawSize.Equals(Vector2.zero))
                    lsaa.customFemaleDrawSize = this.alienRace.generalSettings.alienPartGenerator.customFemaleDrawSize;

                if (lsaa.customFemalePortraitDrawSize.Equals(Vector2.zero))
                    lsaa.customFemalePortraitDrawSize = this.alienRace.generalSettings.alienPartGenerator.customFemalePortraitDrawSize;

                if (lsaa.customFemaleHeadDrawSize.Equals(Vector2.zero))
                    lsaa.customFemaleHeadDrawSize = this.alienRace.generalSettings.alienPartGenerator.customFemaleHeadDrawSize;

                if (lsaa.customFemalePortraitHeadDrawSize.Equals(Vector2.zero))
                    lsaa.customFemalePortraitHeadDrawSize = this.alienRace.generalSettings.alienPartGenerator.customFemalePortraitHeadDrawSize;

                if (lsaa.headOffset.Equals(Vector2.zero))
                    lsaa.headOffset = this.alienRace.generalSettings.alienPartGenerator.headOffset;

                if (lsaa.headFemaleOffset.Equals(Vector2.negativeInfinity))
                    lsaa.headFemaleOffset = this.alienRace.generalSettings.alienPartGenerator.headFemaleOffset;

                lsaa.headOffsetDirectional ??= this.alienRace.generalSettings.alienPartGenerator.headOffsetDirectional;
                lsaa.headOffsetDirectional.west ??= lsaa.headOffsetDirectional.east;

                lsaa.headFemaleOffsetDirectional ??= this.alienRace.generalSettings.alienPartGenerator.headFemaleOffsetDirectional;
                lsaa.headFemaleOffsetDirectional.west ??= lsaa.headFemaleOffsetDirectional.east;
            }

            //if (this.alienRace.graphicPaths.body.path == GraphicPaths.VANILLA_BODY_PATH && !this.alienRace.graphicPaths.body.GetSubGraphics().MoveNext())
                //this.alienRace.graphicPaths.body.debug = false;

            if (this.alienRace.graphicPaths.head.path == GraphicPaths.VANILLA_HEAD_PATH && !this.alienRace.graphicPaths.head.GetSubGraphics().Any())
            {
                foreach (HeadTypeDef headType in DefDatabase<HeadTypeDef>.AllDefs)
                {
                    AlienPartGenerator.ExtendedConditionGraphic headtypeGraphic = new()
                                                                                 {
                                                                                     conditions = [new ConditionHeadType {headType = headType}],
                                                                                     path = headType.graphicPath
                                                                                 };

                    this.alienRace.graphicPaths.head.extendedGraphics.Add(headtypeGraphic);
                    //this.alienRace.graphicPaths.head.debug = false;
                }
            }

            if (this.alienRace.graphicPaths.skeleton.path == GraphicPaths.VANILLA_SKELETON_PATH && !this.alienRace.graphicPaths.skeleton.GetSubGraphics().Any())
            {
                this.alienRace.graphicPaths.skeleton.path             = string.Empty;

                foreach (BodyTypeDef bodyType in this.alienRace.generalSettings.alienPartGenerator.bodyTypes)
                    this.alienRace.graphicPaths.skeleton.extendedGraphics.Add(new AlienPartGenerator.ExtendedConditionGraphic()
                                                                              {
                                                                                  conditions = [new ConditionBodyType { bodyType = bodyType }],
                                                                                  path       = bodyType.bodyDessicatedGraphicPath
                                                                              });
            }
            
            void RecursiveAttributeCheck(Type type, Traverse instance, string debug)
            {
                if (type == typeof(ThingDef_AlienRace))
                    return;
                try
                {
                    debug += ".";
                    string debugBackup = debug;

                    foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                    {
                        debug = debugBackup;
                        debug += $"({field.FieldType.FullName}) {field.Name}";

                        Traverse instanceNew = instance.Field(field.Name);

                        if (typeof(IList).IsAssignableFrom(field.FieldType))
                        {
                            object value = instanceNew.GetValue();
                            if (value != null)
                                foreach (object o in (IList)value)
                                {
                                    if (o.GetType().Assembly == typeof(ThingDef_AlienRace).Assembly)
                                        RecursiveAttributeCheck(o.GetType(), Traverse.Create(o), debug);
                                }
                        }

                        if (field.FieldType.Assembly == typeof(ThingDef_AlienRace).Assembly)
                            RecursiveAttributeCheck(field.FieldType, instanceNew, debug);

                        LoadDefFromField attribute = field.GetCustomAttribute<LoadDefFromField>();
                        if (attribute != null)
                            if (instanceNew.GetValue() == null)
                                instanceNew.SetValue(attribute.defName == "this" ? this : attribute.GetDef(field.FieldType));
                    }
                }
                catch (InvalidOperationException ex)
                {
                    Log.Error($"RecursiveAttribute Error: {debug}\n{ex}");
                }
            }
            RecursiveAttributeCheck(typeof(AlienSettings), Traverse.Create(this.alienRace), this.defName);


            foreach (ThingDef bedDef in this.alienRace.generalSettings.validBeds)
                GeneralSettings.lockedBeds.Add(bedDef);

            foreach (ThoughtDef thoughtDef in this.alienRace.thoughtSettings.restrictedThoughts)
            {
                if (!ThoughtSettings.thoughtRestrictionDict.ContainsKey(thoughtDef))
                    ThoughtSettings.thoughtRestrictionDict.Add(thoughtDef, []);
                ThoughtSettings.thoughtRestrictionDict[thoughtDef].Add(this);
            }

            foreach (ThingDef thingDef in this.alienRace.raceRestriction.apparelList)
            {
                RaceRestrictionSettings.apparelRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whiteApparelList.Add(thingDef);
            }

            foreach (ThingDef thingDef in this.alienRace.raceRestriction.weaponList)
            {
                RaceRestrictionSettings.weaponRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whiteWeaponList.Add(thingDef);
            }

            foreach (BuildableDef thingDef in this.alienRace.raceRestriction.buildingList)
            {
                RaceRestrictionSettings.buildingRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whiteBuildingList.Add(thingDef);
            }

            foreach (RecipeDef recipeDef in this.alienRace.raceRestriction.recipeList)
            {
                RaceRestrictionSettings.recipeRestricted.Add(recipeDef);
                this.alienRace.raceRestriction.whiteRecipeList.Add(recipeDef);
            }

            foreach (ThingDef thingDef in this.alienRace.raceRestriction.plantList)
            {
                RaceRestrictionSettings.plantRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whitePlantList.Add(thingDef);
            }

            foreach (TraitDef traitDef in this.alienRace.raceRestriction.traitList)
            {
                RaceRestrictionSettings.traitRestricted.Add(traitDef);
                this.alienRace.raceRestriction.whiteTraitList.Add(traitDef);
            }

            foreach (ThingDef thingDef in this.alienRace.raceRestriction.foodList)
            {
                RaceRestrictionSettings.foodRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whiteFoodList.Add(thingDef);
            }

            foreach (ThingDef thingDef in this.alienRace.raceRestriction.petList)
            {
                RaceRestrictionSettings.petRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whitePetList.Add(thingDef);
            }

            foreach (ResearchProjectDef projectDef in this.alienRace.raceRestriction.researchList.SelectMany(selector: rl => rl?.projects))
            {
                if (!RaceRestrictionSettings.researchRestrictionDict.ContainsKey(projectDef))
                    RaceRestrictionSettings.researchRestrictionDict.Add(projectDef, []);
                RaceRestrictionSettings.researchRestrictionDict[projectDef].Add(this);
            }

            foreach (GeneDef geneDef in this.alienRace.raceRestriction.geneList)
            {
                RaceRestrictionSettings.geneRestricted.Add(geneDef);
                this.alienRace.raceRestriction.whiteGeneList.Add(geneDef);
            }

            foreach (GeneDef geneDef in this.alienRace.raceRestriction.geneListEndo)
            {
                RaceRestrictionSettings.geneRestrictedEndo.Add(geneDef);
                this.alienRace.raceRestriction.whiteGeneListEndo.Add(geneDef);
            }

            foreach (GeneDef geneDef in this.alienRace.raceRestriction.geneListXeno)
            {
                RaceRestrictionSettings.geneRestrictedXeno.Add(geneDef);
                this.alienRace.raceRestriction.whiteGeneListXeno.Add(geneDef);
            }

            foreach (XenotypeDef xenotypeDef in this.alienRace.raceRestriction.xenotypeList)
            {
                RaceRestrictionSettings.xenotypeRestricted.Add(xenotypeDef);
                this.alienRace.raceRestriction.whiteXenotypeList.Add(xenotypeDef);
            }

            foreach (ThingDef thingDef in this.alienRace.raceRestriction.reproductionList)
            {
                RaceRestrictionSettings.reproductionRestricted.Add(thingDef);
                this.alienRace.raceRestriction.whiteReproductionList.Add(thingDef);
            }

            if (this.race.hasCorpse && this.alienRace.generalSettings.corpseCategory != ThingCategoryDefOf.CorpsesHumanlike)
            {
                ThingCategoryDefOf.CorpsesHumanlike.childThingDefs.Remove(this.race.corpseDef);
                if (this.alienRace.generalSettings.corpseCategory != null)
                {
                    this.race.corpseDef.thingCategories = [this.alienRace.generalSettings.corpseCategory];
                    this.alienRace.generalSettings.corpseCategory.childThingDefs.Add(this.race.corpseDef);
                    this.alienRace.generalSettings.corpseCategory.ResolveReferences();
                }
                ThingCategoryDefOf.CorpsesHumanlike.ResolveReferences();
            }

            this.alienRace.generalSettings.alienPartGenerator.GenerateMeshsAndMeshPools();

            if (this.alienRace.generalSettings.humanRecipeImport && this != ThingDefOf.Human)
            {
                (this.recipes ??= []).AddRange(ThingDefOf.Human.recipes.Where(rd => !rd.targetsBodyPart                      ||
                                                                                    rd.appliedOnFixedBodyParts.NullOrEmpty() ||
                                                                                    rd.appliedOnFixedBodyParts.Any(bpd => this.race.body.AllParts.Any(bpr => bpr.def == bpd))));

                DefDatabase<RecipeDef>.AllDefsListForReading.ForEach(rd =>
                {
                    if (rd.recipeUsers?.Contains(ThingDefOf.Human) ?? false)
                        rd.recipeUsers.Add(this);
                    if (!rd.defaultIngredientFilter?.Allows(ThingDefOf.Meat_Human) ?? false)
                        rd.defaultIngredientFilter.SetAllow(this.race.meatDef, allow: false);
                });
                this.recipes.RemoveDuplicates();
            }
        }

        public class AlienSettings
        {
            public GeneralSettings                 generalSettings  = new();
            public GraphicPaths                    graphicPaths     = new();
            public Dictionary<Type, StyleSettings> styleSettings    = new();
            public ThoughtSettings                 thoughtSettings  = new();
            public RelationSettings                relationSettings = new();
            public RaceRestrictionSettings         raceRestriction  = new();
            public CompatibilityInfo               compatibility    = new();
        }
    }

    public class GeneralSettings
    {
        public float maleGenderProbability = 0.5f;
        public bool immuneToAge = false;
        public bool canLayDown = true;
        public float minAgeForAdulthood = -1f;

        public        List<ThingDef>    validBeds = [];
        public static HashSet<ThingDef> lockedBeds = [];

        public bool CanUseBed(ThingDef bedDef) =>
            this.validBeds.Contains(bedDef) ||
            (this.validBeds.NullOrEmpty() &&
             !lockedBeds.Contains(bedDef));

        public List<ChemicalSettings> chemicalSettings;

        public bool CanUseChemical(ChemicalDef chemical)
        {
            if (this.chemicalSettings.NullOrEmpty() || chemical == null)
                return true;

            foreach (ChemicalSettings cs in this.chemicalSettings)
                if (cs.chemical == chemical && !cs.ingestible)
                    return false;

            return true;
        }

        public List<AlienChanceEntry<TraitWithDegree>>   forcedRaceTraitEntries;
        public List<AlienChanceEntry<TraitWithDegree>>   disallowedTraits;

        public IntRange                           additionalTraits   = IntRange.Zero;
        public AlienPartGenerator                 alienPartGenerator = new();
        public List<SkillGain>                    passions           = new();
        public List<AlienChanceEntry<AbilityDef>> abilities          = new();

        public List<FactionRelationSettings> factionRelations        = [];
        public int                           maxDamageForSocialfight = int.MaxValue;
        public bool                          allowHumanBios          = false;
        public bool                          immuneToXenophobia      = false;
        public List<ThingDef>                notXenophobistTowards   = new();
        public bool                          humanRecipeImport       = false;

        [LoadDefFromField(nameof(AlienDefOf.HAR_AlienCorpseCategory))]
        public ThingCategoryDef corpseCategory;

        public SimpleCurve lovinIntervalHoursFromAge;
        public List<int>   growthAges = new() { 7, 10, 13 };
        public int[]       GrowthAges => this.growthAges?.ToArray();

        public SimpleCurve growthFactorByAge;
        public SimpleCurve ageSkillFactorCurve;

        public List<BackstoryCategoryFilter> childBackstoryFilter;
        public List<BackstoryCategoryFilter> adultBackstoryFilter;
        public List<BackstoryCategoryFilter> adultVatBackstoryFilter;
        public List<BackstoryCategoryFilter> newbornBackstoryFilter;

        public ReproductionSettings reproduction = new();

        public List<AlienChanceEntry<GeneDef>> raceGenes = new();

        internal List<StatPartAgeOverride> ageStatOverrides = [];

        [Unsaved]
        public Dictionary<StatDef, StatPart_Age> ageStatOverride = [];

        public List<MeditationFocusDef> meditationFocii = [];
    }

    public class ReproductionSettings
    {
        public PawnKindDef childKindDef;

        public SimpleCurve maleFertilityAgeFactor = new(new[]
                                                        {
                                                            new CurvePoint(14, 0),
                                                            new CurvePoint(18, 1),
                                                            new CurvePoint(50, 1),
                                                            new CurvePoint(90, 0)
                                                        });
        public SimpleCurve femaleFertilityAgeFactor = new(new[]
                                                          {
                                                              new CurvePoint(14, 0),
                                                              new CurvePoint(20, 1),
                                                              new CurvePoint(28, 1),
                                                              new CurvePoint(35, 0.5f),
                                                              new CurvePoint(40, 0.1f),
                                                              new CurvePoint(45, 0.02f),
                                                              new CurvePoint(50, 0),
                                                          });

        public List<HybridSpecificSettings> hybridSpecific = new();

        public GenderPossibility fertilizingGender   = GenderPossibility.Male;
        public GenderPossibility gestatingGender = GenderPossibility.Female;

        public static bool ApplicableGender(Pawn pawn, bool gestating)
        {
            ReproductionSettings reproduction = (pawn.def as ThingDef_AlienRace)?.alienRace.generalSettings.reproduction ?? new ReproductionSettings();
            return ApplicableGender(pawn.gender, reproduction, gestating);
        }

        public static bool ApplicableGender(Gender gender, ReproductionSettings reproduction, bool gestating) =>
            gestating switch
            {
                true when reproduction.gestatingGender.IsGenderApplicable(gender) => true,
                false when reproduction.fertilizingGender.IsGenderApplicable(gender) => true,
                _ => false
            };

        public static bool GenderReproductionCheck(Pawn pawn, Pawn partnerPawn)
        {
            ReproductionSettings pawnReproduction    = (pawn.def as ThingDef_AlienRace)?.alienRace.generalSettings.reproduction        ?? new ReproductionSettings();
            ReproductionSettings partnerReproduction = (partnerPawn.def as ThingDef_AlienRace)?.alienRace.generalSettings.reproduction ?? new ReproductionSettings();

            return (ApplicableGender(pawn.gender, pawnReproduction, false) &&
                    ApplicableGender(partnerPawn.gender, partnerReproduction, true)) ||
                   (ApplicableGender(pawn.gender,        pawnReproduction,    true) &&
                    ApplicableGender(partnerPawn.gender, partnerReproduction, false));
        }
    }

    public class HybridSpecificSettings
    {
        public ThingDef    partnerRace;
        public float       probability = 100;
        public PawnKindDef childKindDef;
    }

    public class FactionRelationSettings
    {
        public List<FactionDef> factions;
        public IntRange goodwill;
    }

    public class ChemicalSettings
    {
        public ChemicalDef chemical;
        public bool ingestible = true;
        public List<IngestionOutcomeDoer> reactions;
    }

    public class AlienChanceEntry<T>
    {
        [LoadAlias("defName")]
        public T entry;
        public List<AlienChanceEntry<T>> options = [];
        public int count = 1;
        public float chance = 100;

        public float commonalityMale = -1f;
        public float commonalityFemale = -1f;

        [Unsaved]
        private readonly List<AlienChanceEntry<T>> shuffledOptions = [];

        public bool Approved() =>
            Rand.Range(0, 100) < this.chance;

        public bool Approved(Gender gender) =>
            (gender == Gender.Male   && (this.commonalityMale   < 0 || Rand.Range(0, 100) < this.commonalityMale)   ||
             gender == Gender.Female && (this.commonalityFemale < 0 || Rand.Range(0, 100) < this.commonalityFemale) ||
             gender == Gender.None) && 
            this.Approved();

        public bool Approved(Pawn pawn) =>
            this.Approved(pawn.gender);

        public IEnumerable<T> Select(Pawn pawn)
        {
            if (pawn != null)
            {
                if (!this.Approved(pawn))
                    yield break;
            } else if (!this.Approved())
            {
                yield break;
            }

            if (!Equals(this.entry, default(T))) 
                yield return this.entry;

            // Doing this instead of GenCollection.TakeRandom because TakeRandom allows repeats
            if (this.shuffledOptions.Count != this.options.Count)
            {
                this.shuffledOptions.Clear();
                this.shuffledOptions.AddRange(this.options);
            }

            this.shuffledOptions.Shuffle();
            int limit = Math.Min(this.shuffledOptions.Count, this.count);
            for (int i = 0; i < limit; i ++)
            {
                foreach (T entryInner in this.shuffledOptions[i].Select(pawn))
                    yield return entryInner;
            }
        }

        [UsedImplicitly]
        public void LoadDataFromXmlCustom(XmlNode xmlRoot)
        {
            if (xmlRoot.ChildNodes.Count == 1 && xmlRoot.FirstChild.NodeType == XmlNodeType.Text)
            {
                if(typeof(T).IsSubclassOf(typeof(Def)))
                    DirectXmlCrossRefLoader.RegisterObjectWantsCrossRef(this, nameof(this.entry), xmlRoot.FirstChild.Value);
                else
                    Utilities.SetFieldFromXmlNode(Traverse.Create(this), xmlRoot, this, nameof(this.entry));
            }
            else
            {
                Traverse traverse = Traverse.Create(this);
                foreach (XmlNode xmlNode in xmlRoot.ChildNodes) 
                    Utilities.SetFieldFromXmlNode(traverse, xmlNode, this, xmlNode.Name == "defName" ? nameof(this.entry) : xmlNode.Name);
            }
        }
    }

    public class TraitWithDegree
    {
        public TraitDef def;
        public int      degree = 0;

        [UsedImplicitly]
        public void LoadDataFromXmlCustom(XmlNode xmlRoot)
        {
            DirectXmlCrossRefLoader.RegisterObjectWantsCrossRef(this, nameof(this.def), xmlRoot?.FirstChild?.Value ?? xmlRoot?.Value ?? xmlRoot?.InnerText);
            int.TryParse(xmlRoot.Attributes?["Degree"]?.Value, out this.degree);
        }

        public override string ToString() => $"{nameof(TraitWithDegree)}: {this.def?.defName} | {this.degree}";
    }

    public class GraphicPaths
    {
        public const string VANILLA_HEAD_PATH     = "Things/Pawn/Humanlike/Heads/";
        public const string VANILLA_BODY_PATH     = "Things/Pawn/Humanlike/Bodies/";
        public const string VANILLA_SKELETON_PATH = "Things/Pawn/Humanlike/HumanoidDessicated";
        
        public AlienPartGenerator.ExtendedGraphicTop body              = new() { path = VANILLA_BODY_PATH};
        public AlienPartGenerator.ExtendedGraphicTop bodyMasks         = new() { path = string.Empty };
        public AlienPartGenerator.ExtendedGraphicTop head              = new() { path = VANILLA_HEAD_PATH };
        public AlienPartGenerator.ExtendedGraphicTop headMasks         = new() { path = string.Empty };

        public AlienPartGenerator.ExtendedGraphicTop skeleton = new() { path = VANILLA_SKELETON_PATH };
        public AlienPartGenerator.ExtendedGraphicTop skull    = new() { path = "Things/Pawn/Humanlike/Heads/None_Average_Skull" };
        public AlienPartGenerator.ExtendedGraphicTop stump    = new() { path = "Things/Pawn/Humanlike/Heads/None_Average_Stump" };
        public AlienPartGenerator.ExtendedGraphicTop swaddle  = new() { path = "Things/Pawn/Humanlike/Apparel/SwaddledBaby/Swaddled_Child" };

        public ApparelGraphics.ApparelGraphicsOverrides apparel = new();

        public ShaderTypeDef   skinShader;
        public Color           skinColor = new(1f, 0f, 0f, 1f);

        private List<ShaderParameter> skinColoringParameter;
        public List<ShaderParameter> SkinColoringParameter
        {
            get
            {
                if (this.skinColoringParameter == null)
                {
                    ShaderParameter parameter = new();
                    Traverse        traverse  = Traverse.Create(parameter);
                    traverse.Field("name").SetValue("_ShadowColor");
                    traverse.Field("value").SetValue(new Vector4(this.skinColor.r, this.skinColor.g, this.skinColor.b, this.skinColor.a));
                    traverse.Field("type").SetValue(1);
                    this.skinColoringParameter = [parameter];
                }
                return this.skinColoringParameter;
            }
        }
    }

    public class DirectionOffset
    {
        public Vector2 north = Vector2.zero;
        public Vector2 west = Vector2.zero;
        public Vector2 east = Vector2.zero;
        public Vector2 south = Vector2.zero;

        public Vector2 GetOffset(Rot4 rot) => 
            rot == Rot4.North ? this.north : rot == Rot4.East ? this.east : rot == Rot4.West ? this.west : this.south;
    }

    public class StyleSettings
    {
        public bool          hasStyle        = true;
        public bool          genderRespected = true;
        public List<string>  styleTags;
        public List<string>  styleTagsOverride;
        public List<string>  bannedTags;
        public ShaderTypeDef shader;

        public bool IsValidStyle(StyleItemDef styleItemDef, Pawn pawn, bool useOverrides = false) =>
            !this.hasStyle ?
                styleItemDef.styleTags.Contains("alienNoStyle") :

                (useOverrides ?
                     this.styleTagsOverride.NullOrEmpty() || this.styleTagsOverride.Any(s => styleItemDef.styleTags.Contains(s)) :
                     this.styleTags.NullOrEmpty()         || this.styleTags.Any(s => styleItemDef.styleTags.Contains(s))) &&
                (this.bannedTags.NullOrEmpty() || !this.bannedTags.Any(s => styleItemDef.styleTags.Contains(s)))          &&

                (!this.genderRespected      ||
                 pawn.gender == Gender.None ||
                 (pawn.Ideo?.style.GetGender(styleItemDef) ?? styleItemDef.styleGender, pawn.gender) switch
                 {
                     (StyleGender.Any or StyleGender.MaleUsually or StyleGender.FemaleUsually, _) or (StyleGender.Male, Gender.Male) or (StyleGender.Female, Gender.Female) => true,
                     _ => false
                 });
    }

    public class ThoughtSettings
    {
        public List<ThoughtDef> cannotReceiveThoughts;
        public bool         cannotReceiveThoughtsAtAll = false;
        public List<ThoughtDef> canStillReceiveThoughts;

        public static Dictionary<ThoughtDef, List<ThingDef_AlienRace>> thoughtRestrictionDict = new();
        public        List<ThoughtDef>                                 restrictedThoughts     = new();

        public ThoughtDef ReplaceIfApplicable(ThoughtDef def)
        {
            if (this.replacerList == null || this.replacerList.Select(tr => tr.replacer).Contains(def))
                return def;
            
            for (int i = 0; i < this.replacerList.Count; i++)
            {
                if(this.replacerList[i].original == def)
                    return this.replacerList[i].replacer ?? def;
            }

            return def;
        }

        public ButcherThought       butcherThoughtGeneral  = new();
        public List<ButcherThought> butcherThoughtSpecific = new();

        public AteThought       ateThoughtGeneral  = new();
        public List<AteThought> ateThoughtSpecific = new();

        public ThoughtDef GetAteThought(ThingDef race, bool cannibal, bool ingredient) =>
            (this.ateThoughtSpecific?.FirstOrDefault(predicate: at => at.raceList?.Contains(race) ?? false) ?? this.ateThoughtGeneral)?.GetThought(cannibal, ingredient);

        public bool CanGetThought(ThoughtDef def)
        {
            def = this.ReplaceIfApplicable(def);

            return (!this.cannotReceiveThoughtsAtAll || (this.canStillReceiveThoughts?.Contains(def) ?? false)) &&
                   (!(this.cannotReceiveThoughts?.Contains(def) ?? false));
        }

        private static readonly Dictionary<uint, bool> canGetThoughtCache = [];

        public static bool CanGetThought(ThoughtDef def, ThingDef race)
        {
            uint key = def.shortHash | ((uint)race.shortHash << 16);

            if (!canGetThoughtCache.TryGetValue(key, out bool canGetThought))
            {
                bool result = !(thoughtRestrictionDict.TryGetValue(def, out List<ThingDef_AlienRace> races));

                canGetThoughtCache.Add(key, canGetThought = race is not ThingDef_AlienRace alienProps ?
                                                                result :
                                                                (races?.Contains(alienProps) ?? true) && alienProps.alienRace.thoughtSettings.CanGetThought(def));
            }
            return canGetThought;
        }

        public static bool CanGetThought(ThoughtDef def, Pawn pawn) => 
            CanGetThought(def, pawn.def);

        public List<ThoughtReplacer> replacerList;
    }

    public class ButcherThought
    {
        public List<ThingDef> raceList;

        [LoadDefFromField(nameof(AlienDefOf.ButcheredHumanlikeCorpse))]
        public ThoughtDef thought;

        [LoadDefFromField(nameof(AlienDefOf.KnowButcheredHumanlikeCorpse))]
        public ThoughtDef knowThought;
    }

    public class AteThought
    {
        public List<ThingDef> raceList;
        [LoadDefFromField(nameof(AlienDefOf.AteHumanlikeMeatDirect))]
        public ThoughtDef thought;

        [LoadDefFromField(nameof(AlienDefOf.AteHumanlikeMeatDirectCannibal))]
        public ThoughtDef thoughtCannibal;

        [LoadDefFromField(nameof(AlienDefOf.AteHumanlikeMeatAsIngredient))]
        public ThoughtDef ingredientThought;

        [LoadDefFromField(nameof(AlienDefOf.AteHumanlikeMeatAsIngredientCannibal))]
        public ThoughtDef ingredientThoughtCannibal;

        public ThoughtDef GetThought(bool cannibal, bool ingredient) =>
            cannibal ? ingredient ? this.ingredientThoughtCannibal : this.thoughtCannibal : ingredient ? this.ingredientThought : this.thought;
    }

    public class ThoughtReplacer
    {
        public ThoughtDef original;
        public ThoughtDef replacer;
    }

    public class RelationSettings
    {
        public float relationChanceModifierChild = 1f;
        public float relationChanceModifierExLover = 1f;
        public float relationChanceModifierExSpouse = 1f;
        public float relationChanceModifierFiance = 1f;
        public float relationChanceModifierLover = 1f;
        public float relationChanceModifierParent = 1f;
        public float relationChanceModifierSibling = 1f;
        public float relationChanceModifierSpouse = 1f;

        public List<RelationRenamer> renamer;
    }

    public class RelationRenamer
    {
        public PawnRelationDef relation;
        public string label;
        public string femaleLabel;
    }

    public class RaceRestrictionSettings
    {
        public bool           onlyUseRaceRestrictedApparel = false;
        public List<ThingDef> apparelList                  = new();
        public List<ThingDef> whiteApparelList             = new();
        public List<ThingDef> blackApparelList             = new();

        public static HashSet<ThingDef> apparelRestricted = new();

        public static bool CanWear(ThingDef apparel, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool result = true;

            if (apparelRestricted.Contains(apparel) || (raceRestriction?.onlyUseRaceRestrictedApparel ?? false)) 
                result = raceRestriction?.whiteApparelList.Contains(apparel) ?? false;

            return result && !(raceRestriction?.blackApparelList.Contains(apparel) ?? false);
        }


        public        List<ResearchProjectRestrictions>                        researchList            = new();
        public static Dictionary<ResearchProjectDef, List<ThingDef_AlienRace>> researchRestrictionDict = new();

        public static bool CanResearch(IEnumerable<ThingDef> races, ResearchProjectDef project) =>
            !researchRestrictionDict.ContainsKey(project) || races.Any(predicate: ar => researchRestrictionDict[project].Contains(ar));


        public bool           onlyUseRaceRestrictedWeapons = false;
        public List<ThingDef> weaponList                   = new();
        public List<ThingDef> whiteWeaponList              = new();
        public List<ThingDef> blackWeaponList              = new();

        public static HashSet<ThingDef>                              weaponRestricted     = new();

        public static bool CanEquip(ThingDef weapon, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (weaponRestricted.Contains(weapon) || (raceRestriction?.onlyUseRaceRestrictedWeapons ?? false))
                result = raceRestriction?.whiteWeaponList.Contains(weapon)                          ?? false;

            return result && !(raceRestriction?.blackWeaponList.Contains(weapon) ?? false);
        }

        public bool               onlyBuildRaceRestrictedBuildings = false;
        public List<BuildableDef> buildingList                     = [];
        public List<BuildableDef> whiteBuildingList                = [];
        public List<BuildableDef> blackBuildingList                = [];
        public List<BuildableDef> hiddenBuildingList               = [];

        public static readonly HashSet<BuildableDef> buildingRestricted = [];

        public static readonly HashSet<BuildableDef> buildingsRestrictedWithCurrentColony = [];

        public static bool CanColonyBuild(BuildableDef building) => 
            !buildingsRestrictedWithCurrentColony.Contains(building);

        public static bool CanBuild(BuildableDef building, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (buildingRestricted.Contains(building) || (raceRestriction?.onlyBuildRaceRestrictedBuildings ?? false))
                result = raceRestriction?.whiteBuildingList.Contains(building)                          ?? false;

            return result && !(raceRestriction?.blackBuildingList.Contains(building) ?? false);
        }




        public bool            onlyDoRaceRestrictedRecipes = false;
        public List<RecipeDef> recipeList                  = new();
        public List<RecipeDef> whiteRecipeList             = new();
        public List<RecipeDef> blackRecipeList             = new();

        public static HashSet<RecipeDef> recipeRestricted = new();

        public static bool CanDoRecipe(RecipeDef recipe, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (recipeRestricted.Contains(recipe) || (raceRestriction?.onlyDoRaceRestrictedRecipes ?? false))
                result = raceRestriction?.whiteRecipeList.Contains(recipe)                              ?? false;

            return result && !(raceRestriction?.blackRecipeList.Contains(recipe) ?? false);
        }

        public bool           onlyDoRaceRestrictedPlants = false;
        public List<ThingDef> plantList                  = new();
        public List<ThingDef> whitePlantList             = new();
        public List<ThingDef> blackPlantList             = new();

        public static HashSet<ThingDef> plantRestricted = new();

        public static bool CanPlant(ThingDef plant, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (plantRestricted.Contains(plant) || (raceRestriction?.onlyDoRaceRestrictedPlants ?? false))
                result = raceRestriction?.whitePlantList.Contains(plant)                          ?? false;

            return result && !(raceRestriction?.blackPlantList.Contains(plant) ?? false);
        }

        public bool           onlyGetRaceRestrictedTraits = false;
        public List<TraitDef> traitList                   = new();
        public List<TraitDef> whiteTraitList              = new();
        public List<TraitDef> blackTraitList              = new();

        public static HashSet<TraitDef> traitRestricted = new();

        public static bool CanGetTrait(TraitDef trait, Pawn pawn, int degree = 0)
        {
            List<AlienChanceEntry<TraitWithDegree>> disallowedTraits = [];

            foreach (BackstoryDef backstory in pawn.story.AllBackstories)
                if (backstory is AlienBackstoryDef alienBackstory)
                    if(!alienBackstory.disallowedTraitsChance.NullOrEmpty()) 
                        disallowedTraits.AddRange(alienBackstory.disallowedTraitsChance);

            return CanGetTrait(trait, pawn.def, degree, disallowedTraits);
        }

        public static bool CanGetTrait(TraitDef trait, ThingDef race, int degree = 0, List<AlienChanceEntry<TraitWithDegree>> disallowedTraits = null)
        {
            ThingDef_AlienRace.AlienSettings           alienProps   = (race as ThingDef_AlienRace)?.alienRace;
            RaceRestrictionSettings raceRestriction = alienProps?.raceRestriction;
            bool                    result          = true;

            if (traitRestricted.Contains(trait) || (raceRestriction?.onlyGetRaceRestrictedTraits ?? false))
                result &= raceRestriction?.whiteTraitList.Contains(trait)                        ?? false;

            disallowedTraits ??= [];

            if (!(alienProps?.generalSettings.disallowedTraits.NullOrEmpty() ?? true))
                disallowedTraits.AddRange(alienProps.generalSettings.disallowedTraits);

            if (!disallowedTraits.NullOrEmpty())
                result &= disallowedTraits.All(ace => ace.Select(null).All(traitEntry => traitEntry.def != trait || degree != traitEntry.degree));
            

            return result && !(raceRestriction?.blackTraitList.Contains(trait) ?? false);
        }
        
        public bool           onlyEatRaceRestrictedFood = false;
        public List<ThingDef> foodList                  = new();
        public List<ThingDef> whiteFoodList             = new();
        public List<ThingDef> blackFoodList             = new();

        public static HashSet<ThingDef> foodRestricted = new();

        public static bool CanEat(ThingDef food, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (foodRestricted.Contains(food) || (raceRestriction?.onlyEatRaceRestrictedFood ?? false))
                result = raceRestriction?.whiteFoodList.Contains(food)                        ?? false;

            result &= !(raceRestriction?.blackFoodList.Contains(food) ?? false);

            ChemicalDef chemical = food.GetCompProperties<CompProperties_Drug>()?.chemical;
            return result && (chemical == null || ((race as ThingDef_AlienRace)?.alienRace.generalSettings.CanUseChemical(chemical) ?? true));
        }

        public bool           onlyTameRaceRestrictedPets = false;
        public List<ThingDef> petList                    = new();
        public List<ThingDef> whitePetList               = new();
        public List<ThingDef> blackPetList               = new();

        public static HashSet<ThingDef> petRestricted = new();

        public static bool CanTame(ThingDef pet, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (petRestricted.Contains(pet) || (raceRestriction?.onlyTameRaceRestrictedPets ?? false))
                result = raceRestriction?.whitePetList.Contains(pet)                       ?? false;

            return result && !(raceRestriction?.blackPetList.Contains(pet) ?? false);
        }

        public List<ConceptDef> conceptList = new();

        public List<WorkGiverDef> workGiverList = new();

        public bool          onlyHaveRaceRestrictedGenes = false;
        public List<GeneDef> geneList                    = [];
        public List<GeneDef> whiteGeneList               = [];
        public List<string>  whiteGeneTags               = [];
        public List<GeneDef> blackGeneList               = [];
        public List<string>  blackGeneTags               = [];
        public List<EndogeneCategory> blackEndoCategories = [];

        public static HashSet<GeneDef> geneRestricted = [];

        public bool                   onlyHaveRaceRestrictedGenesXeno = false;
        public List<GeneDef>          geneListXeno                    = [];
        public List<GeneDef>          whiteGeneListXeno               = [];
        public List<string>           whiteGeneTagsXeno               = [];
        public List<GeneDef>          blackGeneListXeno               = [];
        public List<string>           blackGeneTagsXeno               = [];
        public List<EndogeneCategory> blackEndoCategoriesXeno         = [];

        public static HashSet<GeneDef> geneRestrictedXeno = [];

        public bool onlyHaveRaceRestrictedGenesEndo = false;
        public List<GeneDef> geneListEndo = [];
        public List<GeneDef> whiteGeneListEndo = [];
        public List<string> whiteGeneTagsEndo = [];
        public List<GeneDef> blackGeneListEndo = [];
        public List<string> blackGeneTagsEndo = [];
        public List<EndogeneCategory> blackEndoCategoriesEndo = [];

        public static HashSet<GeneDef> geneRestrictedEndo = [];

        public static bool CanHaveGene(GeneDef gene, ThingDef race, bool xeno)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (geneRestricted.Contains(gene) || (raceRestriction?.onlyHaveRaceRestrictedGenes ?? false))
                result = (raceRestriction?.whiteGeneList.Contains(gene)                                           ?? false) ||
                         (gene.exclusionTags?.Any(t => raceRestriction?.whiteGeneTags.Any(t.StartsWith) ?? false) ?? false);

            if (xeno)
            {
                if (geneRestrictedXeno.Contains(gene) || (raceRestriction?.onlyHaveRaceRestrictedGenesXeno ?? false))
                    result &= (raceRestriction?.whiteGeneListXeno.Contains(gene)                                           ?? false) ||
                              (gene.exclusionTags?.Any(t => raceRestriction?.whiteGeneTagsXeno.Any(t.StartsWith) ?? false) ?? false);

                result &= !(raceRestriction?.blackGeneListXeno.Contains(gene)                                           ?? false) &&
                          !(gene.exclusionTags?.Any(t => raceRestriction?.blackGeneTagsXeno.Any(t.StartsWith) ?? false) ?? false) &&
                          !(raceRestriction?.blackEndoCategoriesXeno.Contains(gene.endogeneCategory)                    ?? false);

            }
            else
            {
                if (geneRestrictedEndo.Contains(gene) || (raceRestriction?.onlyHaveRaceRestrictedGenesEndo ?? false))
                    result &= (raceRestriction?.whiteGeneListEndo.Contains(gene)                                           ?? false) ||
                              (gene.exclusionTags?.Any(t => raceRestriction?.whiteGeneTagsEndo.Any(t.StartsWith) ?? false) ?? false);

                result &= !(raceRestriction?.blackGeneListEndo.Contains(gene)                                           ?? false) &&
                          !(gene.exclusionTags?.Any(t => raceRestriction?.blackGeneTagsEndo.Any(t.StartsWith) ?? false) ?? false) &&
                          !(raceRestriction?.blackEndoCategoriesEndo.Contains(gene.endogeneCategory)                    ?? false);
            }

            if (gene.chemical != null) 
                result &= ((race as ThingDef_AlienRace)?.alienRace.generalSettings.CanUseChemical(gene.chemical) ?? true);

            return result                                                                                              &&
                   !(raceRestriction?.blackGeneList.Contains(gene)                                           ?? false) &&
                   !(gene.exclusionTags?.Any(t => raceRestriction?.blackGeneTags.Any(t.StartsWith) ?? false) ?? false) &&
                   !(raceRestriction?.blackEndoCategories.Contains(gene.endogeneCategory)                    ?? false);
        }

        public bool              onlyUseRaceRestrictedXenotypes = false;
        public List<XenotypeDef> xenotypeList                   = new();
        public List<XenotypeDef> whiteXenotypeList              = new();
        public List<XenotypeDef> blackXenotypeList              = new();

        public static HashSet<XenotypeDef> xenotypeRestricted = new();

        public static HashSet<XenotypeDef> FilterXenotypes(IEnumerable<XenotypeDef> xenotypes, ThingDef race, out HashSet<XenotypeDef> removedXenotypes)
        {
            HashSet<XenotypeDef> xenotypeDefs = new();
            removedXenotypes = new HashSet<XenotypeDef>();

            foreach (XenotypeDef xenotypeDef in xenotypes)
                if (CanUseXenotype(xenotypeDef, race))
                    xenotypeDefs.Add(xenotypeDef);
                else
                    removedXenotypes.Add(xenotypeDef);

            return xenotypeDefs;
        }

        public static bool CanUseXenotype(XenotypeDef xenotype, ThingDef race)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            bool                    result          = true;

            if (xenotypeRestricted.Contains(xenotype) || (raceRestriction?.onlyUseRaceRestrictedXenotypes ?? false))
                result = raceRestriction?.whiteXenotypeList.Contains(xenotype)                        ?? false;

            return result && !(raceRestriction?.blackXenotypeList.Contains(xenotype) ?? false);
        }

        public bool           canReproduce                     = true;
        public bool           canReproduceWithSelf             = true;
        public bool           onlyReproduceWithRestrictedRaces = false;
        public List<ThingDef> reproductionList                 = new();
        public List<ThingDef> whiteReproductionList            = new();
        public List<ThingDef> blackReproductionList            = new();

        public static HashSet<ThingDef> reproductionRestricted = new();

        public static bool CanReproduce(Pawn pawn, Pawn partnerPawn) =>
            ReproductionSettings.GenderReproductionCheck(pawn, partnerPawn) &&
            CanReproduce(pawn.def, partnerPawn.def);

        public static bool CanReproduce(ThingDef race, ThingDef partnerRace) => 
            CanReproduceWith(race, partnerRace) && CanReproduceWith(partnerRace, race);

        private static bool CanReproduceWith(ThingDef race, ThingDef partnerRace)
        {
            RaceRestrictionSettings raceRestriction = (race as ThingDef_AlienRace)?.alienRace.raceRestriction;
            if (!(raceRestriction?.canReproduce ?? true))
                return false;

            if (race == partnerRace)
                return raceRestriction?.canReproduceWithSelf ?? true;

            bool result = true;
            if (reproductionRestricted.Contains(partnerRace) || (raceRestriction?.onlyReproduceWithRestrictedRaces ?? false))
                result = raceRestriction?.whiteReproductionList.Contains(partnerRace)                              ?? false;

            return result && !(raceRestriction?.blackReproductionList.Contains(partnerRace) ?? false);
        }
    }

    public class ResearchProjectRestrictions
    {
        public List<ResearchProjectDef> projects = new();
        public List<ThingDef>           apparelList;
    }

    public class Info : DefModExtension
    {
        public bool allowHumanBios = true;
        public float maleGenderProbability = 0.5f;
    }

    public class LifeStageAgeAlien : LifeStageAge
    {
        public BodyDef body;

        public Vector2                              headOffset = Vector2.zero;
        public AlienPartGenerator.DirectionalOffset headOffsetDirectional;

        public Vector2                              headFemaleOffset = Vector2.negativeInfinity;
        public AlienPartGenerator.DirectionalOffset headFemaleOffsetDirectional;


        public Vector2 customDrawSize             = Vector2.zero;
        public Vector2 customPortraitDrawSize     = Vector2.zero;
        public Vector2 customHeadDrawSize         = Vector2.zero;
        public Vector2 customPortraitHeadDrawSize = Vector2.zero;

        public Vector2 customFemaleDrawSize             = Vector2.zero;
        public Vector2 customFemalePortraitDrawSize     = Vector2.zero;
        public Vector2 customFemaleHeadDrawSize         = Vector2.zero;
        public Vector2 customFemalePortraitHeadDrawSize = Vector2.zero;
    }

    public class StatPartAgeOverride
    {
        public StatDef stat;
        public StatPart_Age overridePart;

        public void LoadDataFromXmlCustom(XmlNode xmlRoot)
        {
            DirectXmlCrossRefLoader.RegisterObjectWantsCrossRef(this, "stat", xmlRoot.Name);
            this.overridePart = DirectXmlToObject.ObjectFromXml<StatPart_Age>(xmlRoot, false);
        }
    }

    public class CompatibilityInfo
    {
        protected bool isFlesh = true;

        public virtual bool IsFlesh
        {
            get => this.isFlesh;
            set => this.isFlesh = value;
        }

        public virtual bool IsFleshPawn(Pawn pawn) => this.IsFlesh;

        protected bool isSentient = true;

        public virtual bool IsSentient
        {
            get => this.isSentient;
            set => this.isSentient = value;
        }

        public virtual bool IsSentientPawn(Pawn pawn) => this.IsSentient;

        protected bool hasBlood = true;

        public virtual bool HasBlood
        {
            get => this.hasBlood;
            set => this.hasBlood = value;
        }

        public virtual bool HasBloodPawn(Pawn pawn) => this.HasBlood;
    }

    public class HARStatueContainer : IExposable
    {
        public static readonly string loadKey = typeof(HARStatueContainer).FullName!;

        public ThingDef_AlienRace           alienRace;
        public PawnKindDef                  kindDef;
        public AlienPartGenerator.AlienComp alienComp;

        public List<AlienPartGenerator.ExposableValueTuple<TraitDef, int>> traits = [];


        public void ExposeData()
        {
            Scribe_Defs.Look(ref this.alienRace, nameof(this.alienRace));
            Scribe_Defs.Look(ref this.kindDef, nameof(this.kindDef));
            Scribe_Collections.Look(ref this.traits, nameof(this.traits), LookMode.Deep);

            if (Scribe.mode == LoadSaveMode.LoadingVars)
            {
                this.traits ??= [];
                this.alienComp = Activator.CreateInstance<AlienPartGenerator.AlienComp>();
            }

            this.alienComp.PostExposeData();
        }
    }
}