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(); } } }