From f45d60c88ea6cb45140dce496348181017d12302 Mon Sep 17 00:00:00 2001 From: "ProjectKoi-Kalo\\Kalo" Date: Mon, 1 Sep 2025 10:59:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Defs/ThingDef_Races/ARA_RaceDefBase.xml | 4 +- Source/Documents/ThingDef_AlienRace.cs | 1182 +++++++++++++++++ 2 files changed, 1184 insertions(+), 2 deletions(-) create mode 100644 Source/Documents/ThingDef_AlienRace.cs diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDefBase.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDefBase.xml index 2dc1ed8..309bed8 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDefBase.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDefBase.xml @@ -206,9 +206,9 @@ - (1.75,1.75) + (4,4) (1.75,1.75) - (1.5,1.5) + (2,2) (0,0) diff --git a/Source/Documents/ThingDef_AlienRace.cs b/Source/Documents/ThingDef_AlienRace.cs new file mode 100644 index 0000000..3dc408c --- /dev/null +++ b/Source/Documents/ThingDef_AlienRace.cs @@ -0,0 +1,1182 @@ +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()) + 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.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(); + 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.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 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 validBeds = []; + public static HashSet lockedBeds = []; + + public bool CanUseBed(ThingDef bedDef) => + this.validBeds.Contains(bedDef) || + (this.validBeds.NullOrEmpty() && + !lockedBeds.Contains(bedDef)); + + public List 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> forcedRaceTraitEntries; + public List> disallowedTraits; + + public IntRange additionalTraits = IntRange.Zero; + public AlienPartGenerator alienPartGenerator = new(); + public List passions = new(); + public List> abilities = new(); + + public List factionRelations = []; + public int maxDamageForSocialfight = int.MaxValue; + public bool allowHumanBios = false; + public bool immuneToXenophobia = false; + public List notXenophobistTowards = new(); + public bool humanRecipeImport = false; + + [LoadDefFromField(nameof(AlienDefOf.HAR_AlienCorpseCategory))] + public ThingCategoryDef corpseCategory; + + public SimpleCurve lovinIntervalHoursFromAge; + public List growthAges = new() { 7, 10, 13 }; + public int[] GrowthAges => this.growthAges?.ToArray(); + + public SimpleCurve growthFactorByAge; + public SimpleCurve ageSkillFactorCurve; + + public List childBackstoryFilter; + public List adultBackstoryFilter; + public List adultVatBackstoryFilter; + public List newbornBackstoryFilter; + + public ReproductionSettings reproduction = new(); + + public List> raceGenes = new(); + + internal List ageStatOverrides = []; + + [Unsaved] + public Dictionary ageStatOverride = []; + + public List 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 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 factions; + public IntRange goodwill; + } + + public class ChemicalSettings + { + public ChemicalDef chemical; + public bool ingestible = true; + public List reactions; + } + + public class AlienChanceEntry + { + [LoadAlias("defName")] + public T entry; + public List> options = []; + public int count = 1; + public float chance = 100; + + public float commonalityMale = -1f; + public float commonalityFemale = -1f; + + [Unsaved] + private readonly List> 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 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 skinColoringParameter; + public List 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 styleTags; + public List styleTagsOverride; + public List 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 cannotReceiveThoughts; + public bool cannotReceiveThoughtsAtAll = false; + public List canStillReceiveThoughts; + + public static Dictionary> thoughtRestrictionDict = new(); + public List 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 butcherThoughtSpecific = new(); + + public AteThought ateThoughtGeneral = new(); + public List 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 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 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 replacerList; + } + + public class ButcherThought + { + public List raceList; + + [LoadDefFromField(nameof(AlienDefOf.ButcheredHumanlikeCorpse))] + public ThoughtDef thought; + + [LoadDefFromField(nameof(AlienDefOf.KnowButcheredHumanlikeCorpse))] + public ThoughtDef knowThought; + } + + public class AteThought + { + public List 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 renamer; + } + + public class RelationRenamer + { + public PawnRelationDef relation; + public string label; + public string femaleLabel; + } + + public class RaceRestrictionSettings + { + public bool onlyUseRaceRestrictedApparel = false; + public List apparelList = new(); + public List whiteApparelList = new(); + public List blackApparelList = new(); + + public static HashSet 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 researchList = new(); + public static Dictionary> researchRestrictionDict = new(); + + public static bool CanResearch(IEnumerable races, ResearchProjectDef project) => + !researchRestrictionDict.ContainsKey(project) || races.Any(predicate: ar => researchRestrictionDict[project].Contains(ar)); + + + public bool onlyUseRaceRestrictedWeapons = false; + public List weaponList = new(); + public List whiteWeaponList = new(); + public List blackWeaponList = new(); + + public static HashSet 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 buildingList = []; + public List whiteBuildingList = []; + public List blackBuildingList = []; + public List hiddenBuildingList = []; + + public static readonly HashSet buildingRestricted = []; + + public static readonly HashSet 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 recipeList = new(); + public List whiteRecipeList = new(); + public List blackRecipeList = new(); + + public static HashSet 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 plantList = new(); + public List whitePlantList = new(); + public List blackPlantList = new(); + + public static HashSet 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 traitList = new(); + public List whiteTraitList = new(); + public List blackTraitList = new(); + + public static HashSet traitRestricted = new(); + + public static bool CanGetTrait(TraitDef trait, Pawn pawn, int degree = 0) + { + List> 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> 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 foodList = new(); + public List whiteFoodList = new(); + public List blackFoodList = new(); + + public static HashSet 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()?.chemical; + return result && (chemical == null || ((race as ThingDef_AlienRace)?.alienRace.generalSettings.CanUseChemical(chemical) ?? true)); + } + + public bool onlyTameRaceRestrictedPets = false; + public List petList = new(); + public List whitePetList = new(); + public List blackPetList = new(); + + public static HashSet 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 conceptList = new(); + + public List workGiverList = new(); + + public bool onlyHaveRaceRestrictedGenes = false; + public List geneList = []; + public List whiteGeneList = []; + public List whiteGeneTags = []; + public List blackGeneList = []; + public List blackGeneTags = []; + public List blackEndoCategories = []; + + public static HashSet geneRestricted = []; + + public bool onlyHaveRaceRestrictedGenesXeno = false; + public List geneListXeno = []; + public List whiteGeneListXeno = []; + public List whiteGeneTagsXeno = []; + public List blackGeneListXeno = []; + public List blackGeneTagsXeno = []; + public List blackEndoCategoriesXeno = []; + + public static HashSet geneRestrictedXeno = []; + + public bool onlyHaveRaceRestrictedGenesEndo = false; + public List geneListEndo = []; + public List whiteGeneListEndo = []; + public List whiteGeneTagsEndo = []; + public List blackGeneListEndo = []; + public List blackGeneTagsEndo = []; + public List blackEndoCategoriesEndo = []; + + public static HashSet 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 xenotypeList = new(); + public List whiteXenotypeList = new(); + public List blackXenotypeList = new(); + + public static HashSet xenotypeRestricted = new(); + + public static HashSet FilterXenotypes(IEnumerable xenotypes, ThingDef race, out HashSet removedXenotypes) + { + HashSet xenotypeDefs = new(); + removedXenotypes = new HashSet(); + + 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 reproductionList = new(); + public List whiteReproductionList = new(); + public List blackReproductionList = new(); + + public static HashSet 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 projects = new(); + public List 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(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> 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(); + } + + this.alienComp.PostExposeData(); + } + } +} \ No newline at end of file