diff --git a/Resources/CHANGELOG.txt b/Resources/CHANGELOG.txt
index e97b7e0..bc4d5e1 100644
--- a/Resources/CHANGELOG.txt
+++ b/Resources/CHANGELOG.txt
@@ -17,6 +17,8 @@
- The age generation curve in a race's thing definition now determines the
minimum and maximum age allowed for a starting character. This provides
better compatibility with the Baby and Children mod.
+ - Shift-click to skip the confirmation dialog when deleting a pawn.
+ - Added support for more modded items in the equipment selection view.
_____________________________________________________________________________
diff --git a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml
index 8298ef3..d29a1fd 100644
--- a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml
+++ b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml
@@ -6,6 +6,7 @@
+
@@ -278,7 +279,6 @@ This is not the case for faction leaders. If you make this character a faction
No injuries, conditions or implants
Select a location
Select a severity level
- {0}: <color={2}>{1}</color>
None
None of your pawns are capable of the following work types that are considered to be required by the vanilla game.
diff --git a/Source/CostCalculator.cs b/Source/CostCalculator.cs
index d9692ee..d260f16 100644
--- a/Source/CostCalculator.cs
+++ b/Source/CostCalculator.cs
@@ -1,334 +1,334 @@
-using RimWorld;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using UnityEngine;
-using Verse;
-
-namespace EdB.PrepareCarefully {
- public class ColonistCostDetails {
- public string name;
- public double total = 0;
- public double passionCount = 0;
- public double passions = 0;
- public double traits = 0;
- public double apparel = 0;
- public double bionics = 0;
- public double animals = 0;
- public double marketValue = 0;
- public void Clear() {
- total = 0;
- passions = 0;
- traits = 0;
- apparel = 0;
- bionics = 0;
- animals = 0;
- marketValue = 0;
- }
- public void ComputeTotal() {
- total = Math.Ceiling(passions + traits + apparel + bionics + marketValue + animals);
- }
- public void Multiply(double amount) {
- passions = Math.Ceiling(passions * amount);
- traits = Math.Ceiling(traits * amount);
- marketValue = Math.Ceiling(marketValue * amount);
- ComputeTotal();
- }
- }
-
- public class CostDetails {
- public double total = 0;
- public List colonistDetails = new List();
- public double colonists = 0;
- public double colonistApparel = 0;
- public double colonistBionics = 0;
- public double equipment = 0;
- public double animals = 0;
- public Pawn pawn = null;
- public void Clear(int colonistCount) {
- total = 0;
- equipment = 0;
- animals = 0;
- colonists = 0;
- colonistApparel = 0;
- colonistBionics = 0;
- int listSize = colonistDetails.Count;
- if (colonistCount != listSize) {
- if (colonistCount < listSize) {
- int diff = listSize - colonistCount;
- colonistDetails.RemoveRange(colonistDetails.Count - diff, diff);
- }
- else {
- int diff = colonistCount - listSize;
- for (int i = 0; i < diff; i++) {
- colonistDetails.Add(new ColonistCostDetails());
- }
- }
- }
- }
- public void ComputeTotal() {
- equipment = Math.Ceiling(equipment);
- animals = Math.Ceiling(animals);
- total = equipment + animals;
- foreach (var cost in colonistDetails) {
- total += cost.total;
- colonists += cost.total;
- colonistApparel += cost.apparel;
- colonistBionics += cost.bionics;
- }
- total = Math.Ceiling(total);
- colonists = Math.Ceiling(colonists);
- colonistApparel = Math.Ceiling(colonistApparel);
- colonistBionics = Math.Ceiling(colonistBionics);
- }
- }
-
- public class CostCalculator {
- protected HashSet freeApparel = new HashSet();
- protected HashSet cheapApparel = new HashSet();
-
- public CostCalculator() {
- cheapApparel.Add("Apparel_Pants");
- cheapApparel.Add("Apparel_BasicShirt");
- cheapApparel.Add("Apparel_Jacket");
- }
-
- public void Calculate(CostDetails cost, List pawns, List equipment, List animals) {
- cost.Clear(pawns.Count);
-
- int i = 0;
- foreach (var pawn in pawns) {
- if (pawn.Type == CustomPawnType.Colonist) {
- CalculatePawnCost(cost.colonistDetails[i++], pawn);
- }
- }
- foreach (var e in equipment) {
- cost.equipment += CalculateEquipmentCost(e);
- }
- cost.ComputeTotal();
- }
-
- public void CalculatePawnCost(ColonistCostDetails cost, CustomPawn pawn) {
- cost.Clear();
- cost.name = pawn.NickName;
-
- // Start with the market value plus a bit of a mark-up.
- cost.marketValue = pawn.Pawn.MarketValue;
- cost.marketValue += 300;
-
- // Calculate passion cost. Each passion above 8 makes all passions
- // cost more. Minor passion counts as one passion. Major passion
- // counts as 3.
- double skillCount = pawn.currentPassions.Keys.Count();
- double passionLevelCount = 0;
- double passionLevelCost = 20;
- double passionateSkillCount = 0;
- foreach (SkillDef def in pawn.currentPassions.Keys) {
- Passion passion = pawn.currentPassions[def];
- int level = pawn.GetSkillLevel(def);
-
- if (passion == Passion.Major) {
- passionLevelCount += 3.0;
- passionateSkillCount += 1.0;
- }
- else if (passion == Passion.Minor) {
- passionLevelCount += 1.0;
- passionateSkillCount += 1.0;
- }
- }
- double levelCost = passionLevelCost;
- if (passionLevelCount > 8) {
- double penalty = passionLevelCount - 8;
- levelCost += penalty * 0.4;
- }
- cost.marketValue += levelCost * passionLevelCount;
-
- // Calculate trait cost.
- if (pawn.TraitCount > Constraints.MaxVanillaTraits) {
- int extraTraitCount = pawn.TraitCount - Constraints.MaxVanillaTraits;
- double extraTraitCost = 100;
- for (int i=0; i< extraTraitCount; i++) {
- cost.marketValue += extraTraitCost;
- extraTraitCost = Math.Ceiling(extraTraitCost * 2.5);
- }
- }
-
- // Calculate cost of worn apparel.
- foreach (var layer in PrepareCarefully.Instance.Providers.PawnLayers.GetLayersForPawn(pawn)) {
- if (layer.Apparel) {
- var def = pawn.GetAcceptedApparel(layer);
- if (def == null) {
- continue;
- }
- EquipmentKey key = new EquipmentKey();
- key.ThingDef = def;
- key.StuffDef = pawn.GetSelectedStuff(layer);
- EquipmentRecord record = PrepareCarefully.Instance.EquipmentDatabase.Find(key);
- if (record == null) {
- continue;
- }
- EquipmentSelection selection = new EquipmentSelection(record, 1);
- double c = CalculateEquipmentCost(selection);
- if (def != null) {
- // TODO: Discounted materials should be based on the faction, not hard-coded.
- // TODO: Should we continue with the discounting?
- if (key.StuffDef != null) {
- if (key.StuffDef.defName == "Synthread") {
- if (freeApparel.Contains(key.ThingDef.defName)) {
- c = 0;
- }
- else if (cheapApparel.Contains(key.ThingDef.defName)) {
- c = c * 0.15d;
- }
- }
- }
- }
- cost.apparel += c;
- }
- }
-
- // Calculate cost for any materials needed for implants.
- OptionsHealth healthOptions = PrepareCarefully.Instance.Providers.Health.GetOptions(pawn);
- foreach (Implant option in pawn.Implants) {
-
- // Check if there are any ancestor parts that override the selection.
- UniqueBodyPart uniquePart = healthOptions.FindBodyPartsForRecord(option.BodyPartRecord);
- if (uniquePart == null) {
- Log.Warning("Prepare Carefully could not find body part record when computing the cost of an implant: " + option.BodyPartRecord.def.defName);
- continue;
- }
- if (pawn.AtLeastOneImplantedPart(uniquePart.Ancestors.Select((UniqueBodyPart p) => { return p.Record; }))) {
- continue;
- }
-
- // Figure out the cost of the part replacement based on its recipe's ingredients.
- if (option.recipe != null) {
- RecipeDef def = option.recipe;
- foreach (IngredientCount amount in def.ingredients) {
- int count = 0;
- double totalCost = 0;
- bool skip = false;
- foreach (ThingDef ingredientDef in amount.filter.AllowedThingDefs) {
- if (ingredientDef.IsMedicine) {
- skip = true;
- break;
- }
- count++;
- EquipmentRecord entry = PrepareCarefully.Instance.EquipmentDatabase.LookupEquipmentRecord(new EquipmentKey(ingredientDef, null));
- if (entry != null) {
- totalCost += entry.cost * (double)amount.GetBaseCount();
- }
- }
- if (skip || count == 0) {
- continue;
- }
- cost.bionics += (int)(totalCost / (double)count);
- }
- }
- }
-
- cost.apparel = Math.Ceiling(cost.apparel);
- cost.bionics = Math.Ceiling(cost.bionics);
-
- // Use a multiplier to balance pawn cost vs. equipment cost.
- // Disabled for now.
- cost.Multiply(1.0);
-
- cost.ComputeTotal();
- }
-
- public double CalculateEquipmentCost(EquipmentSelection equipment) {
- EquipmentRecord entry = PrepareCarefully.Instance.EquipmentDatabase.LookupEquipmentRecord(equipment.Key);
- if (entry != null) {
- return (double)equipment.Count * entry.cost;
- }
- else {
- return 0;
- }
- }
-
- /*
- public double CalculateAnimalCost(SelectedAnimal animal) {
- AnimalRecord record = PrepareCarefully.Instance.AnimalDatabase.FindAnimal(animal.Key);
- if (record != null) {
- return (double)animal.Count * record.Cost;
- }
- else {
- return 0;
- }
- }
- */
-
- public double GetBaseThingCost(ThingDef def, ThingDef stuffDef) {
- if (def == null) {
- Log.Warning("Prepare Carefully is trying to calculate the cost of a null ThingDef");
- return 0;
- }
- if (def.BaseMarketValue > 0) {
- if (stuffDef == null) {
- return def.BaseMarketValue;
- }
- else {
- // TODO:
- // Should look at ThingMaker.MakeThing() to decide which validations we need to do
- // before calling that method. That method doesn't do null checks everywhere, so we
- // may need to do those validations ourselves to avoid null pointer exceptions.
- // Should re-evaluate for each new release and then update the todo comment with the next
- // alpha version.
- if (def.thingClass == null) {
- Log.Warning("Prepare Carefully trying to calculate the cost of a ThingDef with null thingClass: " + def.defName);
- return 0;
- }
- if (def.MadeFromStuff && stuffDef == null) {
- Log.Warning("Prepare Carefully trying to calculate the cost of a \"made-from-stuff\" ThingDef without specifying any stuff: " + def.defName);
- return 0;
- }
-
- try {
- // TODO: Creating an instance of a thing may not be the best way to calculate
- // its market value. It may be considered a relatively expensive operation,
- // especially when a lot of mods are enabled. There may be a lower-level set of
- // methods in the vanilla codebase that could be called. Should investigate.
- Thing thing = ThingMaker.MakeThing(def, stuffDef);
- if (thing == null) {
- Log.Warning("Prepare Carefully failed when calling MakeThing(" + def.defName + ", ...) to calculate a ThingDef's market value");
- return 0;
- }
- return thing.MarketValue;
- }
- catch (Exception e) {
- Log.Warning("Prepare Carefully failed to calculate the cost of a ThingDef (" + def.defName + "): ");
- Log.Warning(e.ToString());
- return 0;
- }
- }
- }
- else {
- return 0;
- }
- }
-
- public double CalculateStackCost(ThingDef def, ThingDef stuffDef, double baseCost) {
- double cost = baseCost;
-
- if (def.MadeFromStuff) {
- if (def.IsApparel) {
- cost = cost * 1;
- }
- else {
- cost = cost * 0.5;
- }
- }
-
- if (def.IsRangedWeapon) {
- cost = cost * 2;
- }
-
- //cost = cost * 1.25;
- cost = Math.Round(cost, 1);
-
- return cost;
- }
- }
-}
-
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+
+namespace EdB.PrepareCarefully {
+ public class ColonistCostDetails {
+ public string name;
+ public double total = 0;
+ public double passionCount = 0;
+ public double passions = 0;
+ public double traits = 0;
+ public double apparel = 0;
+ public double bionics = 0;
+ public double animals = 0;
+ public double marketValue = 0;
+ public void Clear() {
+ total = 0;
+ passions = 0;
+ traits = 0;
+ apparel = 0;
+ bionics = 0;
+ animals = 0;
+ marketValue = 0;
+ }
+ public void ComputeTotal() {
+ total = Math.Ceiling(passions + traits + apparel + bionics + marketValue + animals);
+ }
+ public void Multiply(double amount) {
+ passions = Math.Ceiling(passions * amount);
+ traits = Math.Ceiling(traits * amount);
+ marketValue = Math.Ceiling(marketValue * amount);
+ ComputeTotal();
+ }
+ }
+
+ public class CostDetails {
+ public double total = 0;
+ public List colonistDetails = new List();
+ public double colonists = 0;
+ public double colonistApparel = 0;
+ public double colonistBionics = 0;
+ public double equipment = 0;
+ public double animals = 0;
+ public Pawn pawn = null;
+ public void Clear(int colonistCount) {
+ total = 0;
+ equipment = 0;
+ animals = 0;
+ colonists = 0;
+ colonistApparel = 0;
+ colonistBionics = 0;
+ int listSize = colonistDetails.Count;
+ if (colonistCount != listSize) {
+ if (colonistCount < listSize) {
+ int diff = listSize - colonistCount;
+ colonistDetails.RemoveRange(colonistDetails.Count - diff, diff);
+ }
+ else {
+ int diff = colonistCount - listSize;
+ for (int i = 0; i < diff; i++) {
+ colonistDetails.Add(new ColonistCostDetails());
+ }
+ }
+ }
+ }
+ public void ComputeTotal() {
+ equipment = Math.Ceiling(equipment);
+ animals = Math.Ceiling(animals);
+ total = equipment + animals;
+ foreach (var cost in colonistDetails) {
+ total += cost.total;
+ colonists += cost.total;
+ colonistApparel += cost.apparel;
+ colonistBionics += cost.bionics;
+ }
+ total = Math.Ceiling(total);
+ colonists = Math.Ceiling(colonists);
+ colonistApparel = Math.Ceiling(colonistApparel);
+ colonistBionics = Math.Ceiling(colonistBionics);
+ }
+ }
+
+ public class CostCalculator {
+ protected HashSet freeApparel = new HashSet();
+ protected HashSet cheapApparel = new HashSet();
+
+ public CostCalculator() {
+ cheapApparel.Add("Apparel_Pants");
+ cheapApparel.Add("Apparel_BasicShirt");
+ cheapApparel.Add("Apparel_Jacket");
+ }
+
+ public void Calculate(CostDetails cost, List pawns, List equipment, List animals) {
+ cost.Clear(pawns.Where(pawn => pawn.Type == CustomPawnType.Colonist).Count());
+
+ int i = 0;
+ foreach (var pawn in pawns) {
+ if (pawn.Type == CustomPawnType.Colonist) {
+ CalculatePawnCost(cost.colonistDetails[i++], pawn);
+ }
+ }
+ foreach (var e in equipment) {
+ cost.equipment += CalculateEquipmentCost(e);
+ }
+ cost.ComputeTotal();
+ }
+
+ public void CalculatePawnCost(ColonistCostDetails cost, CustomPawn pawn) {
+ cost.Clear();
+ cost.name = pawn.NickName;
+
+ // Start with the market value plus a bit of a mark-up.
+ cost.marketValue = pawn.Pawn.MarketValue;
+ cost.marketValue += 300;
+
+ // Calculate passion cost. Each passion above 8 makes all passions
+ // cost more. Minor passion counts as one passion. Major passion
+ // counts as 3.
+ double skillCount = pawn.currentPassions.Keys.Count();
+ double passionLevelCount = 0;
+ double passionLevelCost = 20;
+ double passionateSkillCount = 0;
+ foreach (SkillDef def in pawn.currentPassions.Keys) {
+ Passion passion = pawn.currentPassions[def];
+ int level = pawn.GetSkillLevel(def);
+
+ if (passion == Passion.Major) {
+ passionLevelCount += 3.0;
+ passionateSkillCount += 1.0;
+ }
+ else if (passion == Passion.Minor) {
+ passionLevelCount += 1.0;
+ passionateSkillCount += 1.0;
+ }
+ }
+ double levelCost = passionLevelCost;
+ if (passionLevelCount > 8) {
+ double penalty = passionLevelCount - 8;
+ levelCost += penalty * 0.4;
+ }
+ cost.marketValue += levelCost * passionLevelCount;
+
+ // Calculate trait cost.
+ if (pawn.TraitCount > Constraints.MaxVanillaTraits) {
+ int extraTraitCount = pawn.TraitCount - Constraints.MaxVanillaTraits;
+ double extraTraitCost = 100;
+ for (int i=0; i< extraTraitCount; i++) {
+ cost.marketValue += extraTraitCost;
+ extraTraitCost = Math.Ceiling(extraTraitCost * 2.5);
+ }
+ }
+
+ // Calculate cost of worn apparel.
+ foreach (var layer in PrepareCarefully.Instance.Providers.PawnLayers.GetLayersForPawn(pawn)) {
+ if (layer.Apparel) {
+ var def = pawn.GetAcceptedApparel(layer);
+ if (def == null) {
+ continue;
+ }
+ EquipmentKey key = new EquipmentKey();
+ key.ThingDef = def;
+ key.StuffDef = pawn.GetSelectedStuff(layer);
+ EquipmentRecord record = PrepareCarefully.Instance.EquipmentDatabase.Find(key);
+ if (record == null) {
+ continue;
+ }
+ EquipmentSelection selection = new EquipmentSelection(record, 1);
+ double c = CalculateEquipmentCost(selection);
+ if (def != null) {
+ // TODO: Discounted materials should be based on the faction, not hard-coded.
+ // TODO: Should we continue with the discounting?
+ if (key.StuffDef != null) {
+ if (key.StuffDef.defName == "Synthread") {
+ if (freeApparel.Contains(key.ThingDef.defName)) {
+ c = 0;
+ }
+ else if (cheapApparel.Contains(key.ThingDef.defName)) {
+ c = c * 0.15d;
+ }
+ }
+ }
+ }
+ cost.apparel += c;
+ }
+ }
+
+ // Calculate cost for any materials needed for implants.
+ OptionsHealth healthOptions = PrepareCarefully.Instance.Providers.Health.GetOptions(pawn);
+ foreach (Implant option in pawn.Implants) {
+
+ // Check if there are any ancestor parts that override the selection.
+ UniqueBodyPart uniquePart = healthOptions.FindBodyPartsForRecord(option.BodyPartRecord);
+ if (uniquePart == null) {
+ Log.Warning("Prepare Carefully could not find body part record when computing the cost of an implant: " + option.BodyPartRecord.def.defName);
+ continue;
+ }
+ if (pawn.AtLeastOneImplantedPart(uniquePart.Ancestors.Select((UniqueBodyPart p) => { return p.Record; }))) {
+ continue;
+ }
+
+ // Figure out the cost of the part replacement based on its recipe's ingredients.
+ if (option.recipe != null) {
+ RecipeDef def = option.recipe;
+ foreach (IngredientCount amount in def.ingredients) {
+ int count = 0;
+ double totalCost = 0;
+ bool skip = false;
+ foreach (ThingDef ingredientDef in amount.filter.AllowedThingDefs) {
+ if (ingredientDef.IsMedicine) {
+ skip = true;
+ break;
+ }
+ count++;
+ EquipmentRecord entry = PrepareCarefully.Instance.EquipmentDatabase.LookupEquipmentRecord(new EquipmentKey(ingredientDef, null));
+ if (entry != null) {
+ totalCost += entry.cost * (double)amount.GetBaseCount();
+ }
+ }
+ if (skip || count == 0) {
+ continue;
+ }
+ cost.bionics += (int)(totalCost / (double)count);
+ }
+ }
+ }
+
+ cost.apparel = Math.Ceiling(cost.apparel);
+ cost.bionics = Math.Ceiling(cost.bionics);
+
+ // Use a multiplier to balance pawn cost vs. equipment cost.
+ // Disabled for now.
+ cost.Multiply(1.0);
+
+ cost.ComputeTotal();
+ }
+
+ public double CalculateEquipmentCost(EquipmentSelection equipment) {
+ EquipmentRecord entry = PrepareCarefully.Instance.EquipmentDatabase.LookupEquipmentRecord(equipment.Key);
+ if (entry != null) {
+ return (double)equipment.Count * entry.cost;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ /*
+ public double CalculateAnimalCost(SelectedAnimal animal) {
+ AnimalRecord record = PrepareCarefully.Instance.AnimalDatabase.FindAnimal(animal.Key);
+ if (record != null) {
+ return (double)animal.Count * record.Cost;
+ }
+ else {
+ return 0;
+ }
+ }
+ */
+
+ public double GetBaseThingCost(ThingDef def, ThingDef stuffDef) {
+ if (def == null) {
+ Log.Warning("Prepare Carefully is trying to calculate the cost of a null ThingDef");
+ return 0;
+ }
+ if (def.BaseMarketValue > 0) {
+ if (stuffDef == null) {
+ return def.BaseMarketValue;
+ }
+ else {
+ // TODO:
+ // Should look at ThingMaker.MakeThing() to decide which validations we need to do
+ // before calling that method. That method doesn't do null checks everywhere, so we
+ // may need to do those validations ourselves to avoid null pointer exceptions.
+ // Should re-evaluate for each new release and then update the todo comment with the next
+ // alpha version.
+ if (def.thingClass == null) {
+ Log.Warning("Prepare Carefully trying to calculate the cost of a ThingDef with null thingClass: " + def.defName);
+ return 0;
+ }
+ if (def.MadeFromStuff && stuffDef == null) {
+ Log.Warning("Prepare Carefully trying to calculate the cost of a \"made-from-stuff\" ThingDef without specifying any stuff: " + def.defName);
+ return 0;
+ }
+
+ try {
+ // TODO: Creating an instance of a thing may not be the best way to calculate
+ // its market value. It may be considered a relatively expensive operation,
+ // especially when a lot of mods are enabled. There may be a lower-level set of
+ // methods in the vanilla codebase that could be called. Should investigate.
+ Thing thing = ThingMaker.MakeThing(def, stuffDef);
+ if (thing == null) {
+ Log.Warning("Prepare Carefully failed when calling MakeThing(" + def.defName + ", ...) to calculate a ThingDef's market value");
+ return 0;
+ }
+ return thing.MarketValue;
+ }
+ catch (Exception e) {
+ Log.Warning("Prepare Carefully failed to calculate the cost of a ThingDef (" + def.defName + "): ");
+ Log.Warning(e.ToString());
+ return 0;
+ }
+ }
+ }
+ else {
+ return 0;
+ }
+ }
+
+ public double CalculateStackCost(ThingDef def, ThingDef stuffDef, double baseCost) {
+ double cost = baseCost;
+
+ if (def.MadeFromStuff) {
+ if (def.IsApparel) {
+ cost = cost * 1;
+ }
+ else {
+ cost = cost * 0.5;
+ }
+ }
+
+ if (def.IsRangedWeapon) {
+ cost = cost * 2;
+ }
+
+ //cost = cost * 1.25;
+ cost = Math.Round(cost, 1);
+
+ return cost;
+ }
+ }
+}
+
diff --git a/Source/EquipmentDatabase.cs b/Source/EquipmentDatabase.cs
index 13d045f..4381569 100644
--- a/Source/EquipmentDatabase.cs
+++ b/Source/EquipmentDatabase.cs
@@ -29,7 +29,6 @@ public class EquipmentDatabase {
protected ThingCategoryDef thingCategorySweetMeals = null;
protected ThingCategoryDef thingCategoryMeatRaw = null;
- protected ThingCategoryDef thingCategoryBodyPartsArtificial = null;
public EquipmentDatabase() {
types.Add(TypeResources);
@@ -42,7 +41,6 @@ public EquipmentDatabase() {
thingCategorySweetMeals = DefDatabase.GetNamedSilentFail("SweetMeals");
thingCategoryMeatRaw = DefDatabase.GetNamedSilentFail("MeatRaw");
- thingCategoryBodyPartsArtificial = DefDatabase.GetNamedSilentFail("BodyPartsArtificial");
}
public LoadingState LoadingProgress { get; protected set; } = new LoadingState();
@@ -115,7 +113,7 @@ protected void NextPhase() {
protected void CountDefs() {
for (int i = 0; i < LoadingProgress.defsToCountPerFrame; i++) {
if (!LoadingProgress.enumerator.MoveNext()) {
- Log.Message("Prepare Carefully finished counting " + LoadingProgress.defCount + " thing definition(s)");
+ //Log.Message("Prepare Carefully finished counting " + LoadingProgress.defCount + " thing definition(s)");
NextPhase();
return;
}
@@ -223,6 +221,20 @@ public void BuildEquipmentLists() {
}
*/
+ private bool FoodTypeIsClassifiedAsFood(ThingDef def) {
+ int foodTypes = (int)def.ingestible.foodType;
+ if ((foodTypes & (int)FoodTypeFlags.Liquor) > 0) {
+ return true;
+ }
+ if ((foodTypes & (int)FoodTypeFlags.Meal) > 0) {
+ return true;
+ }
+ if ((foodTypes & (int)FoodTypeFlags.VegetableOrFruit) > 0) {
+ return true;
+ }
+ return false;
+ }
+
public EquipmentType ClassifyThingDef(ThingDef def) {
if (def.mote != null) {
return TypeDiscard;
@@ -245,54 +257,51 @@ public EquipmentType ClassifyThingDef(ThingDef def) {
if (def.weaponTags != null && def.weaponTags.Count > 0 && def.IsWeapon) {
return TypeWeapons;
}
+ if (BelongsToCategoryContaining(def, "Weapon")) {
+ return TypeWeapons;
+ }
if (def.IsApparel && !def.destroyOnDrop) {
return TypeApparel;
}
- if (def.defName.StartsWith("MechSerum")) {
- return TypeMedical;
+ if (BelongsToCategory(def, "Foods")) {
+ return TypeFood;
}
- if (def.CountAsResource) {
- if (def.IsShell) {
- return TypeWeapons;
- }
- if (def.IsDrug || (def.statBases != null && def.IsMedicine)) {
- if (def.ingestible != null) {
- if (def.thingCategories != null) {
- if (thingCategorySweetMeals != null && def.thingCategories.Contains(thingCategorySweetMeals)) {
- return TypeFood;
- }
- }
- int foodTypes = (int) def.ingestible.foodType;
- bool isFood = ((foodTypes & (int)FoodTypeFlags.Liquor) > 0) | ((foodTypes & (int)FoodTypeFlags.Meal) > 0);
- if (isFood) {
- return TypeFood;
- }
- }
- return TypeMedical;
- }
+ // Ingestibles
+ if (def.IsDrug || (def.statBases != null && def.IsMedicine)) {
if (def.ingestible != null) {
- if (thingCategoryMeatRaw != null && def.thingCategories != null && def.thingCategories.Contains(thingCategoryMeatRaw)) {
+ if (BelongsToCategory(def, thingCategorySweetMeals)) {
return TypeFood;
}
- if (def.ingestible.drugCategory == DrugCategory.Medical) {
- return TypeMedical;
- }
- if (def.ingestible.preferability == FoodPreferability.DesperateOnly || def.ingestible.preferability == FoodPreferability.NeverForNutrition) {
- return TypeResources;
+ if (FoodTypeIsClassifiedAsFood(def)) {
+ return TypeFood;
}
+ }
+ return TypeMedical;
+ }
+ if (def.ingestible != null) {
+ if (BelongsToCategory(def, thingCategoryMeatRaw)) {
return TypeFood;
}
+ if (def.ingestible.drugCategory == DrugCategory.Medical) {
+ return TypeMedical;
+ }
+ if (def.ingestible.preferability == FoodPreferability.DesperateOnly) {
+ return TypeResources;
+ }
+ return TypeFood;
+ }
+
+ if (def.CountAsResource) {
+ if (def.IsShell) {
+ return TypeWeapons;
+ }
return TypeResources;
}
- if (thingCategoryBodyPartsArtificial != null && def.thingCategories != null && def.thingCategories.Contains(thingCategoryBodyPartsArtificial)) {
- return TypeMedical;
- }
-
if (def.building != null && def.Minifiable) {
return TypeBuildings;
}
@@ -301,9 +310,75 @@ public EquipmentType ClassifyThingDef(ThingDef def) {
return TypeAnimals;
}
+ if (def.category == ThingCategory.Item) {
+ if (def.defName.StartsWith("MechSerum")) {
+ return TypeMedical;
+ }
+ // Body parts should be medical
+ if (BelongsToCategoryStartingWith(def, "BodyParts")) {
+ return TypeMedical;
+ }
+ // EPOE parts should be medical
+ if (BelongsToCategoryContaining(def, "Prostheses")) {
+ return TypeMedical;
+ }
+ if (BelongsToCategory(def, "GlitterworldParts")) {
+ return TypeMedical;
+ }
+ if (BelongsToCategoryEndingWith(def, "Organs")) {
+ return TypeMedical;
+ }
+ return TypeResources;
+ }
+
return null;
}
+ public bool BelongsToCategory(ThingDef def, ThingCategoryDef categoryDef) {
+ if (categoryDef == null || def.thingCategories == null) {
+ return false;
+ }
+ return def.thingCategories.FirstOrDefault(d => {
+ return categoryDef == d;
+ }) != null;
+ }
+
+ public bool BelongsToCategoryStartingWith(ThingDef def, string categoryNamePrefix) {
+ if (categoryNamePrefix.NullOrEmpty() || def.thingCategories == null) {
+ return false;
+ }
+ return def.thingCategories.FirstOrDefault(d => {
+ return d.defName.StartsWith(categoryNamePrefix);
+ }) != null;
+ }
+
+ public bool BelongsToCategoryEndingWith(ThingDef def, string categoryNameSuffix) {
+ if (categoryNameSuffix.NullOrEmpty() || def.thingCategories == null) {
+ return false;
+ }
+ return def.thingCategories.FirstOrDefault(d => {
+ return d.defName.EndsWith(categoryNameSuffix);
+ }) != null;
+ }
+
+ public bool BelongsToCategoryContaining(ThingDef def, string categoryNameSubstring) {
+ if (categoryNameSubstring.NullOrEmpty() || def.thingCategories == null) {
+ return false;
+ }
+ return def.thingCategories.FirstOrDefault(d => {
+ return d.defName.Contains(categoryNameSubstring);
+ }) != null;
+ }
+
+ public bool BelongsToCategory(ThingDef def, string categoryName) {
+ if (categoryName.NullOrEmpty() || def.thingCategories == null) {
+ return false;
+ }
+ return def.thingCategories.FirstOrDefault(d => {
+ return categoryName == d.defName;
+ }) != null;
+ }
+
public IEnumerable AllEquipmentOfType(EquipmentType type) {
return entries.Values.Where((EquipmentRecord e) => {
return e.type == type;
diff --git a/Source/LabelTrimmer.cs b/Source/LabelTrimmer.cs
index 5862b02..fbc835a 100644
--- a/Source/LabelTrimmer.cs
+++ b/Source/LabelTrimmer.cs
@@ -5,6 +5,46 @@
using Verse;
namespace EdB.PrepareCarefully {
public class LabelTrimmer {
+
+ public interface LabelProvider {
+ string Trim();
+ string Current {
+ get;
+ }
+ string CurrentWithSuffix(string suffix);
+ }
+
+ public struct DefaultLabelProvider : LabelProvider {
+ private string label;
+ private bool trimmed;
+ public DefaultLabelProvider(string label) {
+ this.label = label;
+ this.trimmed = false;
+ }
+ public string Trim() {
+ int length = label.Length;
+ if (length == 0) {
+ return "";
+ }
+ label = label.Substring(0, length - 1).TrimEnd();
+ trimmed = true;
+ return label;
+ }
+ public string Current {
+ get {
+ return label;
+ }
+ }
+ public string CurrentWithSuffix(string suffix) {
+ if (trimmed) {
+ return label + suffix;
+ }
+ else {
+ return label;
+ }
+ }
+ }
+
private Dictionary cache = new Dictionary();
private string suffix = "...";
private float width;
@@ -21,30 +61,43 @@ public float Width {
return width;
}
set {
+ if (width != value) {
+ cache.Clear();
+ }
width = value;
}
}
public string TrimLabelIfNeeded(string name) {
- if (Text.CalcSize(name).x <= width) {
- return name;
+ return TrimLabelIfNeeded(new DefaultLabelProvider(name));
+ }
+ public string TrimLabelIfNeeded(LabelProvider provider) {
+ string label = provider.Current;
+ if (Text.CalcSize(label).x <= width) {
+ return label;
}
- string shorter;
- if (cache.TryGetValue(name, out shorter)) {
- return shorter + suffix;
+ if (cache.TryGetValue(label, out string shorter)) {
+ return shorter;
}
- shorter = name;
+ return TrimLabel(provider);
+ }
+ public string TrimLabel(LabelProvider provider) {
+ string original = provider.Current;
+ string shorter = original;
while (!shorter.NullOrEmpty()) {
- shorter = shorter.Substring(0, shorter.Length - 1);
- if (shorter.EndsWith(" ")) {
- continue;
+ int length = shorter.Length;
+ shorter = provider.Trim();
+ // The trimmer should always return a shorter length. If it doesn't we bail--it's a bad implementation.
+ if (shorter.Length >= length) {
+ break;
}
- Vector2 size = Text.CalcSize(shorter + suffix);
+ string withSuffix = provider.CurrentWithSuffix(suffix);
+ Vector2 size = Text.CalcSize(withSuffix);
if (size.x <= width) {
- cache.Add(name, shorter);
- return shorter + suffix;
+ cache.Add(original, withSuffix);
+ return shorter;
}
}
- return name;
+ return original;
}
}
}
diff --git a/Source/PanelHealth.cs b/Source/PanelHealth.cs
index faec852..88a6dcb 100644
--- a/Source/PanelHealth.cs
+++ b/Source/PanelHealth.cs
@@ -1,4 +1,4 @@
-using RimWorld;
+using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -77,7 +77,7 @@ public override void Resize(Rect rect) {
RectButtonDelete = new Rect(RectField.xMax - buttonPadding - buttonSize.x,
RectField.y + RectField.height * 0.5f - buttonSize.y * 0.5f,
buttonSize.x, buttonSize.y);
- labelTrimmer.Width = RectField.width - (RectField.xMax - RectButtonDelete.xMin) * 2;
+ labelTrimmer.Width = RectField.width - (RectField.xMax - RectButtonDelete.xMin) * 2 - 10;
RectScrollFrame = new Rect(panelPadding, BodyRect.y, contentSize.x, contentSize.y);
RectScrollView = new Rect(0, 0, RectScrollFrame.width, RectScrollFrame.height);
@@ -135,6 +135,8 @@ protected override void DrawPanelContent(State state) {
base.DrawPanelContent(state);
CustomPawn customPawn = state.CurrentPawn;
+ bool wasScrolling = scrollView.ScrollbarsVisible;
+
float cursor = 0;
GUI.BeginGroup(RectScrollFrame);
@@ -159,6 +161,15 @@ protected override void DrawPanelContent(State state) {
}
partRemovalList.Clear();
}
+
+ // If the addition or removal of an item changed whether or not the scrollbars are visible, then we
+ // need to resize the label trimmer.
+ if (wasScrolling && !scrollView.ScrollbarsVisible) {
+ labelTrimmer.Width += 16;
+ }
+ else if (!wasScrolling && scrollView.ScrollbarsVisible) {
+ labelTrimmer.Width -= 16;
+ }
}
public void DrawAddButton() {
@@ -515,6 +526,68 @@ public float DrawCustomBodyParts(float cursor) {
return cursor;
}
+ // Custom label provider for health diffs that properly maintains the rich text/html tags while trimming.
+ public struct HealthPanelLabelProvider : LabelTrimmer.LabelProvider {
+ private static readonly int PART_NAME = 0;
+ private static readonly int CHANGE_NAME = 1;
+ private int elementToTrim;
+ private string partName;
+ private string changeName;
+ private readonly string color;
+ public HealthPanelLabelProvider(string partName, string changeName, Color color) {
+ this.partName = partName;
+ this.changeName = changeName;
+ this.color = ColorUtility.ToHtmlStringRGBA(color);
+ this.elementToTrim = CHANGE_NAME;
+ }
+ public string Current {
+ get {
+ if (elementToTrim == CHANGE_NAME) {
+ return partName + ": " + changeName + "";
+ }
+ else {
+ return partName;
+ }
+ }
+ }
+ public string CurrentWithSuffix(string suffix) {
+ if (elementToTrim == CHANGE_NAME) {
+ return partName + ": " + changeName + suffix + "";
+ }
+ else {
+ return partName + suffix;
+ }
+ }
+ public string Trim() {
+ if (elementToTrim == CHANGE_NAME) {
+ if (!TrimChangeName()) {
+ elementToTrim = PART_NAME;
+ }
+ }
+ else {
+ TrimPartName();
+ }
+ return Current;
+ }
+ private bool TrimString(ref string value) {
+ int length = value.Length;
+ if (length == 0) {
+ return false;
+ }
+ value = value.Substring(0, length - 1).TrimEnd();
+ if (length == 0) {
+ return false;
+ }
+ return true;
+ }
+ private bool TrimChangeName() {
+ return TrimString(ref changeName);
+ }
+ private bool TrimPartName() {
+ return TrimString(ref partName);
+ }
+ }
+
public float DrawCustomBodyPart(float cursor, CustomBodyPart customPart, Field field) {
bool willScroll = scrollView.ScrollbarsVisible;
Rect entryRect = RectItem;
@@ -530,16 +603,14 @@ public float DrawCustomBodyPart(float cursor, CustomBodyPart customPart, Field f
Rect fieldRect = RectField;
fieldRect.width = fieldRect.width - (willScroll ? 16 : 0);
field.Rect = fieldRect;
- string label;
if (customPart.BodyPartRecord != null) {
- label = "EdB.PC.Panel.Health.PartWithInjury".Translate(customPart.PartName, customPart.ChangeName, "#" + ColorUtility.ToHtmlStringRGBA(customPart.LabelColor));
+ field.Label = labelTrimmer.TrimLabelIfNeeded(new HealthPanelLabelProvider(customPart.PartName, customPart.ChangeName, customPart.LabelColor));
field.Color = Color.white;
}
else {
- label = customPart.ChangeName;
+ field.Label = labelTrimmer.TrimLabelIfNeeded(customPart.ChangeName);
field.Color = customPart.LabelColor;
}
- field.Label = labelTrimmer.TrimLabelIfNeeded(label);
if (customPart.HasTooltip) {
field.Tip = customPart.Tooltip;
diff --git a/Source/PanelPawnList.cs b/Source/PanelPawnList.cs
index 34693cd..6609044 100644
--- a/Source/PanelPawnList.cs
+++ b/Source/PanelPawnList.cs
@@ -141,8 +141,9 @@ protected override void DrawPanelContent(State state) {
base.DrawPanelContent(state);
CustomPawn currentPawn = state.CurrentPawn;
- CustomPawn newPawnSelection = null;
- CustomPawn swapSelection = null;
+ CustomPawn pawnToSelect = null;
+ CustomPawn pawnToSwap = null;
+ CustomPawn pawnToDelete = null;
List pawns = GetPawnListFromState(state);
int colonistCount = pawns.Count();
@@ -181,8 +182,8 @@ protected override void DrawPanelContent(State state) {
foreach (var pawn in pawns) {
bool selected = pawn == currentPawn;
Rect rect = RectEntry;
- rect.y = rect.y + cursor;
- rect.width = rect.width - (scrollView.ScrollbarsVisible ? 16 : 0);
+ rect.y += cursor;
+ rect.width -= (scrollView.ScrollbarsVisible ? 16 : 0);
GUI.color = Style.ColorPanelBackground;
GUI.DrawTexture(rect, BaseContent.WhiteTex);
@@ -204,25 +205,32 @@ protected override void DrawPanelContent(State state) {
// Replacing it with a mousedown event check fixes it for some reason.
//if (GUI.Button(deleteRect, string.Empty, Widgets.EmptyStyle)) {
if (Event.current.type == EventType.MouseDown && deleteRect.Contains(Event.current.mousePosition)) {
- CustomPawn localPawn = pawn;
- Find.WindowStack.Add(
- new Dialog_Confirm("EdB.PC.Panel.PawnList.Delete.Confirm".Translate(),
- delegate {
- PawnDeleted(localPawn);
- },
- true, null, true)
- );
+ // Shift-click skips the confirmation dialog
+ if (Event.current.shift) {
+ // Delete after we've iterated and drawn everything
+ pawnToDelete = pawn;
+ }
+ else {
+ CustomPawn localPawn = pawn;
+ Find.WindowStack.Add(
+ new Dialog_Confirm("EdB.PC.Panel.PawnList.Delete.Confirm".Translate(),
+ delegate {
+ PawnDeleted(localPawn);
+ },
+ true, null, true)
+ );
+ }
}
GUI.color = Color.white;
}
if (rect.Contains(Event.current.mousePosition)) {
Rect swapRect = RectButtonSwap.OffsetBy(rect.position);
- swapRect.x = swapRect.x - (scrollView.ScrollbarsVisible ? 16 : 0);
+ swapRect.x -= (scrollView.ScrollbarsVisible ? 16 : 0);
if (CanDeleteLastPawn || colonistCount > 1) {
Style.SetGUIColorForButton(swapRect);
GUI.DrawTexture(swapRect, pawn.Type == CustomPawnType.Colonist ? Textures.TextureButtonWorldPawn : Textures.TextureButtonColonyPawn);
if (Event.current.type == EventType.MouseDown && swapRect.Contains(Event.current.mousePosition)) {
- swapSelection = pawn;
+ pawnToSwap = pawn;
}
GUI.color = Color.white;
}
@@ -267,8 +275,8 @@ protected override void DrawPanelContent(State state) {
Widgets.Label(professionRect, descriptionTrimmer.TrimLabelIfNeeded(description));
}
- if (pawn != state.CurrentPawn && Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition) && swapSelection == null) {
- newPawnSelection = pawn;
+ if (pawn != state.CurrentPawn && Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition) && pawnToSwap == null) {
+ pawnToSelect = pawn;
}
cursor += rect.height + SizeEntrySpacing;
@@ -292,12 +300,16 @@ protected override void DrawPanelContent(State state) {
Text.Font = GameFont.Small;
Text.Anchor = TextAnchor.UpperLeft;
- if (swapSelection != null) {
- PawnSwapped(swapSelection);
+ if (pawnToDelete != null) {
+ PawnDeleted(pawnToDelete);
}
- else if (newPawnSelection != null) {
- PawnSelected(newPawnSelection);
+ else if (pawnToSwap != null) {
+ PawnSwapped(pawnToSwap);
}
+ else if (pawnToSelect != null) {
+ PawnSelected(pawnToSelect);
+ }
+
}
protected abstract bool IsMaximized(State state);