diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index 6a3ed5c..ab9ccd0 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -62,6 +62,7 @@ + diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 701876a..2ca84cb 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -14,4 +14,4 @@ [assembly: AssemblyVersion("1.1.1")] // Increment for each new release -[assembly: AssemblyFileVersion("1.5.7")] +[assembly: AssemblyFileVersion("1.5.8")] diff --git a/Resources/About/About.xml b/Resources/About/About.xml index 76d8d92..9c8a160 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -25,7 +25,7 @@ If you get a set of starting colonists that you like, save them as a preset so that you can start your game the same way next time. -[Version 1.5.7] +[Version 1.5.8]
  • net.pardeike.rimworld.mod.harmony
  • diff --git a/Resources/About/Manifest.xml b/Resources/About/Manifest.xml index 56ec00c..849484c 100644 --- a/Resources/About/Manifest.xml +++ b/Resources/About/Manifest.xml @@ -1,7 +1,7 @@ EdB.PrepareCarefully - 1.5.7 + 1.5.8 false https://github.com/edbmods/EdBPrepareCarefully/raw/master/Resources/About/Manifest.xml https://github.com/edbmods/EdBPrepareCarefully/releases/latest diff --git a/Resources/CHANGELOG.txt b/Resources/CHANGELOG.txt index 6b8ae45..f0c01d9 100644 --- a/Resources/CHANGELOG.txt +++ b/Resources/CHANGELOG.txt @@ -1,3 +1,17 @@ + _____________________________________________________________________________ + + Version 1.5.8 + _____________________________________________________________________________ + + - Added support for Mechanitor implants in the Health panel's Implants and + Bionics dialog + - Added Mechanoid options to the Equipment Tab + - Bug fixes: + - Fixed issue where sibling relationships were sometimes lost when loading + from a preset + - Fixed issue where non-parent/child relationships between starting pawns + and hidden/world pawns were removed when initializing the game + _____________________________________________________________________________ Version 1.5.7 diff --git a/Resources/Common/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Common/Languages/English/Keyed/EdBPrepareCarefully.xml index 46101ef..cab1bce 100644 --- a/Resources/Common/Languages/English/Keyed/EdBPrepareCarefully.xml +++ b/Resources/Common/Languages/English/Keyed/EdBPrepareCarefully.xml @@ -1,6 +1,17 @@  + + + + + + + + + + + @@ -208,6 +219,9 @@ Apply This implant cannot be installed because it conflicts with the following:\n\n- {0}, {1} Select Implants and Bionics + Implant {0} + This implant must be added because it is required by one of the other implants that you selected + {0}: Select a condition You must select a condition @@ -262,6 +276,7 @@ Apparel Furniture Food + Mechanoids Medical Resources Weapons @@ -269,10 +284,12 @@ Processing materials... Processing items... Finished + {0}: {1}% Start With Start Near Possessions - {0} Animals + Mechanoids All Categories All Mods Cost: {0} @@ -281,8 +298,11 @@ Gender: Hit Points: Material: + Mechanoids Quality: + {0}: Random Pet + Random Mechanoid Spawn Type: Style: diff --git a/Resources/Common/Textures/EdB/PrepareCarefully/Checkmark.png b/Resources/Common/Textures/EdB/PrepareCarefully/Checkmark.png new file mode 100644 index 0000000..46afc83 Binary files /dev/null and b/Resources/Common/Textures/EdB/PrepareCarefully/Checkmark.png differ diff --git a/Resources/Common/Textures/EdB/PrepareCarefully/CheckmarkForcedSelection.png b/Resources/Common/Textures/EdB/PrepareCarefully/CheckmarkForcedSelection.png new file mode 100644 index 0000000..aa03f7a Binary files /dev/null and b/Resources/Common/Textures/EdB/PrepareCarefully/CheckmarkForcedSelection.png differ diff --git a/Source/ControllerPage.cs b/Source/ControllerPage.cs index 4ca9094..2a862f2 100644 --- a/Source/ControllerPage.cs +++ b/Source/ControllerPage.cs @@ -146,13 +146,14 @@ public void PreparePawns() { Find.GameInitData.startingPossessions.Remove(key); } // Destroy any starting pawn that are not in our customized pawn list + Logger.Debug("Destroy any pawn that is not in our carefully prepared pawn list:"); foreach (var pawn in Find.GameInitData.startingAndOptionalPawns) { if (!State.Customizations.AllPawns.Select(p => p.Pawn).Contains(pawn)) { - Logger.Debug("Destroyed starting pawn: " + pawn.LabelCap); + Logger.Debug(" Destroyed starting pawn: " + pawn.LabelShort); ManagerPawns.DestroyPawn(pawn); } else { - Logger.Debug("Kept starting pawn: " + pawn.LabelCap); + Logger.Debug(" Kept starting pawn: " + pawn.LabelShort); } } Find.GameInitData.startingPawnCount = State.Customizations.ColonyPawns.Count; @@ -189,10 +190,7 @@ public void PrepareEquipment() { // Add scenario parts for all of our customized equipment selections foreach (var equipment in State.Customizations.Equipment) { - ScenPart part = CreateScenarioPartForCustomizedEquipment(equipment); - if (part != null) { - scenarioPartsToUse.Add(part); - } + scenarioPartsToUse.AddRange(CreateScenarioPartForCustomizedEquipment(equipment).Where(p => p != null)); } ReflectionUtil.SetFieldValue(Find.Scenario, "parts", scenarioPartsToUse); @@ -207,23 +205,26 @@ public bool ShouldReplaceScenarioPart(ScenPart part) { return false; } - public ScenPart CreateScenarioPartForCustomizedEquipment(CustomizedEquipment equipment) { - Logger.Debug(string.Format("AddScenarioPartForCustomizedEquipment({0}), Animal = {1}", equipment.EquipmentOption?.ThingDef?.defName, equipment.Animal)); + public IEnumerable CreateScenarioPartForCustomizedEquipment(CustomizedEquipment equipment) { + Logger.Debug(string.Format("AddScenarioPartForCustomizedEquipment({0}), Animal = {1}, Mech = {2}", equipment.EquipmentOption?.ThingDef?.defName, equipment.Animal, equipment.Mech)); if (equipment.Animal) { - return CreateStartingAnimalScenarioPart(equipment); + return new List() { CreateStartingAnimalScenarioPart(equipment) }; + } + else if (equipment.Mech) { + return CreateStartingMechScenarioParts(equipment); } var spawnType = equipment.SpawnType; if (equipment.EquipmentOption.RestrictedSpawnType && equipment.EquipmentOption.DefaultSpawnType != spawnType) { spawnType = equipment.EquipmentOption.DefaultSpawnType; } if (spawnType == EquipmentSpawnType.SpawnsWith) { - return CreateStartsWithScenarioPart(equipment); + return new List() { CreateStartsWithScenarioPart(equipment) }; } else if (spawnType == EquipmentSpawnType.SpawnsNear) { - return CreateScatterThingsNearScenarioPart(equipment); + return new List() { CreateScatterThingsNearScenarioPart(equipment) }; } else { - return null; + return Enumerable.Empty(); } } @@ -260,6 +261,49 @@ public ScenPart CreateStartingAnimalScenarioPart(CustomizedEquipment equipment) return CreateStartingAnimalWithRandomGenderScenarioPart(equipment); } } + public IEnumerable CreateStartingMechScenarioParts(CustomizedEquipment equipment) { + if (equipment.EquipmentOption.RandomMech) { + return CreateRandomStartingMechScenarioParts(equipment); + } + else { + return CreateDefinedStartingMechScenarioParts(equipment); + } + } + public IEnumerable CreateDefinedStartingMechScenarioParts(CustomizedEquipment equipment) { + ScenPartDef scenPartDef = DefDatabase.GetNamedSilentFail("StartingMech"); + if (scenPartDef == null) { + Logger.Warning("Could not find definition for starting mech scenario part. Cannot add scenario part"); + yield break; + } + PawnKindDef pawnKindDef = FindPawnKindDefForRace(equipment); + if (pawnKindDef == null) { + Logger.Warning(string.Format("Could not spawn selected mech ({0}). Could not find matching pawn kind", equipment.EquipmentOption?.ThingDef?.defName)); + yield break; + } + for (int i = 0; i < equipment.Count; i++) { + var part = new ScenPart_StartingMech() { + def = scenPartDef + }; + part.SetPrivateField("mechKind", pawnKindDef); + part.SetPrivateField("overseenByPlayerPawnChance", equipment.OverseenChance); + yield return part; + } + } + public IEnumerable CreateRandomStartingMechScenarioParts(CustomizedEquipment equipment) { + ScenPartDef scenPartDef = DefDatabase.GetNamedSilentFail("StartingMech"); + if (scenPartDef == null) { + Logger.Warning("Could not find definition for starting mech scenario part. Cannot add scenario part"); + yield break; + } + for (int i = 0; i < equipment.Count; i++) { + var part = new ScenPart_StartingMech() { + def = scenPartDef + }; + part.SetPrivateField("overseenByPlayerPawnChance", equipment.OverseenChance); + yield return part; + } + } + public ScenPart CreateRandomStartingAnimalScenarioPart(CustomizedEquipment equipment) { ScenPartDef scenPartDef = DefDatabase.GetNamedSilentFail("StartingAnimal"); if (scenPartDef == null) { @@ -278,7 +322,7 @@ public ScenPart CreateStartingAnimalWithRandomGenderScenarioPart(CustomizedEquip Logger.Warning("Could not find definition for starting animal scenario part. Cannot add scenario part"); return null; } - PawnKindDef pawnKindDef = FindPawnKindDefForAnimal(equipment); + PawnKindDef pawnKindDef = FindPawnKindDefForRace(equipment); if (pawnKindDef == null) { Logger.Warning(string.Format("Could not spawn selected animal ({0}). Could not find matching pawn kind", equipment.EquipmentOption?.ThingDef?.defName)); return null; @@ -291,7 +335,7 @@ public ScenPart CreateStartingAnimalWithRandomGenderScenarioPart(CustomizedEquip return part; } public ScenPart CreateStartingAnimalWithSpecificGenderScenarioPart(CustomizedEquipment equipment) { - PawnKindDef pawnKindDef = FindPawnKindDefForAnimal(equipment); + PawnKindDef pawnKindDef = FindPawnKindDefForRace(equipment); if (pawnKindDef == null) { Logger.Warning(string.Format("Could not spawn selected animal ({0}). Could not find matching pawn kind", equipment.EquipmentOption?.ThingDef?.defName)); return null; @@ -303,8 +347,8 @@ public ScenPart CreateStartingAnimalWithSpecificGenderScenarioPart(CustomizedEqu }; return part; } - public PawnKindDef FindPawnKindDefForAnimal(CustomizedEquipment equipment) { - return (from td in DefDatabase.AllDefs where td.race == equipment.EquipmentOption.ThingDef select td).FirstOrDefault(); + public PawnKindDef FindPawnKindDefForRace(CustomizedEquipment equipment) { + return DefDatabase.AllDefs.Where(k => k.race == equipment.EquipmentOption.ThingDef).FirstOrDefault(); } public void MarkCostsForRecalculation() { diff --git a/Source/ControllerTabViewPawns.cs b/Source/ControllerTabViewPawns.cs index 2af8eb5..ee4a0ee 100644 --- a/Source/ControllerTabViewPawns.cs +++ b/Source/ControllerTabViewPawns.cs @@ -313,11 +313,8 @@ public void AddInjury(Injury injury) { public void AddImplant(Implant implant) { PawnManager.AddPawnImplant(ViewState?.CurrentPawn, implant); } - public void RemoveHediff(Hediff hediff) { - PawnManager.RemovePawnHediff(ViewState?.CurrentPawn, hediff); - } public void RemoveHediffs(IEnumerable hediffs) { - PawnManager.RemovePawnHediffs(ViewState?.CurrentPawn, hediffs); + PawnManager.RemoveHediffs(ViewState?.CurrentPawn, hediffs); } public void RandomizeAppearance() { diff --git a/Source/CostCalculator.cs b/Source/CostCalculator.cs index fb554bb..01dac4c 100644 --- a/Source/CostCalculator.cs +++ b/Source/CostCalculator.cs @@ -95,6 +95,7 @@ public class CostCalculator { protected HashSet cheapApparel = new HashSet(); public StatWorker MarketValueStatWorker { get; set; } public float CostForRandomAnimal { get; set; } = 250f; + public float CostForRandomMech { get; set; } = 1200f; public CostCalculator() { @@ -174,6 +175,21 @@ public PawnCostDetailsRefactored CalculatePawnCost(CustomizedPawn pawn) { //Logger.Debug(string.Format("Market value for pawn apparel; pawn = {0}, apparel = {1}, cost = {2}", pawn.Pawn.LabelShortCap, apparel.def.defName, c)); } + // Implants that have a ThingDef associated with them (like a Mechlink) need to include the + // cost of that ThingDef + foreach (Implant implant in pawn.Customizations.Implants) { + if (implant.HediffDef == null) { + continue; + } + if (implant.Option?.ThingDef != null) { + int count = 1; + if (implant.Option.MaxSeverity > 0) { + count = (int)implant.Severity; + } + cost.bionics += MarketValueStatWorker.GetValue(StatRequest.For(implant.Option.ThingDef, null)) * count; + } + } + // Calculate cost for any materials needed for implants. OptionsHealth healthOptions = ProviderHealthOptions.GetOptions(pawn); foreach (Implant option in pawn.Customizations.Implants) { @@ -228,6 +244,9 @@ public double CalculateEquipmentCost(CustomizedEquipment equipment) { else if (equipment.EquipmentOption.RandomAnimal) { return CostForRandomAnimal * equipment.Count; } + else if (equipment.EquipmentOption.RandomMech) { + return CostForRandomMech * equipment.Count; + } else { return 0; } diff --git a/Source/CustomizationsPawn.cs b/Source/CustomizationsPawn.cs index 884b0d6..419bc8e 100644 --- a/Source/CustomizationsPawn.cs +++ b/Source/CustomizationsPawn.cs @@ -120,8 +120,6 @@ public class CustomizationsPawn { public List Injuries { get; set; } = new List(); public List Implants { get; set; } = new List(); - public List BodyParts = new List(); - // Titles public List Titles { get; set; } = new List(); diff --git a/Source/CustomizedEquipment.cs b/Source/CustomizedEquipment.cs index c2d2907..e536186 100644 --- a/Source/CustomizedEquipment.cs +++ b/Source/CustomizedEquipment.cs @@ -12,13 +12,18 @@ public class CustomizedEquipment { public EquipmentSpawnType? SpawnType { get; set; } public int Count { get; set; } public Gender? Gender { get; set; } + public float? OverseenChance { get; set; } public bool Animal { get { return EquipmentOption.RandomAnimal || (EquipmentOption.ThingDef?.race?.Animal ?? false); } } - + public bool Mech { + get { + return EquipmentOption.RandomMech || (EquipmentOption.ThingDef?.race?.IsMechanoid ?? false); + } + } public CustomizedEquipment CreateCopy() { return new CustomizedEquipment() { EquipmentOption = this.EquipmentOption, @@ -27,6 +32,7 @@ public CustomizedEquipment CreateCopy() { Count = this.Count, SpawnType = this.SpawnType, Gender = this.Gender, + OverseenChance = this.OverseenChance, }; } @@ -36,7 +42,8 @@ public override bool Equals(object obj) { EqualityComparer.Default.Equals(StuffDef, equipment.StuffDef) && Quality == equipment.Quality && SpawnType == equipment.SpawnType && - Gender == equipment.Gender; + Gender == equipment.Gender && + OverseenChance == equipment.OverseenChance; } public override int GetHashCode() { @@ -46,6 +53,7 @@ public override int GetHashCode() { hashCode = hashCode * -1521134295 + Quality.GetHashCode(); hashCode = hashCode * -1521134295 + SpawnType.GetHashCode(); hashCode = hashCode * -1521134295 + Gender.GetHashCode(); + hashCode = hashCode * -1521134295 + OverseenChance.GetHashCode(); return hashCode; } } diff --git a/Source/CustomizedHediff.cs b/Source/CustomizedHediff.cs index 6720f07..255a481 100644 --- a/Source/CustomizedHediff.cs +++ b/Source/CustomizedHediff.cs @@ -6,31 +6,7 @@ namespace EdB.PrepareCarefully { public abstract class CustomizedHediff { - public abstract BodyPartRecord BodyPartRecord { get; set; } - - public virtual string PartName { - get { - return BodyPartRecord != null ? (BodyPartRecord.LabelCap) : "EdB.PC.BodyParts.WholeBody".Translate().Resolve(); - } - } - - abstract public string ChangeName { - get; - } - - abstract public Color LabelColor { - get; - } - - public virtual bool HasTooltip { - get { - return false; - } - } - - public abstract string Tooltip { - get; - } + public BodyPartRecord BodyPartRecord { get; set; } } } diff --git a/Source/DialogManageImplants.cs b/Source/DialogManageImplants.cs index 4d22fe4..52e5434 100644 --- a/Source/DialogManageImplants.cs +++ b/Source/DialogManageImplants.cs @@ -11,57 +11,49 @@ namespace EdB.PrepareCarefully { public class DialogManageImplants : Window { public class ImplantBodyPart { - public UniqueBodyPart UniquePart { - get; set; - } + public UniqueBodyPart UniquePart { get; set; } public BodyPartRecord Part { get { - return UniquePart.Record; + return UniquePart?.Record; } } - public bool Selected { - get; set; - } - public bool Disabled { - get; set; - } - public Implant Implant { - get; set; - } - public Implant BlockingImplant { - get; set; - } + public bool Selected { get; set; } + public bool Disabled { get; set; } + public Implant Implant { get; set; } + public Implant BlockingImplant { get; set; } } - public class ImplantRecipe { - protected List parts = new List(); + public class DialogOption { + public ImplantOption ImplantOption { get; set; } public RecipeDef Recipe { - get; set; - } - public bool Selected { - get; set; - } - public bool PartiallySelected { - get; set; - } - public bool Disabled { - get; set; + get { + return ImplantOption?.RecipeDef; + } } + public bool Selected { get; set; } + public bool PartiallySelected { get; set; } + public bool SelectedDependency { get; set; } + public bool Disabled { get; set; } public bool RequiresPartSelection { get { - return parts != null && parts.Count > 1; + return Parts.CountAllowNull() > 1; } } - public Implant BlockingImplant { - get; set; - } - public List Parts { + public string Label { get { - return parts; - } - set { - parts = value; + if (Recipe != null) { + return Recipe?.LabelCap; + } + else if (ImplantOption?.HediffDef != null) { + return "EdB.PC.Dialog.Implant.InstallImplantLabel".Translate(ImplantOption.HediffDef.label); + } + else { + return ""; + } } } + public Implant BlockingImplant { get; set; } + public List Parts { get; set; } = new List(); + public float Severity { get; set; } = 1; } public string ConfirmButtonLabel = "EdB.PC.Dialog.Implant.Button.Confirm"; public string CancelButtonLabel = "EdB.PC.Common.Cancel"; @@ -80,14 +72,17 @@ public List Parts { public Rect HeaderRect { get; protected set; } public Rect CancelButtonRect { get; protected set; } public Rect ConfirmButtonRect { get; protected set; } + public Vector2 LevelLabelSize { get; protected set; } + public Vector2 LevelValueSize { get; protected set; } + public float LevelSliderWidth { get; protected set; } public Rect SingleButtonRect { get; protected set; } public Color DottedLineColor = new Color(60f / 255f, 64f / 255f, 67f / 255f); public Vector2 DottedLineSize = new Vector2(342, 2); protected string headerLabel; protected bool resizeDirtyFlag = true; protected bool confirmed = false; - protected WidgetTable table; - protected List recipes = new List(); + protected WidgetTable table; + protected List options = new List(); protected List implantList = new List(); protected Dictionary replacedParts = new Dictionary(); protected CustomizedPawn customizedPawn = null; @@ -109,7 +104,7 @@ public DialogManageImplants(CustomizedPawn customizedPawn, ProviderHealthOptions protected void InitializeWithCustomizedPawn(CustomizedPawn customizedPawn) { this.customizedPawn = customizedPawn; InitializeImplantList(); - InitializeRecipes(); + InitializeOptions(); ResetDisabledState(); } @@ -117,6 +112,7 @@ protected void InitializeImplantList() { implantList.Clear(); replacedParts.Clear(); foreach (var implant in customizedPawn.Customizations.Implants) { + Logger.Debug("initialize implant " + implant.Option?.HediffDef?.defName + ", severity = " + implant.Severity); implantList.Add(implant); if (implant.ReplacesPart) { replacedParts.Add(implant.BodyPartRecord, implant); @@ -124,20 +120,28 @@ protected void InitializeImplantList() { } } - protected void InitializeRecipes() { + protected void InitializeOptions() { OptionsHealth health = providerHealth.GetOptions(customizedPawn); - this.recipes.Clear(); - var result = new List(); - foreach (var recipe in health.ImplantRecipes) { - var implant = new ImplantRecipe(); - implant.Recipe = recipe; - implant.Selected = implantList.FirstOrDefault((Implant i) => { return i.Recipe == recipe; }) != null; - implant.Disabled = false; - implant.Parts = new List(); - foreach (var part in health.FindBodyPartsForImplantRecipe(recipe)) { + this.options.Clear(); + var result = new List(); + foreach (var implantOption in health.ImplantOptions) { + var option = new DialogOption(); + option.ImplantOption = implantOption; + Implant matchingImplant = implantList.FirstOrDefault((Implant i) => { + return (i.Recipe == implantOption.RecipeDef && implantOption.RecipeDef != null) + || (i.HediffDef == implantOption.HediffDef && implantOption.HediffDef != null); + }); + if (matchingImplant != null) { + option.Selected = true; + option.Severity = matchingImplant.Severity; + Logger.Debug("Initialize implant option " + implantOption.HediffDef.defName + " with severity " + matchingImplant.Severity); + } + option.Disabled = false; + option.Parts = new List(); + foreach (var part in health.FindBodyPartsForImplantRecipe(implantOption.RecipeDef)) { var implantPart = new ImplantBodyPart(); implantPart.UniquePart = part; - Implant foundImplant = implantList.FirstOrDefault((Implant i) => { return i.Recipe == recipe && i.BodyPartRecord == part.Record; }); + Implant foundImplant = implantList.FirstOrDefault((Implant i) => { return i.Recipe == implantOption.RecipeDef && i.BodyPartRecord == part.Record; }); if (foundImplant != null) { implantPart.Selected = true; implantPart.Implant = foundImplant; @@ -147,11 +151,50 @@ protected void InitializeRecipes() { implantPart.Implant = null; } implantPart.Disabled = false; - implant.Parts.Add(implantPart); + option.Parts.Add(implantPart); + } + if (implantOption.BodyPartRecord != null) { + UniqueBodyPart uniqueBodyPart = health.FindBodyPartsForRecord(implantOption.BodyPartRecord); + if (uniqueBodyPart != null) { + var implantPart = new ImplantBodyPart(); + implantPart.UniquePart = uniqueBodyPart; + Implant foundImplant = implantList.FirstOrDefault((Implant i) => { return i.HediffDef == implantOption.HediffDef && i.BodyPartRecord == implantOption.BodyPartRecord; }); + if (foundImplant != null) { + implantPart.Selected = true; + implantPart.Implant = foundImplant; + } + else { + implantPart.Selected = false; + implantPart.Implant = null; + } + implantPart.Disabled = false; + option.Parts.Add(implantPart); + } } - result.Add(implant); + if (implantOption.HediffDef != null && implantOption.BodyPartDefs != null) { + List allBodyParts = new List(); + foreach (var partDef in implantOption.BodyPartDefs) { + allBodyParts.AddRange(health.FindBodyPartsForDef(partDef)); + } + foreach (var part in allBodyParts) { + var implantPart = new ImplantBodyPart(); + implantPart.UniquePart = part; + Implant foundImplant = implantList.FirstOrDefault((Implant i) => { return i.HediffDef == implantOption.HediffDef && i.BodyPartRecord == part.Record; }); + if (foundImplant != null) { + implantPart.Selected = true; + implantPart.Implant = foundImplant; + } + else { + implantPart.Selected = false; + implantPart.Implant = null; + } + implantPart.Disabled = false; + option.Parts.Add(implantPart); + } + } + result.Add(option); } - this.recipes = result; + this.options = result.OrderBy(o => o.Label).ToList(); } protected void ResetDisabledState() { @@ -160,7 +203,7 @@ protected void ResetDisabledState() { // Iterate each selected implant in order to determine if it's valid--if it's not // trying to replace or install on top of an already-missing part. - // The first pass looks for duplicate implants that both try replace the same part. + // The first pass looks for duplicate implants that both try to replace the same part. Dictionary firstPassReplacedParts = new Dictionary(); List firstPassValidImplants = new List(); foreach (var implant in implantList) { @@ -222,19 +265,18 @@ protected void ResetDisabledState() { // Logger.Debug(" " + i.recipe.LabelCap + ", " + i.PartName + (i.ReplacesPart ? ", replaces part" : "")); //} - // Iterate each each body part option for each recipe to determine if that body part is missing, + // Iterate each body part option for each recipe to determine if that body part is missing, // based on the whether or not it or one of its ancestors has been replaced. Only evaluate each // body part once. The result will be used to determine if recipes and part options should be // disabled. HashSet evaluatedParts = new HashSet(); Dictionary blockedParts = new Dictionary(); - foreach (var recipe in recipes) { + foreach (var recipe in options) { foreach (var part in recipe.Parts) { if (evaluatedParts.Contains(part.Part)) { continue; } - Implant blockingImplant = null; - if (!replacedParts.TryGetValue(part.Part, out blockingImplant)) { + if (!replacedParts.TryGetValue(part.Part, out Implant blockingImplant)) { foreach (var ancestor in part.UniquePart.Ancestors) { if (replacedParts.TryGetValue(ancestor.Record, out blockingImplant)) { break; @@ -251,12 +293,12 @@ protected void ResetDisabledState() { // Go through each recipe and recipe part, marking the parts as disabled if // they are missing and marking the recipes as disabled if all of its parts // are disabled. - foreach (var recipe in recipes) { - recipe.Disabled = false; - recipe.BlockingImplant = null; + foreach (var option in options) { + option.Disabled = false; + option.BlockingImplant = null; int disabledCount = 0; - int partCount = recipe.Parts.Count; - foreach (var part in recipe.Parts) { + int partCount = option.Parts.Count; + foreach (var part in option.Parts) { part.Disabled = false; part.BlockingImplant = null; Implant blockingImplant = null; @@ -266,18 +308,18 @@ protected void ResetDisabledState() { part.BlockingImplant = blockingImplant; disabledCount++; if (partCount == 1) { - recipe.BlockingImplant = blockingImplant; + option.BlockingImplant = blockingImplant; } } } } - if (disabledCount == recipe.Parts.Count) { - recipe.Disabled = true; + if (disabledCount == option.Parts.Count) { + option.Disabled = true; } } // Evaluate each recipe's selected state. - foreach (var recipe in recipes) { + foreach (var recipe in options) { recipe.PartiallySelected = false; if (recipe.Selected) { int selectedCount = 0; @@ -298,7 +340,7 @@ protected void ResetDisabledState() { protected void ResetCachedBlockedSelectionAlert() { bool showAlert = false; - foreach (var recipe in recipes) { + foreach (var recipe in options) { foreach (var part in recipe.Parts) { if (part.Disabled && part.Implant != null) { showAlert = true; @@ -314,7 +356,7 @@ protected void ResetCachedBlockedSelectionAlert() { return; } List blockedSelections = new List(); - foreach (var recipe in recipes) { + foreach (var recipe in options) { int partCount = recipe.Parts.Count; foreach (var part in recipe.Parts) { if (part.Disabled && part.Implant != null) { @@ -374,46 +416,174 @@ protected void MarkResizeFlagDirty() { resizeDirtyFlag = true; } - public void ClickRecipeAction (ImplantRecipe recipe) { - if (recipe.Disabled && !recipe.Selected) { + public void ClickOptionAction(DialogOption option) { + if (option.Disabled && !option.Selected) { return; } SoundDefOf.Tick_Tiny.PlayOneShotOnCamera(); - if (recipe.Selected) { - recipe.Selected = false; - foreach (var part in recipe.Parts) { - if (part.Selected) { - part.Selected = false; - RemoveImplant(recipe, part); + if (option.Selected) { + DeselectOption(option); + RemoveUnneededDependencies(); + AddMissingDependencies(); + } + else { + SelectOption(option); + AddMissingDependencies(); + } + MarkDisabledOptionsAsDirty(); + } + + public void SelectOption(DialogOption option) { + option.SelectedDependency = false; + if (option.Selected) { + return; + } + option.Selected = true; + if (option.Parts.Count == 1) { + if (!option.Parts[0].Selected) { + option.Parts[0].Selected = true; + AddImplant(option, option.Parts[0]); + } + } + } + + public void SelectDependencyOption(DialogOption option) { + if (option.Selected) { + return; + } + option.SelectedDependency = true; + if (option.Parts.Count == 1) { + option.Parts[0].Selected = true; + AddImplant(option, option.Parts[0]); + } + } + + public void AddMissingDependencies() { + HashSet missingDependencies = new HashSet(); + int maxAttempts = 100; + int attempt = 0; + while (NeedsDependencies(ref missingDependencies)) { + if (attempt++ > maxAttempts) { + return; + } + foreach (var d in missingDependencies) { + SelectDependencyOption(d); + } + missingDependencies.Clear(); + } + } + + public bool NeedsDependencies(ref HashSet missingDependencies) { + Logger.Debug("NeedsDependencies()"); + if (missingDependencies == null) { + missingDependencies = new HashSet(); + } + HashSet selected = new HashSet(implantList.Select(i => i.Option.HediffDef)); + Logger.Debug(" Selected implants: " + string.Join(", ", selected.Select(d => d.defName))); + foreach (var implant in implantList) { + if (implant.Option.Dependency != null && !selected.Contains(implant.Option.Dependency)) { + var optionToAdd = FindDialogOptionForHediff(implant.Option.Dependency); + if (optionToAdd != null) { + missingDependencies.Add(optionToAdd); } } } - else { - recipe.Selected = true; - if (recipe.Parts.Count == 1) { - recipe.Parts[0].Selected = true; - AddImplant(recipe, recipe.Parts[0]); + Logger.Debug(" Needs dependencies: " + string.Join(", ", missingDependencies.Select(o => o.ImplantOption.HediffDef.defName))); + return missingDependencies.Count > 0; + } + + public void RemoveUnneededDependencies() { + HashSet unneededDependencies = new HashSet(); + int maxAttempts = 100; + int attempt = 0; + while (HasUnneededDependencies(ref unneededDependencies)) { + if (attempt++ > maxAttempts) { + return; + } + foreach (var d in unneededDependencies) { + DeselectOption(d); + } + unneededDependencies.Clear(); + } + } + + public bool HasUnneededDependencies(ref HashSet unneededDependencies) { + if (unneededDependencies == null) { + unneededDependencies = new HashSet(); + } + HashSet neededDependencies = new HashSet(implantList.Select(i => i.Option?.Dependency).Where(d => d != null)); + foreach (var option in options.Where(o => o.SelectedDependency)) { + if (!neededDependencies.Contains(option.ImplantOption.HediffDef)) { + unneededDependencies.Add(option); + } + } + Logger.Debug("Unneeded dependencies: " + string.Join(", ", unneededDependencies.Select(o => o.ImplantOption?.HediffDef?.defName ?? "null"))); + + return unneededDependencies.Count > 0; + } + + protected DialogOption FindDialogOptionForHediff(HediffDef def) { + return options.FirstOrDefault(o => o.ImplantOption.HediffDef == def); + } + + public void DeselectOption(DialogOption option) { + Logger.Debug("DeselectOption(" + option.ImplantOption.HediffDef.defName + ")"); + option.Selected = false; + option.SelectedDependency = false; + if (option.Parts != null) { + Logger.Debug(" option.Parts = " + string.Join(", ", option.Parts.Select(p => p.UniquePart.Record.def.defName))); + } + foreach (var part in option.Parts) { + if (part.Selected) { + part.Selected = false; + RemoveImplant(option, part); } } MarkDisabledOptionsAsDirty(); } - protected void AddImplant(ImplantRecipe recipe, ImplantBodyPart part) { + public void UpdateSeverity(DialogOption option, float severity) { + if (option.Disabled || !option.Selected) { + return; + } + option.Severity = severity; + Implant implant = implantList.FirstOrDefault(i => i.Option == option.ImplantOption); + if (implant != null) { + implant.Severity = option.Severity; + Logger.Debug("Updated implant severity"); + } + else { + Logger.Debug("Didn't find implant to update"); + } + } + protected void AddImplant(DialogOption option, ImplantBodyPart part) { Implant implant = new Implant(); - implant.Recipe = recipe.Recipe; + implant.Option = option.ImplantOption; + implant.Recipe = option.Recipe; + implant.HediffDef = option.ImplantOption.HediffDef; implant.BodyPartRecord = part.Part; + implant.Severity = option.Severity; implantList.Add(implant); part.Implant = implant; + Logger.Debug("Added implant for " + option.ImplantOption.HediffDef.defName); } - protected void RemoveImplant(ImplantRecipe recipe, ImplantBodyPart part) { + protected void RemoveImplant(DialogOption option, ImplantBodyPart part) { if (part.Implant != null) { - implantList.Remove(part.Implant); + if (implantList.Remove(part.Implant)) { + Logger.Debug("Removed implant for " + option.ImplantOption.HediffDef.defName); + } + else { + Logger.Debug("Couldn't find implant to remvoe for " + option.ImplantOption.HediffDef.defName); + } part.Implant = null; } + else { + Logger.Debug("Couldn't remove implant because part had no reference to the implant"); + } } - public void ClickPartAction(ImplantRecipe recipe, ImplantBodyPart part) { + public void ClickPartAction(DialogOption recipe, ImplantBodyPart part) { if (part.Disabled && !part.Selected) { return; } @@ -445,6 +615,11 @@ protected void Resize() { WindowSize = new Vector2(440f, 584f); ButtonSize = new Vector2(140f, 40f); + Text.Font = GameFont.Small; + LevelLabelSize = Text.CalcSize("EdB.PC.Dialog.Implant.LevelLabel".Translate("Level".Translate())); + LevelValueSize = Text.CalcSize("00"); + Text.Font = GameFont.Small; + ContentSize = new Vector2(WindowSize.x - WindowPadding * 2 - ContentMargin.x * 2, WindowSize.y - WindowPadding * 2 - ContentMargin.y * 2 - FooterHeight - headerSize); @@ -472,17 +647,17 @@ protected void Resize() { float radioWidth = 36; Vector2 nameSize = new Vector2(ContentRect.width - portraitSize.x - radioWidth, portraitSize.y * 0.5f); - table = new WidgetTable(); + table = new WidgetTable(); table.Rect = new Rect(Vector2.zero, ContentRect.size); table.RowHeight = LineHeight; table.RowColor = new Color(0, 0, 0, 0); table.AlternateRowColor = new Color(0, 0, 0, 0); - table.SelectedAction = (ImplantRecipe recipe) => { + table.SelectedAction = (DialogOption recipe) => { }; - table.AddColumn(new WidgetTable.Column() { + table.AddColumn(new WidgetTable.Column() { Name = "Recipe", AdjustForScrollbars = true, - DrawAction = (ImplantRecipe recipe, Rect rect, WidgetTable.Metadata metadata) => { + DrawAction = (DialogOption option, Rect rect, WidgetTable.Metadata metadata) => { GUI.color = Color.white; Text.Anchor = TextAnchor.LowerLeft; Rect labelRect = new Rect(rect.x, rect.y, rect.width, LineHeight); @@ -491,88 +666,127 @@ protected void Resize() { Rect clickRect = new Rect(labelRect.x, labelRect.y, labelRect.width - checkboxRect.width, labelRect.height); GUI.color = DottedLineColor; GUI.DrawTexture(dottedLineRect, Textures.TextureDottedLine); - Vector2 labelSize = Text.CalcSize(recipe.Recipe.LabelCap); + Vector2 labelSize = Text.CalcSize(option.Label); GUI.color = Style.ColorWindowBackground; GUI.DrawTexture(new Rect(labelRect.x, labelRect.y, labelSize.x + 2, labelRect.height), BaseContent.WhiteTex); + + if (option.SelectedDependency) { + TooltipHandler.TipRegion(labelRect, "EdB.PC.Dialog.Implant.RequiredImplantMessage".Translate()); + } GUI.DrawTexture(checkboxRect.InsetBy(-2, -2, -40, -2), BaseContent.WhiteTex); - if (!recipe.Disabled) { - Style.SetGUIColorForButton(labelRect, recipe.Selected, Style.ColorText, Style.ColorButtonHighlight, Style.ColorButtonHighlight); - Widgets.Label(labelRect, recipe.Recipe.LabelCap); + if (!option.Disabled) { + Style.SetGUIColorForButton(labelRect, option.Selected || option.SelectedDependency, Style.ColorText, Style.ColorButtonHighlight, Style.ColorButtonHighlight); + Widgets.Label(labelRect, option.Label); if (Widgets.ButtonInvisible(clickRect)) { - ClickRecipeAction(recipe); + ClickOptionAction(option); } GUI.color = Color.white; - Texture2D checkboxTexture = Textures.TextureCheckbox; - if (recipe.PartiallySelected) { - checkboxTexture = Textures.TextureCheckboxPartiallySelected; + GUI.DrawTexture(checkboxRect, Textures.TextureCheckbox); + Texture checkmarkTexture = null; + if (option.PartiallySelected) { + checkmarkTexture = Textures.TextureCheckboxPartiallySelected; } - else if (recipe.Selected) { - checkboxTexture = Textures.TextureCheckboxSelected; + else if (option.Selected) { + GUI.color = Style.ColorCheckmark; + checkmarkTexture = Textures.TextureCheckmark; } - if (Widgets.ButtonImage(checkboxRect, checkboxTexture)) { - ClickRecipeAction(recipe); + else if (option.SelectedDependency) { + GUI.color = Style.ColorCheckmark; + checkmarkTexture = Textures.TextureCheckmarkForcedSelection; + } + if (checkmarkTexture != null) { + GUI.DrawTexture(checkboxRect, checkmarkTexture); + } + GUI.color = Color.white; + if (Widgets.ButtonInvisible(checkboxRect)) { + ClickOptionAction(option); } } else { GUI.color = Style.ColorControlDisabled; - Widgets.Label(labelRect, recipe.Recipe.LabelCap); - GUI.DrawTexture(checkboxRect, recipe.Selected ? Textures.TextureCheckboxPartiallySelected : Textures.TextureCheckbox); + Widgets.Label(labelRect, option.Label); + GUI.DrawTexture(checkboxRect, option.Selected ? Textures.TextureCheckboxPartiallySelected : Textures.TextureCheckbox); if (Widgets.ButtonInvisible(checkboxRect)) { - ClickRecipeAction(recipe); + ClickOptionAction(option); } - if (recipe.BlockingImplant != null) { - TooltipHandler.TipRegion(labelRect, "EdB.PC.Dialog.Implant.Conflict".Translate(recipe.BlockingImplant.Recipe.LabelCap, recipe.BlockingImplant.BodyPartRecord.Label)); + if (option.BlockingImplant != null) { + TooltipHandler.TipRegion(labelRect, "EdB.PC.Dialog.Implant.Conflict".Translate(option.BlockingImplant.Recipe.LabelCap, option.BlockingImplant.BodyPartRecord.Label)); } } - if (recipe.Selected && recipe.RequiresPartSelection) { - float partInset = 32; + if (option.Selected || option.SelectedDependency) { + float inset = 32; float cursor = labelRect.yMax; - foreach (var part in recipe.Parts) { - string labelText = part.Part.LabelCap; - labelRect = new Rect(rect.x + partInset, cursor, rect.width - partInset * 2, LineHeight); - dottedLineRect = new Rect(labelRect.x, labelRect.y + 21, DottedLineSize.x, DottedLineSize.y); - checkboxRect = new Rect(labelRect.x + labelRect.width - 22 - 6, labelRect.MiddleY() - 12, 22, 22); - clickRect = new Rect(labelRect.x, labelRect.y, labelRect.width - checkboxRect.width, labelRect.height); - GUI.color = DottedLineColor; - GUI.DrawTexture(dottedLineRect, Textures.TextureDottedLine); - labelSize = Text.CalcSize(labelText); - GUI.color = Style.ColorWindowBackground; - GUI.DrawTexture(new Rect(labelRect.x, labelRect.y, labelSize.x + 2, labelRect.height), BaseContent.WhiteTex); - GUI.DrawTexture(checkboxRect.InsetBy(-2, -2, -80, -2), BaseContent.WhiteTex); - if (!part.Disabled) { - Style.SetGUIColorForButton(labelRect, part.Selected, Style.ColorText, Style.ColorButtonHighlight, Style.ColorButtonHighlight); - Widgets.Label(labelRect, labelText); - if (Widgets.ButtonInvisible(clickRect)) { - ClickPartAction(recipe, part); - } - GUI.color = Color.white; - if (Widgets.ButtonImage(checkboxRect, part.Selected ? Textures.TextureCheckboxSelected : Textures.TextureCheckbox)) { - ClickPartAction(recipe, part); - } + if (option.ImplantOption.MaxSeverity > 0) { + float rowWidth = rect.width - inset * 2; + float sliderWidth = rowWidth - LevelLabelSize.x - LevelValueSize.x - 12; + float sliderHeight = 12; + float sliderTop = cursor + LineHeight * 0.5f - sliderHeight * 0.5f; + Rect levelLabelRect = new Rect(rect.x + inset, cursor, LevelLabelSize.x, LineHeight); + Rect sliderRect = new Rect(levelLabelRect.xMax + 8, sliderTop, sliderWidth, sliderHeight); + Rect levelValueRect = new Rect(sliderRect.xMax + 4, cursor, LevelValueSize.x, LineHeight); + var guiState = UtilityGUIState.Save(); + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleLeft; + GUI.color = Style.ColorText; + Widgets.Label(levelLabelRect, "EdB.PC.Dialog.Implant.LevelLabel".Translate("Level".Translate())); + Widgets.Label(levelValueRect, ((int)option.Severity).ToString()); + guiState.Restore(); + float value = Widgets.HorizontalSlider(sliderRect, option.Severity, option.ImplantOption.MinSeverity, option.ImplantOption.MaxSeverity, false, null, null, null, 1.0f); + if (value != option.Severity) { + UpdateSeverity(option, value); } - else { - GUI.color = Style.ColorControlDisabled; - Widgets.Label(labelRect, labelText); - GUI.DrawTexture(checkboxRect, part.Selected ? Textures.TextureCheckboxPartiallySelected : Textures.TextureCheckbox); - if (Widgets.ButtonInvisible(checkboxRect)) { - ClickPartAction(recipe, part); + cursor += sliderRect.height; + } + if (option.RequiresPartSelection) { + foreach (var part in option.Parts) { + string labelText = part.Part.LabelCap; + labelRect = new Rect(rect.x + inset, cursor, rect.width - inset * 2, LineHeight); + dottedLineRect = new Rect(labelRect.x, labelRect.y + 21, DottedLineSize.x, DottedLineSize.y); + checkboxRect = new Rect(labelRect.x + labelRect.width - 22 - 6, labelRect.MiddleY() - 12, 22, 22); + clickRect = new Rect(labelRect.x, labelRect.y, labelRect.width - checkboxRect.width, labelRect.height); + GUI.color = DottedLineColor; + GUI.DrawTexture(dottedLineRect, Textures.TextureDottedLine); + labelSize = Text.CalcSize(labelText); + GUI.color = Style.ColorWindowBackground; + GUI.DrawTexture(new Rect(labelRect.x, labelRect.y, labelSize.x + 2, labelRect.height), BaseContent.WhiteTex); + GUI.DrawTexture(checkboxRect.InsetBy(-2, -2, -80, -2), BaseContent.WhiteTex); + if (!part.Disabled) { + Style.SetGUIColorForButton(labelRect, part.Selected, Style.ColorText, Style.ColorButtonHighlight, Style.ColorButtonHighlight); + Widgets.Label(labelRect, labelText); + if (Widgets.ButtonInvisible(clickRect)) { + ClickPartAction(option, part); + } + GUI.color = Color.white; + if (Widgets.ButtonImage(checkboxRect, part.Selected ? Textures.TextureCheckboxSelected : Textures.TextureCheckbox)) { + ClickPartAction(option, part); + } } - if (part.BlockingImplant != null) { - TooltipHandler.TipRegion(labelRect, "EdB.PC.Dialog.Implant.Conflict".Translate(part.BlockingImplant.Recipe.LabelCap, part.BlockingImplant.BodyPartRecord.Label)); + else { + GUI.color = Style.ColorControlDisabled; + Widgets.Label(labelRect, labelText); + GUI.DrawTexture(checkboxRect, part.Selected ? Textures.TextureCheckboxPartiallySelected : Textures.TextureCheckbox); + if (Widgets.ButtonInvisible(checkboxRect)) { + ClickPartAction(option, part); + } + if (part.BlockingImplant != null) { + TooltipHandler.TipRegion(labelRect, "EdB.PC.Dialog.Implant.Conflict".Translate(part.BlockingImplant.Recipe.LabelCap, part.BlockingImplant.BodyPartRecord.Label)); + } } + cursor += labelRect.height; } - cursor += labelRect.height; } } Text.Anchor = TextAnchor.UpperLeft; }, - MeasureAction = (ImplantRecipe recipe, float width, WidgetTable.Metadata metadata) => { - if (recipe.Selected && recipe.Parts.Count > 1) { - return LineHeight + (LineHeight * recipe.Parts.Count); + MeasureAction = (DialogOption option, float width, WidgetTable.Metadata metadata) => { + float height = LineHeight; + if (option.Selected && option.Parts.Count > 1) { + height += LineHeight * option.Parts.Count; } - else { - return LineHeight; + if (option.Selected && option.ImplantOption.MaxSeverity > 0) { + height += LineHeight; } + return height; }, Width = ContentSize.x }); @@ -602,7 +816,7 @@ public override void DoWindowContents(Rect inRect) { GUI.BeginGroup(ContentRect); try { - table.Draw(this.recipes); + table.Draw(this.options); } finally { GUI.EndGroup(); diff --git a/Source/EquipmentDatabase.cs b/Source/EquipmentDatabase.cs index 5e25bfb..ca2f670 100644 --- a/Source/EquipmentDatabase.cs +++ b/Source/EquipmentDatabase.cs @@ -22,6 +22,7 @@ public class EquipmentDatabase { protected EquipmentType TypeBuildings = new EquipmentType("Buildings", "EdB.PC.Equipment.Type.Buildings"); protected EquipmentType TypeAnimals = new EquipmentType("Animals", "EdB.PC.Equipment.Type.Animals"); protected EquipmentType TypeDiscard = new EquipmentType("Discard", ""); + protected EquipmentType TypeMech = new EquipmentType("Mechs", "EdB.PC.Equipment.Type.Mechs"); protected EquipmentType TypeUncategorized = new EquipmentType("Uncategorized", ""); protected ThingCategoryDef thingCategorySweetMeals = null; @@ -35,6 +36,7 @@ public class EquipmentDatabase { public Dictionary ThingCategoryItemCounts { get; set; } = new Dictionary(); public Dictionary ModItemCounts { get; set; } = new Dictionary(); public EquipmentOption RandomAnimalEquipmentOption { get; private set; } + public EquipmentOption RandomMechEquipmentOption { get; private set; } public EquipmentDatabase() { types.Add(TypeResources); @@ -49,6 +51,7 @@ public EquipmentDatabase() { thingCategoryMeatRaw = DefDatabase.GetNamedSilentFail("MeatRaw"); AddRandomAnimalToEquipmentOptions(); + AddRandomMechToEquipmentOptions(); } public IEnumerable EquipmentOptionsByType(EquipmentType type) { @@ -62,6 +65,9 @@ public IEnumerable EquipmentOptionsByCategory(ThingCategoryDef return def.DescendantThingDefs.Select(t => FindOptionForThingDef(t)).Where(t => t != null); } } + public IEnumerable AllMechEquipmentOptions() { + return EquipmentOptions.Where(e => e.Mech); + } public IEnumerable EquipmentOptionsBySearchTerm(string term) { foreach (var option in EquipmentOptions) { if (option.Label.IndexOf(term, StringComparison.CurrentCultureIgnoreCase) != -1) { @@ -173,7 +179,7 @@ protected void UpdateLoadingPhase(LoadingPhase phase) { && d.scatterableOnMapGen && !d.destroyOnDrop) || (d.category == ThingCategory.Building && d.Minifiable) //|| (d.category == ThingCategory.Building && d.scatterableOnMapGen) // TODO: Remove to get rid of "Ancient Lamppost" - || (d.race != null && d.race.Animal) + || ((d.race?.IsMechanoid ?? false) && d.GetCompProperties() != null) ) .GetEnumerator(); } @@ -347,6 +353,17 @@ protected void AddRandomAnimalToEquipmentOptions() { }; EquipmentOptions.Add(RandomAnimalEquipmentOption); } + protected void AddRandomMechToEquipmentOptions() { + RandomMechEquipmentOption = new EquipmentOption() { + ThingDef = null, + DefaultSpawnType = EquipmentSpawnType.Mech, + Materials = null, + SupportsQuality = false, + RandomMech = true, + EquipmentType = TypeMech + }; + EquipmentOptions.Add(RandomMechEquipmentOption); + } protected bool AddStyleToEquipmentOptions(StyleCategoryDef def) { if (def.thingDefStyles.NullOrEmpty()) { @@ -465,6 +482,9 @@ public EquipmentSpawnType DefaultSpawnTypeForThingDef(ThingDef def, out bool res if (def?.race?.Animal ?? false) { return EquipmentSpawnType.Animal; } + if (def?.race?.IsMechanoid ?? false) { + return EquipmentSpawnType.Mech; + } if (def.apparel != null) { return EquipmentSpawnType.SpawnsWith; } @@ -546,6 +566,14 @@ public EquipmentType ClassifyThingDef(ThingDef def) { DiscardDebug(def, "Discarding because it is a plant"); return TypeDiscard; } + if (def.race?.IsMechanoid ?? false) { + if (def.GetCompProperties() != null) { + return TypeMech; + } + else { + return TypeDiscard; + } + } if (def.IsApparel) { return TypeApparel; } diff --git a/Source/EquipmentOption.cs b/Source/EquipmentOption.cs index 4d976a5..acdb672 100644 --- a/Source/EquipmentOption.cs +++ b/Source/EquipmentOption.cs @@ -16,12 +16,18 @@ public class EquipmentOption { public bool RestrictedSpawnType { get; set; } public EquipmentType EquipmentType { get; set; } public bool RandomAnimal { get; set; } - + public bool RandomMech { get; set; } + public bool Animal { get { return RandomAnimal || (ThingDef?.race?.Animal ?? false); } } + public bool Mech { + get { + return RandomMech || (ThingDef?.race?.IsMechanoid ?? false); + } + } public bool Gendered { get { return ThingDef?.race?.hasGenders ?? false; @@ -29,11 +35,14 @@ public bool Gendered { } public string Label { get { - if (!RandomAnimal) { - return ThingDef.LabelCap; + if (RandomAnimal) { + return "EdB.PC.Equipment.AvailableEquipment.RandomAnimalLabel".Translate(); + } + else if (RandomMech) { + return "EdB.PC.Equipment.AvailableEquipment.RandomMechLabel".Translate(); } else { - return "EdB.PC.Equipment.AvailableEquipment.RandomAnimalLabel".Translate(); + return ThingDef.LabelCap; } } } diff --git a/Source/EquipmentSpawnType.cs b/Source/EquipmentSpawnType.cs index 6fad8fa..8315b47 100644 --- a/Source/EquipmentSpawnType.cs +++ b/Source/EquipmentSpawnType.cs @@ -9,6 +9,7 @@ public enum EquipmentSpawnType { SpawnsWith, SpawnsNear, Possession, - Animal + Animal, + Mech } } diff --git a/Source/Implant.cs b/Source/Implant.cs index d254c7d..2aee0d1 100644 --- a/Source/Implant.cs +++ b/Source/Implant.cs @@ -10,74 +10,23 @@ public class Implant : CustomizedHediff { public string label = ""; - protected string tooltip; - public Implant() { } - protected BodyPartRecord bodyPartRecord; - public override BodyPartRecord BodyPartRecord { - get { - return bodyPartRecord; - } - set { - bodyPartRecord = value; - tooltip = null; - } - } - - protected Hediff hediff = null; - public Hediff Hediff { - get => hediff; - set => hediff = value; - } - - protected HediffDef hediffDef = null; - public HediffDef HediffDef { - get => hediffDef; - set => hediffDef = value; - } - - - override public string ChangeName { - get { - return Label; - } - } - - override public Color LabelColor { - get { - if (recipe.addsHediff != null) { - return recipe.addsHediff.defaultLabelColor; - } - else { - return Style.ColorText; - } - } - } - public Implant(BodyPartRecord bodyPartRecord, RecipeDef recipe) { this.BodyPartRecord = bodyPartRecord; - this.recipe = recipe; + this.Recipe = recipe; } - protected RecipeDef recipe = null; - public RecipeDef Recipe { - get { - return recipe; - } - set { - recipe = value; - tooltip = null; - } - } + public ImplantOption Option { get; set; } + public RecipeDef Recipe { get; set; } + public HediffDef HediffDef { get; set; } + public Hediff Hediff { get; set; } + public float Severity { get; set; } = 0f; public string Label { get { - if (recipe == null) { - return ""; - } - return recipe.addsHediff.LabelCap; + return Recipe?.addsHediff?.LabelCap ?? ""; } } @@ -92,51 +41,19 @@ public bool ReplacesPart { } } - public override bool HasTooltip { - get { - return hediff != null; - } - } - - public override string Tooltip { - get { - if (tooltip == null) { - InitializeTooltip(); - } - return tooltip; - } - } - - protected void InitializeTooltip() { - StringBuilder stringBuilder = new StringBuilder(); - Hediff_Injury hediff_Injury = hediff as Hediff_Injury; - string damageLabel = hediff.SeverityLabel; - if (!hediff.Label.NullOrEmpty() || !damageLabel.NullOrEmpty() || !hediff.CapMods.NullOrEmpty()) { - stringBuilder.Append(hediff.LabelCap); - if (!damageLabel.NullOrEmpty()) { - stringBuilder.Append(": " + damageLabel); - } - stringBuilder.AppendLine(); - string tipStringExtra = hediff.TipStringExtra; - if (!tipStringExtra.NullOrEmpty()) { - stringBuilder.AppendLine(tipStringExtra.TrimEndNewlines().Indented()); - } - } - tooltip = stringBuilder.ToString(); - } - public override bool Equals(object obj) { return obj is Implant implant && - EqualityComparer.Default.Equals(bodyPartRecord, implant.bodyPartRecord) && - EqualityComparer.Default.Equals(hediffDef, implant.hediffDef) && - EqualityComparer.Default.Equals(recipe, implant.recipe); + ReferenceEquals(BodyPartRecord, implant.BodyPartRecord) && + ReferenceEquals(HediffDef, implant.HediffDef) && + ReferenceEquals(Recipe, implant.Recipe) + ; } public override int GetHashCode() { var hashCode = 223280684; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(bodyPartRecord); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(hediffDef); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(recipe); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(BodyPartRecord); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(HediffDef); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Recipe); return hashCode; } } diff --git a/Source/ImplantOption.cs b/Source/ImplantOption.cs new file mode 100644 index 0000000..86b9449 --- /dev/null +++ b/Source/ImplantOption.cs @@ -0,0 +1,25 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse; + +namespace EdB.PrepareCarefully { + public class ImplantOption { + public RecipeDef RecipeDef { get; set; } + public HediffDef HediffDef { get; set; } + public HediffDef Dependency { get; set; } + public ThingDef ThingDef { get; set; } + public BodyPartRecord BodyPartRecord { get; set; } + public HashSet BodyPartDefs { get; set; } + public float MinSeverity { get; set; } = 0; + public float MaxSeverity { get; set; } = 0; + public bool SupportsSeverity { + get { + return MaxSeverity > 0; + } + } + } +} diff --git a/Source/Injury.cs b/Source/Injury.cs index 15eb23b..78c019f 100644 --- a/Source/Injury.cs +++ b/Source/Injury.cs @@ -9,124 +9,27 @@ namespace EdB.PrepareCarefully { public class Injury : CustomizedHediff { - + public InjuryOption Option { get; set; } public Hediff Hediff { get; set; } - - public string tooltip; - - public InjuryOption option; - public InjuryOption Option { - get { - return option; - } - set { - option = value; - tooltip = null; - stageLabel = ComputeStageLabel(); - } - } - - - - protected string stageLabel = null; - + public float Severity { get; set; } = 0; public float? PainFactor { get; set; } - public ChemicalDef Chemical { get; set; } - protected float severity = 0; - public float Severity { - get { - return severity; - } - set { - tooltip = null; - severity = value; - stageLabel = ComputeStageLabel(); - } - } - public Injury() { } - public BodyPartRecord bodyPartRecord; - override public BodyPartRecord BodyPartRecord { - get { - return bodyPartRecord; - } - set { - bodyPartRecord = value; - } - } - - override public string ChangeName { - get { - if (stageLabel != null) { - return stageLabel; - } - else if (Option != null) { - return Option.Label; - } - else { - return "?"; - } - } - } - - override public Color LabelColor { - get { - if (Option != null && Option.HediffDef != null) { - return Option.IsOldInjury ? Color.gray : Option.HediffDef.defaultLabelColor; - } - else { - return Style.ColorText; - } - } - } - - // EVERY RELEASE: - // Check the PainCategory enum to verify that we still only have 4 values and that their int values match the logic here. - // This method converts a float value into a PainCategory. It's here because we don't quite remember where that float - // value comes from and if it contain a value that won't map to one of the PainCategory enum values. - // Unchanged for 1.14 - protected PainCategory PainCategoryForFloat(float value) { - int intValue = Mathf.FloorToInt(value); - if (intValue == 2) { - intValue = 1; - } - else if (intValue > 3 && intValue < 6) { - intValue = 3; - } - else if (intValue > 6) { - intValue = 6; - } - return (PainCategory)intValue; - } - - protected string ComputeStageLabel() { - if (Option.HasStageLabel) { - return "EdB.PC.Panel.Health.InjuryLabel.Stage".Translate(this.option.Label, CurStage.label); - } - else if (Option.IsOldInjury) { - return "EdB.PC.Panel.Health.InjuryLabel.Severity".Translate(this.option.Label, (int)severity); - } - else { - return null; - } - } - protected HediffStage CurStage { get { - return (!this.option.HediffDef.stages.NullOrEmpty()) ? this.option.HediffDef.stages[this.CurStageIndex] : null; + return (!this.Option.HediffDef.stages.NullOrEmpty()) ? this.Option.HediffDef.stages[this.CurStageIndex] : null; } } protected int CurStageIndex { get { - if (this.option.HediffDef.stages == null) { + if (this.Option.HediffDef.stages == null) { return 0; } - List stages = this.option.HediffDef.stages; + List stages = this.Option.HediffDef.stages; for (int i = stages.Count - 1; i >= 0; i--) { if (this.Severity >= stages[i].minSeverity) { return i; @@ -136,65 +39,23 @@ protected int CurStageIndex { } } - public override bool HasTooltip { - get { - return Hediff != null; - } - } - - public override string Tooltip { - get { - if (tooltip == null) { - InitializeTooltip(); - } - return tooltip; - } - } - - protected void InitializeTooltip() { - StringBuilder stringBuilder = new StringBuilder(); - if (Hediff.Part != null) { - stringBuilder.Append(Hediff.Part.def.LabelCap + ": "); - stringBuilder.Append(" " + Hediff.pawn.health.hediffSet.GetPartHealth(Hediff.Part).ToString() + " / " + Hediff.Part.def.GetMaxHealth(Hediff.pawn).ToString()); - } - else { - stringBuilder.Append("WholeBody".Translate()); - } - stringBuilder.AppendLine(); - stringBuilder.AppendLine("------------------"); - Hediff_Injury hediff_Injury = Hediff as Hediff_Injury; - string damageLabel = Hediff.SeverityLabel; - if (!Hediff.Label.NullOrEmpty() || !damageLabel.NullOrEmpty() || !Hediff.CapMods.NullOrEmpty()) { - stringBuilder.Append(Hediff.LabelCap); - if (!damageLabel.NullOrEmpty()) { - stringBuilder.Append(": " + damageLabel); - } - stringBuilder.AppendLine(); - string tipStringExtra = Hediff.TipStringExtra; - if (!tipStringExtra.NullOrEmpty()) { - stringBuilder.AppendLine(tipStringExtra.TrimEndNewlines().Indented()); - } - } - tooltip = stringBuilder.ToString(); - } - public override bool Equals(object obj) { return obj is Injury injury && - EqualityComparer.Default.Equals(option, injury.option) && + EqualityComparer.Default.Equals(Option, injury.Option) && PainFactor == injury.PainFactor && EqualityComparer.Default.Equals(Chemical, injury.Chemical) && - severity == injury.severity && - EqualityComparer.Default.Equals(bodyPartRecord, injury.bodyPartRecord) && + Severity == injury.Severity && + EqualityComparer.Default.Equals(BodyPartRecord, injury.BodyPartRecord) && EqualityComparer.Default.Equals(CurStage, injury.CurStage); } public override int GetHashCode() { var hashCode = 662792533; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(option); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Option); hashCode = hashCode * -1521134295 + PainFactor.GetHashCode(); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Chemical); - hashCode = hashCode * -1521134295 + severity.GetHashCode(); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(bodyPartRecord); + hashCode = hashCode * -1521134295 + Severity.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(BodyPartRecord); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(CurStage); return hashCode; } diff --git a/Source/ManagerEquipment.cs b/Source/ManagerEquipment.cs index e12d68f..aaf0f1f 100644 --- a/Source/ManagerEquipment.cs +++ b/Source/ManagerEquipment.cs @@ -121,6 +121,34 @@ public void InitializeStateFromScenarioAndStartingPawns() { State.ReplacedScenarioParts.Add(part); } } + + // Go through all of the scenario steps that spawn a mech and add the mech to the equipment/resource list. + if (part is ScenPart_StartingMech mech) { + PawnKindDef pawnKindDef = part.GetPrivateField("mechKind"); + float overseenChance = part.GetPrivateField("overseenByPlayerPawnChance"); + if (pawnKindDef != null && pawnKindDef.race != null) { + EquipmentDatabase.PreloadDefinition(pawnKindDef.race); + EquipmentOption option = EquipmentDatabase.FindOptionForThingDef(pawnKindDef.race); + if (option != null) { + AddEquipment(new CustomizedEquipment() { + EquipmentOption = option, + Count = 1, + SpawnType = EquipmentSpawnType.Mech, + OverseenChance = overseenChance + }); + State.ReplacedScenarioParts.Add(part); + } + } + else { + AddEquipment(new CustomizedEquipment() { + EquipmentOption = EquipmentDatabase.RandomMechEquipmentOption, + Count = 1, + SpawnType = EquipmentSpawnType.Mech, + OverseenChance = overseenChance + }); + State.ReplacedScenarioParts.Add(part); + } + } } // Go through starting possessions diff --git a/Source/ManagerPawns.cs b/Source/ManagerPawns.cs index 3ae334b..89d26a7 100644 --- a/Source/ManagerPawns.cs +++ b/Source/ManagerPawns.cs @@ -27,6 +27,7 @@ public class ManagerPawns { public ProviderBodyTypes ProviderBodyTypes { get; set; } public ProviderFactions ProviderFactions { get; set; } public ProviderHeadTypes ProviderHeadTypes { get; set; } + public ProviderHealthOptions ProviderHealthOptions { get; set; } public AgeModifier AgeModifier { get; set; } public EquipmentDatabase EquipmentDatabase { get; set; } public PawnLoader PawnLoader { get; set; } @@ -691,7 +692,6 @@ public void AddPawnInjury(CustomizedPawn customizedPawn, Injury injury) { return; } customizations.Injuries.Add(injury); - customizations.BodyParts.Add(injury); Customizer.ApplyInjuryAndImplantCustomizationsToPawn(pawn, customizations); PawnToCustomizationsMapper.MapInjuriesAndImplants(pawn, customizations); CostAffected?.Invoke(); @@ -703,7 +703,6 @@ public void AddPawnImplant(CustomizedPawn customizedPawn, Implant implant) { return; } customizations.Implants.Add(implant); - customizations.BodyParts.Add(implant); Customizer.ApplyInjuryAndImplantCustomizationsToPawn(pawn, customizations); PawnToCustomizationsMapper.MapInjuriesAndImplants(pawn, customizations); CostAffected?.Invoke(); @@ -715,66 +714,119 @@ public void UpdateImplants(CustomizedPawn customizedPawn, IEnumerable i return; } List implantsToRemove = new List(); - foreach (var bodyPart in customizations.BodyParts) { - Implant asImplant = bodyPart as Implant; - implantsToRemove.Add(asImplant); - } - foreach (var implant in implantsToRemove) { - customizations.BodyParts.Remove(implant); + foreach (var existingImplant in customizations.Implants) { + var matchingImplant = implants.FirstOrDefault(i => i.Equals(existingImplant)); + if (matchingImplant == null) { + implantsToRemove.Add(existingImplant); + } } + RemoveHediffsInternal(customizedPawn, implantsToRemove.SelectMany(i => HediffsMatchingImplant(customizedPawn, i))); customizations.Implants.Clear(); - foreach (var implant in implants) { - customizations.BodyParts.Add(implant); - customizations.Implants.Add(implant); - } + customizations.Implants.AddRange(implants); Customizer.ApplyInjuryAndImplantCustomizationsToPawn(pawn, customizations); PawnToCustomizationsMapper.MapInjuriesAndImplants(pawn, customizations); ClearPawnGraphicsCache(pawn); CostAffected?.Invoke(); } - public void RemovePawnHediffs(CustomizedPawn customizedPawn, IEnumerable hediffs) { + public IEnumerable HediffsMatchingImplant(CustomizedPawn customizedPawn, Implant implant) { + Pawn pawn = customizedPawn.Pawn; + if (pawn == null) { + return Enumerable.Empty(); + } + if (implant == null) { + Logger.Warning("implant is null"); + return Enumerable.Empty(); + } + + return pawn.health.hediffSet.hediffs.Where(h => h?.def == implant?.Option?.HediffDef && h?.Part == implant?.BodyPartRecord); + } + + private void RemoveHediffsInternal(CustomizedPawn customizedPawn, IEnumerable hediffs) { Pawn pawn = customizedPawn?.Pawn; CustomizationsPawn customizations = customizedPawn?.Customizations; if (pawn == null || customizations == null) { return; } - foreach (Hediff hediff in hediffs) { - pawn.health.RemoveHediff(hediff); - Injury injury = customizations.Injuries.FirstOrDefault(i => i.Hediff == hediff); - Implant implant = customizations.Implants.FirstOrDefault(i => i.Hediff == hediff); - if (injury != null) { - RemoveCustomBodyParts(injury, customizations); - } - if (implant != null) { - RemoveCustomBodyParts(implant, customizations); - } + var list = hediffs.ToList(); + foreach (Hediff hediff in list) { + RemoveHediff(customizedPawn, hediff); } + RemoveHediffsWithUnsatisfiedDependencies(customizedPawn); + } + + public void RemoveHediffs(CustomizedPawn customizedPawn, IEnumerable hediffs) { + Pawn pawn = customizedPawn?.Pawn; + CustomizationsPawn customizations = customizedPawn?.Customizations; + if (pawn == null || customizations == null) { + return; + } + RemoveHediffsInternal(customizedPawn, hediffs); + RemoveHediffsWithUnsatisfiedDependencies(customizedPawn); Customizer.ApplyInjuryAndImplantCustomizationsToPawn(pawn, customizations); PawnToCustomizationsMapper.MapInjuriesAndImplants(pawn, customizations); ClearPawnGraphicsCache(pawn); CostAffected?.Invoke(); } - public void RemoveCustomBodyParts(CustomizedHediff part, CustomizationsPawn customizations) { - Implant implant = part as Implant; - Injury injury = part as Injury; - if (implant != null) { - customizations.Implants.Remove(implant); + + protected void RemoveHediff(CustomizedPawn customizedPawn, Hediff hediff) { + Pawn pawn = customizedPawn?.Pawn; + CustomizationsPawn customizations = customizedPawn?.Customizations; + if (pawn == null || customizations == null) { + return; } + Logger.Debug("Removed hediff: " + hediff.def.defName + ", " + hediff.Part?.def?.defName); + pawn.health.RemoveHediff(hediff); + Injury injury = customizations.Injuries.FirstOrDefault(i => i.Hediff == hediff); + Implant implant = customizations.Implants.FirstOrDefault(i => i.Hediff == hediff); if (injury != null) { customizations.Injuries.Remove(injury); } - customizations.BodyParts.Remove(part); - CostAffected?.Invoke(); + if (implant != null) { + customizations.Implants.Remove(implant); + } + } + + protected void RemoveHediffsWithUnsatisfiedDependencies(CustomizedPawn customizedPawn) { + HashSet unsatisifedHediffs = new HashSet(); + int maxAttempts = 100; + int attempt = 0; + while (HasHediffsWithUnsatisfiedDependencies(customizedPawn, ref unsatisifedHediffs)) { + if (attempt++ > maxAttempts) { + return; + } + foreach (var hediff in unsatisifedHediffs) { + RemoveHediff(customizedPawn, hediff); + } + unsatisifedHediffs.Clear(); + } } - public void RemovePawnHediff(CustomizedPawn customizedPawn, Hediff hediff) { + + protected bool HasHediffsWithUnsatisfiedDependencies(CustomizedPawn customizedPawn, ref HashSet unsatisfiedHediffs) { Pawn pawn = customizedPawn?.Pawn; if (pawn == null) { - return; + return false; } - pawn.health.RemoveHediff(hediff); - CostAffected?.Invoke(); + var options = ProviderHealthOptions.GetOptions(customizedPawn); + if (options == null) { + return false; + } + Dictionary dependencies = new Dictionary(); + foreach (var hediff in pawn.health.hediffSet.hediffs) { + var matchingImplantOption = options.FindImplantOptionsThatAddHediff(hediff).FirstOrDefault(); + if (matchingImplantOption?.Dependency != null) { + dependencies[hediff] = matchingImplantOption.Dependency; + } + } + unsatisfiedHediffs = new HashSet(); + foreach (var pair in dependencies) { + if (!pawn.health.hediffSet.hediffs.ContainsAny(h => h.def == pair.Value)) { + unsatisfiedHediffs.Add(pair.Key); + } + } + return unsatisfiedHediffs.Count > 0; } + public void UpdateBodyType(CustomizedPawn customizedPawn, BodyTypeDef bodyTypeDef) { Pawn pawn = customizedPawn?.Pawn; CustomizationsPawn customizations = customizedPawn?.Customizations; diff --git a/Source/ManagerRelationships.cs b/Source/ManagerRelationships.cs index ab63373..0a74dd9 100644 --- a/Source/ManagerRelationships.cs +++ b/Source/ManagerRelationships.cs @@ -73,7 +73,7 @@ public void InitializeHiddenPawns(IEnumerable customPawns) { Pawn otherPawn = r.otherPawn; if (!customPawnLookup.Contains(otherPawn)) { if (!hiddenPawns.ContainsAny(c => c.Pawn == otherPawn)) { - Logger.Debug(" Added hidden pawn: " + otherPawn.Label + ", faction = " + otherPawn.Faction?.Name + ", leader = " + (otherPawn.Faction?.leader == otherPawn ? "true" : "false")); + Logger.Debug(" Added hidden pawn: " + otherPawn.LabelShort + ", faction = " + otherPawn.Faction?.Name + ", leader = " + (otherPawn.Faction?.leader == otherPawn ? "true" : "false")); hiddenPawns.Add(new CustomizedPawn() { Pawn = otherPawn, Type = CustomizedPawnType.Hidden, @@ -381,10 +381,11 @@ public void InitializeWithCustomizedPawns(IEnumerable pawns) { public void InitializeRelationshipsForStartingPawns(IEnumerable customPawns) { Logger.Debug("InitializeRelationshipsForStartingPawns(): " + customPawns?.Count()); + IEnumerable allPawns = customPawns.Concat(hiddenPawns); // Go through each pawn and check for relationships between it and all other pawns. - foreach (CustomizedPawn pawn in customPawns) { - foreach (CustomizedPawn other in customPawns) { + foreach (CustomizedPawn pawn in allPawns) { + foreach (CustomizedPawn other in allPawns) { if (pawn == other) { continue; } @@ -535,7 +536,7 @@ public void RemoveAllRelationshipsForPawn(Pawn pawn) { } public RelationshipBuilder GetRelationshipBuilder() { - return new RelationshipBuilder(State.Customizations.Relationships, State.Customizations.ParentChildGroups); + return new RelationshipBuilder(State.Customizations.AllPawns, State.Customizations.Relationships, State.Customizations.ParentChildGroups); } } } diff --git a/Source/MapperPawnToCustomizations.cs b/Source/MapperPawnToCustomizations.cs index 29bcf36..0257828 100644 --- a/Source/MapperPawnToCustomizations.cs +++ b/Source/MapperPawnToCustomizations.cs @@ -71,7 +71,7 @@ private List CreateCustomizedGeneList(Pawn pawn, IEnumerable= 0) { + implant.Severity = level.level; + } + } implants.Add(implant); } - else if (hediff.def.defName != "MissingBodyPart") { - Logger.Warning("Could not add hediff {" + hediff.def.defName + "} to the pawn because no recipe adds it to the body part {" + (hediff.Part?.def?.defName ?? "WholeBody") + "}"); - } else { - Logger.Warning("Could not add hediff {" + hediff.def.defName + "} to the pawn. It is not currently supported"); + Logger.Warning("Could not add hediff {" + hediff.def.defName + "} to the pawn because found no matching implant option for the body part {" + (hediff.Part?.def?.defName ?? "WholeBody") + "}"); } } } customizations.Injuries.Clear(); customizations.Implants.Clear(); - customizations.BodyParts.Clear(); foreach (var injury in injuries) { //Logger.Debug("Adding injury: " + injury.Option.Label); customizations.Injuries.Add(injury); - customizations.BodyParts.Add(injury); } foreach (var implant in implants) { //Logger.Debug("Adding implant: " + implant.Label); customizations.Implants.Add(implant); - customizations.BodyParts.Add(implant); } } diff --git a/Source/Mod.cs b/Source/Mod.cs index 6aeb3d7..b0c4516 100644 --- a/Source/Mod.cs +++ b/Source/Mod.cs @@ -137,6 +137,7 @@ public void Start(Page_ConfigureStartingPawns configureStartingPawnsPage) { ProviderHeadTypes = providerHeadTypes, ProviderBodyTypes = providerBodyTypes, ProviderFactions = providerFactions, + ProviderHealthOptions = providerHealthOptions, AgeModifier = ageModifier, EquipmentDatabase = equipmentDatabase, PawnLoader = pawnLoader, diff --git a/Source/OptionsHealth.cs b/Source/OptionsHealth.cs index f335be2..d6b7cd7 100644 --- a/Source/OptionsHealth.cs +++ b/Source/OptionsHealth.cs @@ -20,16 +20,15 @@ public class UniqueBodyPart { public class OptionsHealth { protected List bodyPartList = new List(); protected Dictionary> bodyPartDefLookup = new Dictionary>(); - protected Dictionary> implantRecipeLookup = new Dictionary>(); protected Dictionary bodyPartRecordLookup = new Dictionary(); protected Dictionary> bodyPartGroupLookup = new Dictionary>(); - protected List implantRecipes = new List(); + protected Dictionary> implantRecipeLookup = new Dictionary>(); + protected Dictionary injuryOptionsByHediff = new Dictionary(); protected List injuryOptions = new List(); - public OptionsHealth() { - } + public List ImplantOptions { get; private set; } = new List(); public BodyDef BodyDef { get; set; } @@ -131,32 +130,103 @@ public List FindBodyPartsForDef(BodyPartDef def) { } } public IEnumerable FindImplantRecipesThatAddHediff(Hediff hediff) { - return ImplantRecipes.Where((RecipeDef def) => { - if (def.addsHediff == null) { + return ImplantOptions.Where(o => RecipeAddsHediff(o.RecipeDef, hediff)).Select(o => o.RecipeDef); + } + + public IEnumerable FindImplantOptionsThatAddHediff(Hediff hediff) { + return ImplantOptions.Where((ImplantOption o) => { + if (RecipeAddsHediff(o.RecipeDef, hediff)) { + return true; + } + if (o.HediffDef == null) { return false; } - if (def.addsHediff != hediff.def) { + if (o.HediffDef != hediff.def) { return false; } if (hediff.Part != null) { - if (def.appliedOnFixedBodyParts.Contains(hediff.Part.def)) { - return true; + if (o.BodyPartDefs.NullOrEmpty()) { + return false; } - foreach (var group in def.appliedOnFixedBodyPartGroups) { - var parts = this.PartsForBodyPartGroup(group.defName); - if (parts != null) { - if (parts.ConvertAll(p => p.Record.def).Contains(hediff.Part.def)) { - return true; - } - } + if (!o.BodyPartDefs.Contains(hediff.Part.def)) { + return false; } } - else if (def.appliedOnFixedBodyParts.Count == 0 && def.appliedOnFixedBodyPartGroups.Count == 0) { + return true; + }); + } + + public ImplantOption FindImplantOptionThatAddsHediffDefToBodyPart(HediffDef hediffDef, BodyPartDef bodyPartDef) { + return ImplantOptions.Where(o => { + if (o.HediffDef != hediffDef) { + return false; + } + if (bodyPartDef == null && o.BodyPartDefs == null) { return true; } + return o.BodyPartDefs.Contains(bodyPartDef); + }).FirstOrDefault(); + } + + public ImplantOption FindImplantOptionThatAddsRecipeDefToBodyPart(RecipeDef recipefDef, BodyPartDef bodyPartDef) { + if (recipefDef == null) { + return null; + } + return ImplantOptions.Where(o => { + return RecipeTargetsBodyPart(recipefDef, bodyPartDef); + }).FirstOrDefault(); + } + + public bool RecipeAddsHediff(RecipeDef recipe, Hediff hediff) { + if (recipe == null) { return false; - }); + } + if (recipe.addsHediff == null) { + return false; + } + if (recipe.addsHediff != hediff.def) { + return false; + } + if (hediff.Part != null) { + if (recipe.appliedOnFixedBodyParts.Contains(hediff.Part.def)) { + return true; + } + foreach (var group in recipe.appliedOnFixedBodyPartGroups) { + var parts = this.PartsForBodyPartGroup(group.defName); + if (parts != null) { + if (parts.ConvertAll(p => p.Record.def).Contains(hediff.Part.def)) { + return true; + } + } + } + } + else if (recipe.appliedOnFixedBodyParts.Count == 0 && recipe.appliedOnFixedBodyPartGroups.Count == 0) { + return true; + } + return false; + } + public bool RecipeTargetsBodyPart(RecipeDef recipeDef, BodyPartDef bodyPartDef) { + if (bodyPartDef == null && !recipeDef.targetsBodyPart) { + return true; + } + if (recipeDef.appliedOnFixedBodyParts != null) { + if (recipeDef.appliedOnFixedBodyParts.Contains(bodyPartDef)) { + return true; + } + } + if (recipeDef.appliedOnFixedBodyPartGroups != null) { + foreach (var group in recipeDef.appliedOnFixedBodyPartGroups) { + var parts = this.PartsForBodyPartGroup(group.defName); + if (parts != null) { + if (parts.Select(p => p.Record.def).Contains(bodyPartDef)) { + return true; + } + } + } + } + return false; } + public IEnumerable SkinCoveredBodyParts { get { return bodyPartList.Where((UniqueBodyPart p) => { return p.SkinCovered; }); @@ -174,37 +244,52 @@ public IEnumerable SolidBodyParts { } public void AddImplantRecipe(RecipeDef recipe, List parts) { if (parts != null && parts.Count > 0) { - List partList; - if (implantRecipeLookup.TryGetValue(recipe, out partList)) { + // If we've already added the recipe than just add any new parts to the existing + // part list. Otherwise, add the implant option. + if (implantRecipeLookup.TryGetValue(recipe, out List partList)) { partList.AddRange(parts); } else { - implantRecipes.Add(recipe); + ImplantOptions.Add(new ImplantOption() { + RecipeDef = recipe, + HediffDef = recipe.addsHediff + }); implantRecipeLookup.Add(recipe, parts.ToList()); } } } - public List FindBodyPartsForImplantRecipe(RecipeDef recipeDef) { - List partList; - if (implantRecipeLookup.TryGetValue(recipeDef, out partList)) { + public void AddImplantOption(ImplantOption option) { + ImplantOptions.Add(option); + } + public void AddImplantHediffDef(HediffDef hediffDef, BodyPartRecord bodyPartRecord = null) { + ImplantOptions.Add(new ImplantOption() { + RecipeDef = null, + HediffDef = hediffDef, + BodyPartRecord = bodyPartRecord, + }); + } + + public IEnumerable FindBodyPartsForImplantRecipe(RecipeDef recipeDef) { + if (recipeDef == null) { + return Enumerable.Empty(); + } + if (implantRecipeLookup.TryGetValue(recipeDef, out List partList)) { return partList; } else { - return null; - } - } - public List ImplantRecipes { - get { - return implantRecipes; + return Enumerable.Empty(); } } + public void Sort() { SortImplants(); SortInjuries(); } protected void SortImplants() { - implantRecipes.Sort((RecipeDef a, RecipeDef b) => { - return string.Compare(a.LabelCap.Resolve(), b.LabelCap.Resolve()); + ImplantOptions.Sort((ImplantOption a, ImplantOption b) => { + string aLabel = a.RecipeDef?.LabelCap.Resolve() ?? a.HediffDef?.LabelCap ?? ""; + string bLabel = b.RecipeDef?.LabelCap.Resolve() ?? b.HediffDef?.LabelCap ?? ""; + return string.Compare(aLabel, bLabel); }); } protected void SortInjuries() { diff --git a/Source/PanelEquipmentAvailable.cs b/Source/PanelEquipmentAvailable.cs index 58e53d8..e068a59 100644 --- a/Source/PanelEquipmentAvailable.cs +++ b/Source/PanelEquipmentAvailable.cs @@ -35,6 +35,9 @@ public class ViewEquipmentList { protected Rect RectScrollView; protected Rect RectRow; protected Rect RectItem; + protected Rect RectOverseenLabel; + protected Rect RectOverseenSlider; + protected Rect RectOverseenPercentLabel; protected Vector2 AddButtonSize; protected static Vector2 SizeTextureSortIndicator = new Vector2(8, 4); protected EquipmentType selectedType = null; @@ -47,6 +50,7 @@ public class ViewEquipmentList { private EquipmentOption LastDrawnEquipmentOption = null; private ThingCategoryDef FilterThingCategory = null; + private bool FilterMechs = false; private string FilterModName = null; private string FilterSearchTerm = null; private List FilteredOptions = new List(); @@ -128,6 +132,14 @@ public override void Resize(Rect rect) { RectRow = new Rect(0, 0, RectScrollView.width, 42); RectItem = new Rect(10, 2, 38, 38); + + Text.Font = GameFont.Tiny; + Vector2 rectOverseenLabelSize = Text.CalcSize("EdB.PC.Equipment.AvailableEquipment.OverseenChanceLabel".Translate("StartOverseenChance".Translate())); + Vector2 rectOverseenPercentLabelSize = Text.CalcSize("000%".Translate()); + Text.Font = GameFont.Small; + RectOverseenLabel = new Rect(0, 0, rectOverseenLabelSize.x, 18); + RectOverseenSlider = new Rect(RectOverseenLabel.xMax + 8, 3, 240, 12); + RectOverseenPercentLabel = new Rect(RectOverseenSlider.xMax + 8, 0, rectOverseenPercentLabelSize.x, 18); } protected bool FilterTick() { @@ -154,6 +166,9 @@ protected void ApplyCurrentFilters() { else { options = ProviderEquipment.Equipment; } + if (FilterMechs) { + options = ProviderEquipment.EquipmentDatabase.AllMechEquipmentOptions(); + } if (FilterModName != null) { options = ProviderEquipment.EquipmentDatabase.ApplyModNameFilter(options, FilterModName); } @@ -164,21 +179,31 @@ protected void ApplyCurrentFilters() { } protected void PrepareThingCategoryFilterOptions() { - ThingCategoryFloatMenuOptions = new List(); - ThingCategoryFloatMenuOptions.Add(new FloatMenuOption("EdB.PC.Equipment.AvailableEquipment.AllCategoriesOption".Translate(), () => { + List options = new List(); + options.Add(new FloatMenuOption("EdB.PC.Equipment.AvailableEquipment.MechCategoryLabel".Translate(), () => { FilterThingCategory = null; + FilterMechs = true; ApplyCurrentFilters(); }, MenuOptionPriority.Default, null, null, 0, null, null)); foreach (var thingCategory in ProviderEquipment.EquipmentDatabase.ThingCategories) { if (thingCategory.defName == "Root") { continue; } - ThingCategoryFloatMenuOptions.Add(new FloatMenuOption(LabelForThingCategory(thingCategory), () => { + options.Add(new FloatMenuOption(LabelForThingCategory(thingCategory), () => { FilterThingCategory = thingCategory; + FilterMechs = false; ApplyCurrentFilters(); }, MenuOptionPriority.Default, null, null, 0, null, null)); - ThingCategoryFloatMenuOptions = ThingCategoryFloatMenuOptions.OrderBy(m => m.Label).ToList(); } + + ThingCategoryFloatMenuOptions = new List() { + new FloatMenuOption("EdB.PC.Equipment.AvailableEquipment.AllCategoriesOption".Translate(), () => { + FilterThingCategory = null; + FilterMechs = false; + ApplyCurrentFilters(); + }, MenuOptionPriority.Default, null, null, 0, null, null) + }; + ThingCategoryFloatMenuOptions.AddRange(options.OrderBy(m => m.Label)); } protected void PrepareModNameFilterOptions() { ModNameFloatMenuOptions = new List(); @@ -395,6 +420,9 @@ public void SelectOption(EquipmentOption option) { Count = 1, Gender = null }; + if (option.Mech) { + SelectedValues.OverseenChance = 1.0f; + } RecalculateSelectedOptionCost(); } @@ -409,7 +437,7 @@ public void DrawIconForEquipmentOption(EquipmentOption equipment, float y) { Widgets.ThingIcon(iconRect, equipment.ThingDef, equipment.ThingDef.defaultStuff); } } - else if (equipment.RandomAnimal) { + else if (equipment.RandomAnimal || equipment.RandomMech) { GUI.color = Style.ColorTextSecondary; Rect iconRect = new Rect(12f, y + 6, 24f, 24f); GUI.DrawTexture(iconRect, Textures.TextureButtonRandom); @@ -532,6 +560,33 @@ public float DrawSelectedRow(float y, float width, int index, EquipmentOption eq y += 24; } + // Draw overseen chance + if (SelectedOption.Mech) { + // Draw the slider + Text.Font = GameFont.Tiny; + GUI.color = Style.ColorText; + Text.Anchor = TextAnchor.MiddleLeft; + Rect overseenLabelRect = RectOverseenLabel.OffsetBy(insetMargin, y); + Widgets.Label(overseenLabelRect, "EdB.PC.Equipment.AvailableEquipment.OverseenChanceLabel".Translate("StartOverseenChance".Translate())); + Text.Anchor = TextAnchor.MiddleCenter; + Text.Font = GameFont.Tiny; + Rect sliderRect = RectOverseenSlider.OffsetBy(insetMargin, y); + Rect percentLabelRect = RectOverseenPercentLabel.OffsetBy(insetMargin, y); + Widgets.Label(percentLabelRect, String.Format("{0:F0}%", SelectedValues.OverseenChance.Value * 100f)); + Text.Anchor = TextAnchor.MiddleLeft; + GUI.color = Color.white; + float value = GUI.HorizontalSlider(sliderRect, SelectedValues.OverseenChance.Value, 0f, 1f); + y += sliderRect.height; + + Text.Font = GameFont.Small; + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + y += 8; + + // Update the hit points based on the slider value + SelectedValues.OverseenChance = value; + } + // Add button Rect addButtonRect = new Rect(insetMargin, y + 4, AddButtonSize.x, AddButtonSize.y); Text.Font = GameFont.Tiny; diff --git a/Source/PanelEquipmentSelected.cs b/Source/PanelEquipmentSelected.cs index 4a541d9..e9e6949 100644 --- a/Source/PanelEquipmentSelected.cs +++ b/Source/PanelEquipmentSelected.cs @@ -175,7 +175,7 @@ public float DrawSelectedEquipment(float y, float width, int index, CustomizedEq } Widgets.ThingIcon(iconRect, equipment.EquipmentOption.ThingDef, stuff); } - else if (equipment.EquipmentOption.RandomAnimal) { + else if (equipment.EquipmentOption.RandomAnimal || equipment.EquipmentOption.RandomMech) { GUI.color = Style.ColorTextSecondary; Rect iconRect = new Rect(12f, y + 6, 24f, 24f); GUI.DrawTexture(iconRect, Textures.TextureButtonRandom); @@ -214,6 +214,12 @@ public float DrawSelectedEquipment(float y, float width, int index, CustomizedEq string subtitleText = equipment.Gender.Value.GetLabel().CapitalizeFirst(); Widgets.Label(subtitleRect, subtitleText.Truncate(subtitleRect.width - 48)); } + else if (equipment.OverseenChance != null) { + Text.Font = GameFont.Tiny; + string subtitleText = "EdB.PC.Equipment.SelectedEquipment.OverseenChanceLabel" + .Translate("StartOverseenChance".Translate(), string.Format("{0:F0}", equipment.OverseenChance.Value * 100f)); + Widgets.Label(subtitleRect, subtitleText.Truncate(subtitleRect.width - 48)); + } else { labelRect = new Rect(labelLeftMargin, y, rowRect.width - labelLeftMargin, subtitleRect.yMax - labelRect.y); Text.Anchor = TextAnchor.MiddleLeft; diff --git a/Source/PanelHealth.cs b/Source/PanelHealth.cs index 4a0c5cf..e8320ef 100644 --- a/Source/PanelHealth.cs +++ b/Source/PanelHealth.cs @@ -14,12 +14,10 @@ public class PanelHealth : PanelModule { protected static readonly string HediffTypeImplant = "HediffTypeImplant"; public delegate void AddInjuryHandler(Injury injury); - public delegate void AddImplantHandler(Implant implant); public delegate void RemoveHediffsHandler(IEnumerable hediffs); public delegate void UpdateImplantsHandler(IEnumerable implants); public event AddInjuryHandler InjuryAdded; - public event AddImplantHandler ImplantAdded; public event RemoveHediffsHandler HediffsRemoved; public event UpdateImplantsHandler ImplantsUpdated; @@ -201,7 +199,6 @@ public void DrawAddButton(float y, float width) { OptionsHealth healthOptions = ProviderHealth.GetOptions(customizedPawn); string selectedHediffType = this.selectedHediffType; - RecipeDef selectedRecipe = null; InjuryOption selectedInjury = null; BodyPartRecord selectedBodyPart = null; bool bodyPartSelectionRequired = true; @@ -304,9 +301,6 @@ public void DrawAddButton(float y, float width) { addInjuryAction(); } } - else if (selectedHediffType == HediffTypeImplant) { - ImplantAdded(new Implant(selectedBodyPart, selectedRecipe)); - } } }; @@ -315,7 +309,6 @@ public void DrawAddButton(float y, float width) { CancelButtonLabel = "EdB.PC.Common.Cancel".Translate(), HeaderLabel = "EdB.PC.Dialog.Injury.Header".Translate(), NameFunc = (InjuryOption option) => { - //return option.HediffDef.defName; return option.Label; }, DescriptionFunc = (InjuryOption option) => { @@ -415,15 +408,6 @@ public void DrawAddButton(float y, float width) { } } - protected void ApplyImplantsToPawn(CustomizedPawn pawn, List implants) { - //Logger.Debug("Updated implants"); - //foreach (var i in implants) { - // Logger.Debug(" " + i.recipe.LabelCap + ", " + i.PartName + (i.ReplacesPart ? ", replaces part" : "")); - //} - // TODO - //pawn.UpdateImplants(implants); - } - protected void AddInjuryToPawn(InjuryOption option, InjurySeverity severity, BodyPartRecord bodyPart) { Injury injury = new Injury(); injury.BodyPartRecord = bodyPart; diff --git a/Source/PanelRelationshipsOther.cs b/Source/PanelRelationshipsOther.cs index 5e6d90e..4a81e1c 100644 --- a/Source/PanelRelationshipsOther.cs +++ b/Source/PanelRelationshipsOther.cs @@ -77,7 +77,7 @@ protected override void DrawPanelContent() { try { foreach (CustomizedRelationship relationship in State.Customizations.Relationships) { // Don't show relationships between two hidden pawns - if (relationship.Source.Type != CustomizedPawnType.Hidden && relationship.Target.Type != CustomizedPawnType.Hidden) { + if (relationship.Source.Type != CustomizedPawnType.Hidden || relationship.Target.Type != CustomizedPawnType.Hidden) { cursor = DrawRelationship(cursor, relationship); } } diff --git a/Source/PawnCustomizer.cs b/Source/PawnCustomizer.cs index 5d3c05e..ade0087 100644 --- a/Source/PawnCustomizer.cs +++ b/Source/PawnCustomizer.cs @@ -447,17 +447,29 @@ public void ApplyImplantToPawn(Pawn pawn, Implant implant) { if (pawn == null || implant == null) { return; } - Logger.Debug("Adding implant to pawn, recipe = " + implant.Recipe?.defName + ", hediff = " + implant.HediffDef?.defName); + //Logger.Debug("Adding implant to pawn, recipe = " + implant.Recipe?.defName + ", hediff = " + implant.HediffDef?.defName + ", bodyPart = " + implant.BodyPartRecord?.def?.defName); if (implant.BodyPartRecord == null) { Logger.Warning("Could not add implant to pawn because no BodyPartRecord is defined"); } if (implant.Recipe != null) { implant.Hediff = HediffMaker.MakeHediff(implant.Recipe.addsHediff, pawn, implant.BodyPartRecord); - pawn.health.AddHediff(implant.Hediff, implant.BodyPartRecord, new DamageInfo?()); + if (implant.Severity > 0) { + implant.Hediff.Severity = implant.Severity; + } + pawn.health.AddHediff(implant.Hediff, implant.BodyPartRecord); } else if (implant.HediffDef != null) { - implant.Hediff = HediffMaker.MakeHediff(implant.HediffDef, pawn, implant.BodyPartRecord); - pawn.health.AddHediff(implant.Hediff, implant.BodyPartRecord, new DamageInfo?()); + var hediff = HediffMaker.MakeHediff(implant.HediffDef, pawn, implant.BodyPartRecord); + if (implant.Severity > 0) { + if (hediff is Hediff_Level level) { + level.level = (int)implant.Severity; + } + else { + hediff.Severity = implant.Severity; + } + } + pawn.health.AddHediff(hediff, implant.BodyPartRecord); + implant.Hediff = hediff; } else { Logger.Warning("Could not add implant to pawn because no RecipeDef or HediffDef is defined"); @@ -494,7 +506,7 @@ public void ApplyInjuryToPawn(Pawn pawn, Injury injury) { injury.Hediff = hediff; } else if (injury.Option.HediffDef == HediffDefOf.PsychicAmplifier) { - Logger.Debug("Adding Psylink with level " + injury.Severity); + //Logger.Debug("Adding Psylink with level " + injury.Severity); Hediff_Psylink mainPsylinkSource = pawn.GetMainPsylinkSource(); HashSet abilitiesBefore = new HashSet(pawn.abilities.AllAbilitiesForReading.Select(a => a.def)); if (mainPsylinkSource == null) { @@ -518,7 +530,7 @@ public void ApplyInjuryToPawn(Pawn pawn, Injury injury) { } } else { - Hediff hediff = HediffMaker.MakeHediff(injury.Option.HediffDef, pawn, injury.bodyPartRecord); + Hediff hediff = HediffMaker.MakeHediff(injury.Option.HediffDef, pawn, injury.BodyPartRecord); hediff.Severity = injury.Severity; if (hediff is Hediff_ChemicalDependency chemicalDependency) { chemicalDependency.chemical = injury.Chemical; diff --git a/Source/PawnLoaderV3.cs b/Source/PawnLoaderV3.cs index b4d7a47..ea6c584 100644 --- a/Source/PawnLoaderV3.cs +++ b/Source/PawnLoaderV3.cs @@ -350,22 +350,20 @@ public PawnLoaderResult ConvertSaveRecordToCustomizedPawn(SaveRecordPawnV3 recor result.AddWarning("Could not add the implant because it could not find the recipe definition \"" + implantRecord.recipe + "\""); continue; } - bool found = false; - foreach (var p in recipeDef.appliedOnFixedBodyParts) { - if (p.defName.Equals(bodyPart.def.defName)) { - found = true; - break; - } + ImplantOption implantOption = healthOptions.FindImplantOptionThatAddsRecipeDefToBodyPart(recipeDef, bodyPart?.def); + if (implantOption != null) { + Implant implant = new Implant() { + Option = implantOption, + Recipe = recipeDef, + BodyPartRecord = bodyPart, + HediffDef = recipeDef.addsHediff, + }; + implant.label = implant.Label; + customizations.Implants.Add(implant); } - if (!found) { - result.AddWarning("Could not apply the saved implant recipe \"" + implantRecord.recipe + "\" to the body part \"" + bodyPart.def.defName + "\". Recipe does not support that part."); - continue; + else { + result.AddWarning("Could not add implant to pawn because no matching option was found for specified RecipeDef {" + implantRecord.recipe + "} and BodyPartDef {" + bodyPart?.def?.defName + "}"); } - Implant implant = new Implant(); - implant.BodyPartRecord = bodyPart; - implant.Recipe = recipeDef; - implant.label = implant.Label; - customizations.Implants.Add(implant); } } diff --git a/Source/PawnLoaderV5.cs b/Source/PawnLoaderV5.cs index 00fab32..828c237 100644 --- a/Source/PawnLoaderV5.cs +++ b/Source/PawnLoaderV5.cs @@ -616,44 +616,39 @@ public PawnLoaderResult ConvertSaveRecordToCustomizedPawn(SaveRecordPawnV5 recor result.AddWarning("Could not add the implant because it could not find the recipe definition \"" + implantRecord.recipe + "\""); continue; } - bool found = false; - foreach (var p in recipeDef.appliedOnFixedBodyParts) { - if (p.defName.Equals(bodyPart.def.defName)) { - found = true; - break; - } + ImplantOption implantOption = healthOptions.FindImplantOptionThatAddsRecipeDefToBodyPart(recipeDef, bodyPart?.def); + if (implantOption != null) { + Implant implant = new Implant() { + Option = implantOption, + BodyPartRecord = bodyPart, + Recipe = recipeDef, + HediffDef = recipeDef.addsHediff, + }; + // TODO: This looks weird; something to do with caching the label. Should rework it. + implant.label = implant.Label; + customizations.Implants.Add(implant); + Logger.Debug("Added implant customizations " + recipeDef?.defName); } - if (!found) { - if (recipeDef.appliedOnFixedBodyPartGroups != null) { - foreach (var g in recipeDef.appliedOnFixedBodyPartGroups) { - if (bodyPart.IsInGroup(g)) { - found = true; - break; - } - } - } - if (!found) { - result.AddWarning("Could not apply the saved implant recipe \"" + implantRecord.recipe + "\" to the body part \"" + bodyPart.def.defName + "\". Recipe does not support that part."); - continue; - } + else { + result.AddWarning("Could not add implant to pawn because no matching option was found for specified RecipeDef {" + implantRecord.recipe + "} and BodyPartDef {" + bodyPart?.def?.defName + "}"); } - Implant implant = new Implant() { - BodyPartRecord = bodyPart, - Recipe = recipeDef - }; - // TODO: This looks weird; something to do with caching the label. Should rework it. - implant.label = implant.Label; - customizations.Implants.Add(implant); - Logger.Debug("Added implant customizations " + recipeDef?.defName); } else if (implantRecord.hediffDef != null) { HediffDef hediffDef = DefDatabase.GetNamedSilentFail(implantRecord.hediffDef); if (hediffDef != null) { - Implant implant = new Implant(); - implant.BodyPartRecord = bodyPart; - implant.label = implant.Label; - implant.HediffDef = hediffDef; - customizations.Implants.Add(implant); + ImplantOption implantOption = healthOptions.FindImplantOptionThatAddsHediffDefToBodyPart(hediffDef, bodyPart?.def); + if (implantOption == null) { + result.AddWarning("Could not add implant to pawn because no matching option was found for specified HediffDef {" + implantRecord.hediffDef + "} and BodyPartDef {" + bodyPart?.def?.defName + "}"); + } + else { + Implant implant = new Implant(); + implant.Option = implantOption; + implant.BodyPartRecord = bodyPart; + implant.label = implant.Label; + implant.HediffDef = hediffDef; + implant.Severity = implantRecord.severity; + customizations.Implants.Add(implant); + } } else { result.AddWarning("Could not add implant to pawn because the specified HediffDef {" + implantRecord.hediffDef + "} for the implant was not found"); diff --git a/Source/PresetLoaderV5.cs b/Source/PresetLoaderV5.cs index 672aedf..e9caf62 100644 --- a/Source/PresetLoaderV5.cs +++ b/Source/PresetLoaderV5.cs @@ -230,9 +230,9 @@ protected void LoadParentChildGroups(SaveRecordPresetV5 preset, PresetLoaderResu } } } - if (group.Parents.Count > 0 && group.Children.Count > 0) { + + if (ValidateParentChildGroup(group)) { customizations.ParentChildGroups.Add(group); - Logger.Debug("Loaded parent child group"); } } ManagerRelationships.ReassignHiddenPawnIndices(); @@ -243,6 +243,21 @@ protected void LoadParentChildGroups(SaveRecordPresetV5 preset, PresetLoaderResu } } + protected bool ValidateParentChildGroup(ParentChildGroup group) { + if (group == null) { + return false; + } + int parentCount = group.Parents.CountAllowNull(); + int childCount = group.Children.CountAllowNull(); + if (parentCount == 0 && childCount < 2) { + return false; + } + if (childCount == 0) { + return false; + } + return true; + } + protected Dictionary ResolveIdeoMap(SaveRecordPresetV5 preset) { Dictionary ideoMap = new Dictionary(); Dictionary uniqueSaveRecordsToResolve = new Dictionary(); diff --git a/Source/ProviderHealthOptions.cs b/Source/ProviderHealthOptions.cs index 4e67d7e..46a920c 100644 --- a/Source/ProviderHealthOptions.cs +++ b/Source/ProviderHealthOptions.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Security.Cryptography; using System.Text; using UnityEngine; using Verse; using Verse.Sound; +using static HarmonyLib.Code; namespace EdB.PrepareCarefully { public class ProviderHealthOptions { @@ -82,12 +84,16 @@ protected void InitializeImplantRecipes(OptionsHealth options, ThingDef pawnThin if (def.addsHediff != null && ((def.appliedOnFixedBodyParts != null && def.appliedOnFixedBodyParts.Count > 0) || (def.appliedOnFixedBodyPartGroups != null && def.appliedOnFixedBodyPartGroups.Count > 0)) && (def.recipeUsers.NullOrEmpty() || def.recipeUsers.Contains(pawnThingDef))) { + //Logger.Debug("Adding implant recipe: " + def.defName); return true; } else { + //Logger.Debug("Excluding implant recipe: " + def.defName); return false; } })); + + // Remove duplicates: recipes that apply the same hediff on the same body parts. HashSet recipeHashes = new HashSet(); @@ -128,6 +134,43 @@ protected void InitializeImplantRecipes(OptionsHealth options, ThingDef pawnThin } } } + + // Add options for thing definitions that have an install implant comp + Dictionary implantOptionLookup = new Dictionary(); + foreach (var def in DefDatabase.AllDefs.Where(d => d.HasComp())) { + var useEffectInstallImplant = def.GetCompProperties(); + var usable = def.GetCompProperties(); + if (useEffectInstallImplant != null && usable != null) { + string hediffDefName = useEffectInstallImplant.hediffDef?.defName; + if (hediffDefName == null) { + continue; + } + HediffDef hediffDef = useEffectInstallImplant.hediffDef; + // Exclude psychic amplifier because that's added as an injury and requires special handling + // to avoid the default behavior that adds a random ability + if (hediffDef == HediffDefOf.PsychicAmplifier) { + continue; + } + if (!implantOptionLookup.TryGetValue(hediffDefName, out ImplantOption option)) { + option = new ImplantOption() { + HediffDef = hediffDef, + ThingDef = def, + BodyPartDefs = new HashSet(), + Dependency = usable.userMustHaveHediff, + }; + if (typeof(Hediff_Level).IsAssignableFrom(hediffDef.hediffClass)) { + option.MinSeverity = hediffDef.minSeverity > 0 ? hediffDef.minSeverity : 1; + option.MaxSeverity = hediffDef.maxSeverity; + Logger.Debug(string.Format("Adding option {0} with severity {1}-{2} from thingDef {3}", hediffDef.defName, option.MinSeverity, option.MaxSeverity, def.defName)); + } + implantOptionLookup.Add(hediffDefName, option); + } + option.BodyPartDefs.Add(useEffectInstallImplant.bodyPart); + } + } + foreach (var value in implantOptionLookup.Values) { + options.AddImplantOption(value); + } } protected bool InitializeHediffGivenByUseEffect(OptionsHealth options, CompProperties_UseEffectInstallImplant useEffect) { diff --git a/Source/RelationshipBuilder.cs b/Source/RelationshipBuilder.cs index dff18db..d93deb0 100644 --- a/Source/RelationshipBuilder.cs +++ b/Source/RelationshipBuilder.cs @@ -10,15 +10,13 @@ namespace EdB.PrepareCarefully { public class RelationshipBuilder { private List relationships; private List parentChildGroups; - FieldInfo directRelationsField = null; - FieldInfo pawnsWithField = null; + private List pawns; private List compatibilityPool = new List(); - public RelationshipBuilder(List relationships, List parentChildGroups) { - directRelationsField = typeof(Pawn_RelationsTracker).GetField("directRelations", BindingFlags.Instance | BindingFlags.NonPublic); - pawnsWithField = typeof(Pawn_RelationsTracker).GetField("pawnsWithDirectRelationsWithMe", BindingFlags.Instance | BindingFlags.NonPublic); + public RelationshipBuilder(IEnumerable pawns, List relationships, List parentChildGroups) { this.relationships = relationships; this.parentChildGroups = parentChildGroups; + this.pawns = pawns.ToList(); int compatibilityPoolSize = Mathf.Max(Mathf.Min(relationships.Count * 6, 12), 50); this.FillCompatibilityPool(compatibilityPoolSize); @@ -27,6 +25,9 @@ public RelationshipBuilder(List relationships, List Build() { // These include all the pawns that have relationships with them. HashSet relevantPawns = new HashSet(); + foreach (var pawn in pawns) { + relevantPawns.Add(pawn); + } foreach (var rel in relationships) { relevantPawns.Add(rel.Source); relevantPawns.Add(rel.Target); @@ -50,7 +51,7 @@ public List Build() { } } - // Go through each starting pawn and evaluate that any existing relationships need to be remain. + // Go through each starting pawn and evaluate that any existing relationships need to remain. // If not, remove them. PawnRelationDef parentDef = PawnRelationDefOf.Parent; foreach (var pawn in relevantPawns) { @@ -61,10 +62,10 @@ public List Build() { if (relationCount == 0) { continue; } - Logger.Debug("Checking existing relations for " + pawn.Pawn.LabelCap + ", " + relationCount); + Logger.Debug("Checking existing relations for " + pawn.Pawn.LabelShort + ", " + relationCount); for (int i = 0; i < relationCount; i++) { var r = pawn.Pawn.relations.DirectRelations[i]; - Logger.Debug(string.Format(" [{0}]: relationship {1} between {2} and {3}", i, r.def.defName, pawn.Pawn.LabelShortCap, r.otherPawn.LabelShortCap)); + Logger.Debug(string.Format(" [{0}]: relationship {1} between {2} and {3}", i, r.def.defName, pawn.Pawn.LabelShort, r.otherPawn.LabelShort)); } var validity = new List(Enumerable.Range(0, relationCount).Select(i => false)); foreach (var group in parentChildGroups) { @@ -74,7 +75,7 @@ public List Build() { return r.def == parentDef && r.otherPawn == parent.Pawn && child.Pawn == pawn.Pawn; }); if (index != -1) { - Logger.Debug(string.Format(" Found direct parent relation between child {0} and parent {1} at index: {2}", pawn.Pawn.LabelShortCap, parent.Pawn.LabelShortCap, index)); + Logger.Debug(string.Format(" Found direct parent relation between child {0} and parent {1} at index: {2}", pawn.Pawn.LabelShort, parent.Pawn.LabelShort, index)); validity[index] = true; } } @@ -87,20 +88,21 @@ public List Build() { || (pawn == relationship.Source && r.otherPawn == relationship.Target.Pawn)); }); if (index != -1) { - Logger.Debug(string.Format(" Found direct relation {0} between pawn {1} and target {2} at index: {3}", relationship.Def.defName, pawn.Pawn.LabelShortCap, relationship.Source.Pawn.LabelShortCap, index)); + Logger.Debug(string.Format(" Found direct relation {0} between pawn {1} and target {2} at index: {3}", relationship.Def.defName, pawn.Pawn.LabelShort, relationship.Source.Pawn.LabelShort, index)); validity[index] = true; } } - Logger.Debug(" " + string.Join(", ", validity.Select(v => v ? "1" : "0"))); + Logger.Debug(" Relationship validity: [" + string.Join(", ", validity.Select(v => v ? "valid" : "invalid")) + "]"); List relationsToRemove = new List(); for (int i=0; i.GetNamedSilentFail(def.defName); diff --git a/Source/Style.cs b/Source/Style.cs index 0550d72..8ae62e7 100644 --- a/Source/Style.cs +++ b/Source/Style.cs @@ -26,6 +26,8 @@ public static class Style { public static Color ColorTabViewBackground = new Color(42f / 255f, 43f / 255f, 44f / 255f); public static Color ColorWindowBackground = new Color(21f / 255f, 25f / 255f, 29f / 255f); + public static Color ColorCheckmark = new Color(38f / 255f, 217f / 255f, 38f / 255f); + public static Color ColorCheckmarkForcedSelection = ColorCheckmark * 0.8f; public static readonly float DialogHeaderHeight = 36; public static readonly Vector2 DialogMargin = new Vector2(10, 18); diff --git a/Source/Textures.cs b/Source/Textures.cs index 56e324a..d5d33eb 100644 --- a/Source/Textures.cs +++ b/Source/Textures.cs @@ -66,6 +66,8 @@ public static class Textures { public static Texture2D TextureIconWarning; public static Texture2D TextureFavoriteColor; public static Texture2D TextureIdeoColor; + public static Texture2D TextureCheckmark; + public static Texture2D TextureCheckmarkForcedSelection; public static Texture2D TextureWhite { get { @@ -161,6 +163,9 @@ private static void LoadTextures() { TextureFavoriteColor = ContentFinder.Get("UI/Icons/ColorSelector/ColorFavourite", true); TextureIdeoColor = ContentFinder.Get("UI/Icons/ColorSelector/ColorIdeology", true); + TextureCheckmark = ContentFinder.Get("EdB/PrepareCarefully/Checkmark", true); + TextureCheckmarkForcedSelection = ContentFinder.Get("EdB/PrepareCarefully/CheckmarkForcedSelection", true); + loaded = true; } } diff --git a/Source/UtilityEquipmentSpawnType.cs b/Source/UtilityEquipmentSpawnType.cs index 81f549b..bdb54d4 100644 --- a/Source/UtilityEquipmentSpawnType.cs +++ b/Source/UtilityEquipmentSpawnType.cs @@ -20,6 +20,9 @@ public static string LabelForSpawnTypeHeader(EquipmentSpawnType spawnType) { else if (spawnType == EquipmentSpawnType.Possession) { return "EdB.PC.Equipment.SelectedEquipment.SpawnType.Possession".Translate(); } + else if (spawnType == EquipmentSpawnType.Mech) { + return "EdB.PC.Equipment.SelectedEquipment.SpawnType.Mech".Translate(); + } else { return ""; } diff --git a/Source/Version5/SaveRecordImplantV5.cs b/Source/Version5/SaveRecordImplantV5.cs index 50035ec..cbf4014 100644 --- a/Source/Version5/SaveRecordImplantV5.cs +++ b/Source/Version5/SaveRecordImplantV5.cs @@ -11,6 +11,7 @@ public class SaveRecordImplantV5 : IExposable { public int? bodyPartIndex = null; public string recipe = null; public string hediffDef = null; + public float severity = 0f; public SaveRecordImplantV5() { } @@ -19,6 +20,7 @@ public SaveRecordImplantV5(Implant option) { this.bodyPart = option.BodyPartRecord.def.defName; this.recipe = option.Recipe != null ? option.Recipe.defName : null; this.hediffDef = option?.Hediff?.def?.defName; + this.severity = option?.Severity ?? 0f; } public void ExposeData() { @@ -26,6 +28,7 @@ public void ExposeData() { Scribe_Values.Look(ref this.bodyPartIndex, "bodyPartIndex", null, false); Scribe_Values.Look(ref recipe, "recipe", null, false); Scribe_Values.Look(ref hediffDef, "hediff", null, false); + Scribe_Values.Look(ref severity, "severity", 0f, false); } } }