diff --git a/.gitignore b/.gitignore index 03b6af2..6d01b57 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Libraries/* *.user *.bat !dist.bat +Resources/Assemblies/*.dll diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index ccf3939..f0d6c0b 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -9,7 +9,8 @@ EdBPrepareCarefully 0.17.1.7 False - v3.5 + v4.7.2 + true @@ -20,6 +21,7 @@ prompt 4 false + false full @@ -28,21 +30,29 @@ prompt 4 false + false Libraries\0Harmony.dll + + Libraries\Assembly-CSharp.dll + - - Libraries\Assembly-CSharp.dll - False + + Libraries\UnityEngine.CoreModule.dll + + + Libraries\UnityEngine.IMGUIModule.dll + + + Libraries\UnityEngine.InputLegacyModule.dll - - Libraries\UnityEngine.dll - False + + Libraries\UnityEngine.TextRenderingModule.dll @@ -61,7 +71,9 @@ + + @@ -127,6 +139,8 @@ + + @@ -219,6 +233,7 @@ + diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 92b295f..7972219 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -1,9 +1,6 @@ using System.Reflection; using System.Runtime.CompilerServices; -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - [assembly: AssemblyTitle("EdBPrepareCarefully")] [assembly: AssemblyDescription("A RimWorld mod")] [assembly: AssemblyConfiguration("")] @@ -13,15 +10,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.16")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] +// Only change this if the interfaces change in a way that should force users to re-link to the newer version +[assembly: AssemblyVersion("1.1.1")] +// Increment for each new release +[assembly: AssemblyFileVersion("1.1.1")] diff --git a/README.md b/README.md index 463f7b8..a022ab9 100644 --- a/README.md +++ b/README.md @@ -12,22 +12,43 @@ The solution file was created using Xamarin Studio/MonoDevelop, but it should al Note that the solution has dependencies on the following RimWorld DLLs: - Assembly-CSharp.dll -- UnityEngine.dll +- UnityEngine.CoreModule.dll +- UnityEngine.IMGUIModule +- UnityEngine.InputLegacyModule +- UnityEngine.TextRenderingModule Copy those dependencies from the RimWorld game directory into the "Libraries" directory. Be sure to make _copies_ of the originals--don't accidentally move/delete them from the original game directory. The solution also has a dependency on the following third-party DLL: - 0Harmony.dll -The Harmony DLL is available from https://github.com/pardeike/Harmony/releases and should also be placed in the "Libraries" directory. Be sure to download and use the "Release" version. +The Harmony DLL is available from https://github.com/pardeike/Harmony/releases and should also be placed in the "Libraries" directory. Prepare Carefully uses version 2.0.0.5 of Harmony. When you download Harmony, you'll see +multiple versions of the DLL organized into various directories. Be sure to use the one in the "Release/net472" directory. + +Only if you _must_ create a build that also supports RimWorld 1.0, you will need to get the DLL from the latest Prepare Carefully release for 1.0, along with the DLL for Harmony _1.2_. Place these DLLs into the `Resources/Assemblies` directory. The result of the build will be the following DLL: - EdBPrepareCarefully.dll -This DLL must be packaged with the contents of the `Resources` directory to create a working mod. The DLL should be placed inside an `Assemblies` directory. - -To automatically build the mod directory for your Release DLL, run the `dist.bat` script. This will copy all of the mod resources and the DLL into a `dist/EdBPrepareCarefully` directory. Copy this `EdBPrepareCarefully` directory into your RimWorld `Mods` folder. - +This DLL must be packaged alongside the contents of the `Resources` directory to create a working mod. The DLL built by the project should be placed inside a `1.1/Assemblies` directory along with the Harmony DLL. The directory structure should look like this: + +``` ++ EdBPrepareCarefully + + 1.1 + + Assemblies + - 0Harmony.dll + - EdBPrepareCarefully.dll + + About + + Assemblies + + Defs + + Languages + + Textures + - CHANGLELOG.txt + - LICENSE + - LoadFolders.xml +``` + +If Windows is the OS on which you're developing, you don't need to manually create the mod directory. Instead, you can automatically package up the mod by running the `dist.bat` script. This will copy all of the mod resources and the DLL into a `dist/EdBPrepareCarefully` directory. Copy this `EdBPrepareCarefully` directory into your RimWorld `Mods` folder to use the mod in your game. ## Versioning @@ -40,4 +61,5 @@ Other conventions used to determine the mod version numbers: Some examples: + **0.18.2**: The second release of the mod for the Beta 18 version of RimWorld -+ **1.0.1**: The first release of the mod for RimWorld 1.0 ++ **1.0.11**: The eleventh release of the mod for RimWorld 1.0 ++ **1.1.1**: The first release of the mod for RimWorld 1.1 diff --git a/Resources/1.1/Assemblies/README.md b/Resources/1.1/Assemblies/README.md new file mode 100644 index 0000000..de94220 --- /dev/null +++ b/Resources/1.1/Assemblies/README.md @@ -0,0 +1,3 @@ +This directory will contain the Prepare Carefully DLL built by the project, along with any required DLL dependencies. + +See the build instructions for more information. \ No newline at end of file diff --git a/Resources/About/About.xml b/Resources/About/About.xml index 0a72ca6..231c3e4 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -2,14 +2,16 @@ EdB Prepare Carefully EdB + EdB.PrepareCarefully - -
  • 1.0
  • -
    + +
  • 1.0
  • +
  • 1.1
  • +
    Customize your colonists, choose your gear and prepare carefully for your crash landing! 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.0.16] +[Version 1.1.1]
    \ No newline at end of file diff --git a/Resources/Assemblies/README.md b/Resources/Assemblies/README.md new file mode 100644 index 0000000..16f74da --- /dev/null +++ b/Resources/Assemblies/README.md @@ -0,0 +1,3 @@ +This directory should contain the latest DLLs from the Prepare Carefully 1.0 release, but only if you need to support both the latest version _and_ the 1.0 version of RimWorld. + +See the build instructions for more information. \ No newline at end of file diff --git a/Resources/CHANGELOG.txt b/Resources/CHANGELOG.txt index aa29da7..b981f9f 100644 --- a/Resources/CHANGELOG.txt +++ b/Resources/CHANGELOG.txt @@ -1,3 +1,10 @@ + _____________________________________________________________________________ + + Version 1.1.1 + _____________________________________________________________________________ + + - Added support for RimWorld 1.1 + _____________________________________________________________________________ Version 1.0.16 diff --git a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml index d29a1fd..c612b27 100644 --- a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml +++ b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml @@ -1,4 +1,8 @@  + + + + @@ -197,9 +201,10 @@ You must have at least {0} colonists. Prepare Carefully failed to initialize. -This is almost always caused by another mod. Mods do unexpected things, and sometimes Prepare Carefully breaks when loading custom items, factions, etc. In some cases, Prepare Carefully will need to be fixed to make the mods work together. In other cases, it's the other mod that needs to be fixed. Unfortunately, there are a handful of mods that may never be compatible because they make significant changes to the game that just don't fit well with Prepare Carefully. +This is frequently caused by an unexpected issue between Prepare Carefully and another mod but could also be a caused by a bug in Prepare Carefully. -Before reporting an error, please try to figure out which mod is causing the problem. If you have a lot of mods, you can use a divide and conquer approach to disable mods until you find the one that's triggering the issue. +Before reporting an error, please try to figure out which mod may be causing the problem. If you have a lot of mods, you can use a divide and conquer approach to disable mods until you find the one that's triggering the issue. + {0} ({1}) {0} diff --git a/Resources/LoadFolders.xml b/Resources/LoadFolders.xml new file mode 100644 index 0000000..f1b7362 --- /dev/null +++ b/Resources/LoadFolders.xml @@ -0,0 +1,6 @@ + + +
  • /
  • +
  • 1.1
  • +
    +
    \ No newline at end of file diff --git a/Source/ColonistFiles.cs b/Source/ColonistFiles.cs index 0ae0a53..f1425a2 100644 --- a/Source/ColonistFiles.cs +++ b/Source/ColonistFiles.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.IO; @@ -10,7 +10,7 @@ public static class ColonistFiles { public static string SavedColonistsFolderPath { get { try { - return (string)typeof(GenFilePaths).GetMethod("FolderUnderSaveData", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { "PrepareCarefully" }); + return Reflection.GenFilePaths.FolderUnderSaveData("PrepareCarefully"); } catch (Exception e) { Log.Error("Failed to get colonist save directory"); diff --git a/Source/ControllerPawns.cs b/Source/ControllerPawns.cs index 67bc71e..711bfdc 100644 --- a/Source/ControllerPawns.cs +++ b/Source/ControllerPawns.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -29,7 +29,7 @@ public void CheckPawnCapabilities() { if (w.requireCapableColonist) { bool workTypeEnabledOnAtLeastOneColonist = false; foreach (CustomPawn pawn in PrepareCarefully.Instance.Pawns.Where((pawn) => { return pawn.Type == CustomPawnType.Colonist; })) { - if (!pawn.Pawn.story.WorkTypeIsDisabled(w)) { + if (!pawn.Pawn.WorkTypeIsDisabled(w)) { workTypeEnabledOnAtLeastOneColonist = true; break; } @@ -101,13 +101,17 @@ public void RandomizeBackstories() { if (factionDef == null) { factionDef = Faction.OfPlayer.def; } - MethodInfo method = typeof(PawnBioAndNameGenerator).GetMethod("FillBackstorySlotShuffled", BindingFlags.Static | BindingFlags.NonPublic); - object[] arguments = new object[] { currentPawn.Pawn, BackstorySlot.Childhood, null, kindDef.backstoryCategories, factionDef }; - method.Invoke(null, arguments); - currentPawn.Childhood = arguments[2] as Backstory; - arguments = new object[] { currentPawn.Pawn, BackstorySlot.Adulthood, null, kindDef.backstoryCategories, factionDef }; - method.Invoke(null, arguments); - currentPawn.Adulthood = arguments[2] as Backstory; + List backstoryCategoryFiltersFor = Reflection.PawnBioAndNameGenerator + .GetBackstoryCategoryFiltersFor(currentPawn.Pawn, factionDef); + if (!Reflection.PawnBioAndNameGenerator.TryGetRandomUnusedSolidBioFor(backstoryCategoryFiltersFor, + kindDef, currentPawn.Gender, null, out PawnBio pawnBio)) { + return; + } + currentPawn.Childhood = pawnBio.childhood; + // TODO: Remove the hard-coded adult age and get the value from a provider instead? + if (currentPawn.BiologicalAge >= 20) { + currentPawn.Adulthood = pawnBio.adulthood; + } } // Trait-related actions. @@ -290,7 +294,7 @@ public void AddFactionPawn(PawnKindDef kindDef, bool startingPawn) { Faction = faction, KindDef = kindDef, Context = PawnGenerationContext.NonPlayer, - WorldPawnFactionDoesntMatter = true + WorldPawnFactionDoesntMatter = false }.Request); if (pawn.equipment != null) { pawn.equipment.DestroyAllEquipment(DestroyMode.Vanish); @@ -312,6 +316,7 @@ public void AddFactionPawn(PawnKindDef kindDef, bool startingPawn) { finally { kindDef.weaponMoney = savedWeaponsMoney; } + // Reset the quality and damage of all apparel. foreach (var a in pawn.apparel.WornApparel) { a.SetQuality(QualityCategory.Normal); @@ -319,6 +324,10 @@ public void AddFactionPawn(PawnKindDef kindDef, bool startingPawn) { } CustomPawn customPawn = new CustomPawn(pawn); + customPawn.OriginalKindDef = kindDef; + customPawn.OriginalFactionDef = faction.def; + pawn.SetFaction(Faction.OfPlayer); + customPawn.Type = startingPawn ? CustomPawnType.Colonist : CustomPawnType.World; if (!startingPawn) { CustomFaction customFaction = PrepareCarefully.Instance.Providers.Factions.FindRandomCustomFactionByDef(factionDef); diff --git a/Source/CostCalculator.cs b/Source/CostCalculator.cs index d260f16..3527f7c 100644 --- a/Source/CostCalculator.cs +++ b/Source/CostCalculator.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -269,12 +269,11 @@ public double GetBaseThingCost(ThingDef def, ThingDef stuffDef) { return def.BaseMarketValue; } else { - // TODO: + // EVERY RELEASE: // 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. + // Should re-evaluate for each new release. if (def.thingClass == null) { Log.Warning("Prepare Carefully trying to calculate the cost of a ThingDef with null thingClass: " + def.defName); return 0; diff --git a/Source/CustomBodyPart.cs b/Source/CustomBodyPart.cs index 9b44470..9901b63 100644 --- a/Source/CustomBodyPart.cs +++ b/Source/CustomBodyPart.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using UnityEngine; @@ -13,7 +13,7 @@ public abstract BodyPartRecord BodyPartRecord { public virtual string PartName { get { - return BodyPartRecord != null ? (BodyPartRecord.LabelCap) : "EdB.PC.BodyParts.WholeBody".Translate(); + return BodyPartRecord != null ? (BodyPartRecord.LabelCap) : "EdB.PC.BodyParts.WholeBody".Translate().Resolve(); } } diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index d50611d..b28cdd8 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -26,7 +26,7 @@ public class CustomPawn { public Dictionary originalPassions = new Dictionary(); public Dictionary currentPassions = new Dictionary(); - + protected string incapable = null; protected Pawn pawn; @@ -46,7 +46,7 @@ public class CustomPawn { // A GUID provides a unique identifier for the CustomPawn. protected string id; - + protected CustomHeadType headType; protected List implants = new List(); @@ -56,6 +56,8 @@ public class CustomPawn { protected bool portraitDirty = true; protected AlienRace alienRace = null; protected CustomFaction faction = null; + protected PawnKindDef originalKindDef = null; + protected FactionDef originalFactionDef = null; public CustomPawn() { GenerateId(); @@ -91,7 +93,7 @@ public bool Hidden { return Type == CustomPawnType.Hidden || Type == CustomPawnType.Temporary; } } - + public CustomFaction Faction { get { return faction; @@ -101,6 +103,16 @@ public CustomFaction Faction { } } + // Stores the original FactionDef of a pawn that was created from a faction template. + public FactionDef OriginalFactionDef { + get { + return originalFactionDef; + } + set { + originalFactionDef = value; + } + } + public BodyTypeDef BodyType { get { return pawn.story.bodyType; @@ -116,7 +128,7 @@ public AlienRace AlienRace { return alienRace; } } - + public bool HasCustomBodyParts { get { return bodyParts.Count > 0; @@ -172,6 +184,9 @@ public void InitializeWithPawn(Pawn pawn) { this.pawn = pawn; this.pawn.ClearCaches(); + this.originalKindDef = pawn.kindDef; + this.originalFactionDef = pawn.Faction != null ? pawn.Faction.def : null; + PrepareCarefully.Instance.Providers.Health.GetOptions(this); // Set the skills. @@ -230,7 +245,7 @@ public void InitializeWithPawn(Pawn pawn) { // Evaluate all hediffs. InitializeInjuriesAndImplantsFromPawn(pawn); - + // Set the alien race, if any. alienRace = PrepareCarefully.Instance.Providers.AlienRaces.GetAlienRace(pawn.def); @@ -675,7 +690,7 @@ public string LabelShort { return pawn.LabelShort; } } - + public IEnumerable Implants { get { return implants; @@ -695,6 +710,18 @@ public bool IsAdult { } } + // Stores the original PawnKindDef of the pawn. This value automatically changes when you assign + // a pawn to the FactionOf.Colony, so we want to preserve it for faction pawns that are created from + // a different PawnKindDef. + public PawnKindDef OriginalKindDef { + get { + return originalKindDef; + } + set { + originalKindDef = value; + } + } + public void SetPassion(SkillDef def, Passion level) { if (IsSkillDisabled(def)) { return; @@ -1049,7 +1076,7 @@ public Backstory Adulthood { } } - protected void ResetBackstories() { + public void ResetBackstories() { UpdateSkillLevelsForNewBackstoryOrTrait(); } @@ -1307,12 +1334,11 @@ protected void ResetGender() { } public string ResetCachedIncapableOf() { - pawn.ClearCachedDisabledWorkTypes(); pawn.ClearCachedDisabledSkillRecords(); List incapableList = new List(); - WorkTags combinedDisabledWorkTags = pawn.story.CombinedDisabledWorkTags; + WorkTags combinedDisabledWorkTags = pawn.story.DisabledWorkTagsBackstoryAndTraits; if (combinedDisabledWorkTags != WorkTags.None) { - IEnumerable list = (IEnumerable)typeof(CharacterCardUtility).GetMethod("WorkTagsFrom", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { combinedDisabledWorkTags }); + IEnumerable list = Reflection.CharacterCardUtility.WorkTagsFrom(combinedDisabledWorkTags); foreach (var tag in list) { incapableList.Add(WorkTypeDefsUtility.LabelTranslated(tag).CapitalizeFirst()); } diff --git a/Source/DialogInitializationError.cs b/Source/DialogInitializationError.cs index 1a0e880..d01816d 100644 --- a/Source/DialogInitializationError.cs +++ b/Source/DialogInitializationError.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -9,13 +9,16 @@ namespace EdB.PrepareCarefully { public class DialogInitializationError : Window { + private Exception exception; + public override Vector2 InitialSize { get { return new Vector2(500f, 400f); } } - public DialogInitializationError() { + public DialogInitializationError(Exception exception) { + this.exception = exception; this.forcePause = true; this.absorbInputAroundWindow = true; this.closeOnClickedOutside = false; diff --git a/Source/EquipmentDatabase.cs b/Source/EquipmentDatabase.cs index e667524..4abe2de 100644 --- a/Source/EquipmentDatabase.cs +++ b/Source/EquipmentDatabase.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -124,7 +124,7 @@ protected void CountDefs() { protected void ProcessStuff() { for (int i = 0; i < LoadingProgress.stuffToProcessPerFrame; i++) { if (!LoadingProgress.enumerator.MoveNext()) { - Log.Message("Prepare Carefully :: Loaded equipment database with " + LoadingProgress.stuffCount + " material(s)"); + Logger.Message("Loaded equipment database with " + LoadingProgress.stuffCount + " material(s)"); NextPhase(); return; } @@ -138,7 +138,7 @@ protected void ProcessStuff() { protected void ProcessThings() { for (int i=0; i(typeof(Log), "messageCount"); Pawn pawn = PawnGenerator.GeneratePawn(request); if (ReflectionUtil.GetNonPublicStatic(typeof(Log), "messageCount") > messageCount) { diff --git a/Source/ExtensionsBackstory.cs b/Source/ExtensionsBackstory.cs new file mode 100644 index 0000000..a95a777 --- /dev/null +++ b/Source/ExtensionsBackstory.cs @@ -0,0 +1,51 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse; + +namespace EdB.PrepareCarefully { + public static class ExtensionsBackstory { + + private static HashSet ProblemBackstories = new HashSet() { + "pirate king" + }; + + public static string CheckedDescriptionFor(this Backstory backstory, Pawn pawn) { + if (ProblemBackstories.Contains(backstory.untranslatedTitle)) { + return PartialDescriptionFor(backstory); + } + string description = backstory.FullDescriptionFor(pawn); + if (description.StartsWith("Could not resolve")) { + return PartialDescriptionFor(backstory); + //Log.Message("Failed to resolve description for backstory with this pawn: " + backstory.title + ", " + backstory.identifier); + } + return description; + } + + // EVERY RELEASE: + // This is a copy of Backstory.FullDescriptionFor() that only includes the disabled work types and the skill adjustments. + // Every release, we should evaluate that method to make sure that the logic has not changed. + public static string PartialDescriptionFor(this Backstory backstory) { + StringBuilder stringBuilder = new StringBuilder(); + List allDefsListForReading = DefDatabase.AllDefsListForReading; + for (int i = 0; i < allDefsListForReading.Count; i++) { + SkillDef skillDef = allDefsListForReading[i]; + if (backstory.skillGainsResolved.ContainsKey(skillDef)) { + stringBuilder.AppendLine(skillDef.skillLabel.CapitalizeFirst() + ": " + backstory.skillGainsResolved[skillDef].ToString("+##;-##")); + } + } + stringBuilder.AppendLine(); + foreach (WorkTypeDef current in backstory.DisabledWorkTypes) { + stringBuilder.AppendLine(current.gerundLabel.CapitalizeFirst() + " " + "DisabledLower".Translate()); + } + foreach (WorkGiverDef current2 in backstory.DisabledWorkGivers) { + stringBuilder.AppendLine(current2.workType.gerundLabel.CapitalizeFirst() + ": " + current2.LabelCap + " " + "DisabledLower".Translate()); + } + string str = stringBuilder.ToString().TrimEndNewlines(); + return str; + } + } +} diff --git a/Source/ExtensionsPawn.cs b/Source/ExtensionsPawn.cs index d097afa..c1b2a33 100644 --- a/Source/ExtensionsPawn.cs +++ b/Source/ExtensionsPawn.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -77,23 +77,15 @@ private static void CopyModdedProperties(Pawn source, Pawn target) { public static void ClearCaches(this Pawn pawn) { pawn.ClearCachedHealth(); pawn.ClearCachedLifeStage(); - pawn.ClearCachedDisabledWorkTypes(); pawn.ClearCachedDisabledSkillRecords(); } - public static void ClearCachedDisabledWorkTypes(this Pawn pawn) { - if (pawn.story != null) { - typeof(Pawn_StoryTracker).GetField("cachedDisabledWorkTypes", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, null); - } - } - public static void ClearCachedDisabledSkillRecords(this Pawn pawn) { if (pawn.skills != null && pawn.skills.skills != null) { - FieldInfo field = typeof(SkillRecord).GetField("cachedTotallyDisabled", BindingFlags.NonPublic | BindingFlags.Instance); - foreach (var record in pawn.skills.skills) { - field.SetValue(record, BoolUnknown.Unknown); - } + pawn.skills.Notify_SkillDisablesChanged(); } + Reflection.Pawn.ClearCachedDisabledWorkTypes(pawn); + Reflection.Pawn.ClearCachedDisabledWorkTypesPermanent(pawn); } public static void ClearCachedHealth(this Pawn pawn) { @@ -120,5 +112,10 @@ public static void ClearCachedPortraits(this Pawn pawn) { PortraitsCache.SetDirty(pawn); } + public static void AssignToFaction(this Pawn pawn, Faction faction) { + FieldInfo field = typeof(Pawn_AgeTracker).GetField("factionInt", BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(pawn, faction); + } + } } diff --git a/Source/FilterBackstoryMatchesFaction.cs b/Source/FilterBackstoryMatchesFaction.cs index 2792394..3f9e6a2 100644 --- a/Source/FilterBackstoryMatchesFaction.cs +++ b/Source/FilterBackstoryMatchesFaction.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -11,17 +11,15 @@ public FilterBackstoryMatchesFaction() { this.LabelShort = this.LabelFull = "EdB.PC.Dialog.Backstory.Filter.MatchesFaction".Translate(); this.FilterFunction = (Backstory backstory) => { CustomPawn pawn = PrepareCarefully.Instance.State.CurrentPawn; - PawnKindDef kindDef = pawn.Pawn.kindDef; - HashSet pawnKindBackstoryCategories = new HashSet(kindDef.backstoryCategories); - if (kindDef.backstoryCategories == null || kindDef.backstoryCategories.Count == 0) { - return true; + PawnKindDef kindDef = pawn.OriginalKindDef; + if (kindDef == null) { + kindDef = PawnKindDefOf.Colonist; } - foreach (var c in backstory.spawnCategories) { - if (pawnKindBackstoryCategories.Contains(c)) { - return true; - } + var set = PrepareCarefully.Instance.Providers.Backstories.BackstoriesForPawnKindDef(kindDef); + if (set == null) { + return false; } - return false; + return set.Contains(backstory); }; } } diff --git a/Source/GenStep_CustomScatterThings.cs b/Source/GenStep_CustomScatterThings.cs index 4271b11..ff22a41 100644 --- a/Source/GenStep_CustomScatterThings.cs +++ b/Source/GenStep_CustomScatterThings.cs @@ -1,13 +1,13 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using UnityEngine; using Verse; namespace EdB.PrepareCarefully { - // TODO: Duplicate again for 1.0 Release // Duplicate of GenStep_ScatterThings with a radius field to allow for a large spawn area. public class GenStep_CustomScatterThings : GenStep_Scatterer { + // // Static Fields // @@ -15,21 +15,20 @@ public class GenStep_CustomScatterThings : GenStep_Scatterer { private const int ClusterRadius = 4; - public int radius = 4; // // Fields // public ThingDef thingDef; - [Unsaved] - private int leftInCluster = 0; + [Unsaved(false)] + private int leftInCluster; - [Unsaved] + [Unsaved(false)] private IntVec3 clusterCenter; private List possibleRotationsInt; - public float terrainValidationRadius = 0f; + public float terrainValidationRadius; public int clusterSize = 1; @@ -40,12 +39,8 @@ public class GenStep_CustomScatterThings : GenStep_Scatterer { [NoTranslate] private List terrainValidationDisallowed; - // Copied from GenStep_ScatterThings. Should it in fact be unique? - public override int SeedPart { - get { - return 1158116095; - } - } + // EdB: New radius field + public int radius = 4; // // Properties @@ -68,6 +63,12 @@ private List PossibleRotations { } } + public override int SeedPart { + get { + return 1158116095; + } + } + // // Static Methods // @@ -82,17 +83,14 @@ public static List CountDividedIntoStacks(int count, IntRange stackSizeRang for (int i = 0; i < list.Count * 4; i++) { int num2 = Rand.RangeInclusive(0, list.Count - 1); int num3 = Rand.RangeInclusive(0, list.Count - 1); - if (num2 != num3) { - if (list[num2] > list[num3]) { - int num4 = (int)((float)(list[num2] - list[num3]) * Rand.Value); - List list2; - int index; - list2 = list; - index = num2; - list2[index] = list2[index] - num4; - int index2 = num3; - list2[index2] = list2[index2] + num4; - } + if (num2 != num3 && list[num2] > list[num3]) { + int num4 = (int)((float)(list[num2] - list[num3]) * Rand.Value); + List list2 = list; + int index = num2; + list2[index] -= num4; + list2 = list; + index = num3; + list2[index] += num4; } } } @@ -103,147 +101,136 @@ public static List CountDividedIntoStacks(int count, IntRange stackSizeRang // Methods // protected override bool CanScatterAt(IntVec3 loc, Map map) { - bool result; - Rot4 rot; if (!base.CanScatterAt(loc, map)) { - result = false; + return false; } - else if (!this.TryGetRandomValidRotation(loc, map, out rot)) { - result = false; + Rot4 rot; + if (!this.TryGetRandomValidRotation(loc, map, out rot)) { + return false; } - else { - if (this.terrainValidationRadius > 0f) { - foreach (IntVec3 current in GenRadial.RadialCellsAround(loc, this.terrainValidationRadius, true)) { - if (current.InBounds(map)) { - TerrainDef terrain = current.GetTerrain(map); - for (int i = 0; i < this.terrainValidationDisallowed.Count; i++) { - if (terrain.HasTag(this.terrainValidationDisallowed[i])) { - result = false; - return result; - } + if (this.terrainValidationRadius > 0f) { + foreach (IntVec3 current in GenRadial.RadialCellsAround(loc, this.terrainValidationRadius, true)) { + if (current.InBounds(map)) { + TerrainDef terrain = current.GetTerrain(map); + for (int i = 0; i < this.terrainValidationDisallowed.Count; i++) { + if (terrain.HasTag(this.terrainValidationDisallowed[i])) { + return false; } } } } - result = true; + return true; } - return result; + return true; } public override void Generate(Map map, GenStepParams parms) { - if (this.allowInWaterBiome || !map.TileInfo.WaterCovered) { - int count = base.CalculateFinalCount(map); - IntRange one; - if (this.thingDef.ingestible != null && this.thingDef.ingestible.IsMeal && this.thingDef.stackLimit <= 10) { - one = IntRange.one; - } - else if (this.thingDef.stackLimit > 5) { - one = new IntRange(Mathf.RoundToInt((float)this.thingDef.stackLimit * 0.5f), this.thingDef.stackLimit); - } - else { - one = new IntRange(this.thingDef.stackLimit, this.thingDef.stackLimit); - } - List list = GenStep_ScatterThings.CountDividedIntoStacks(count, one); - for (int i = 0; i < list.Count; i++) { - IntVec3 intVec; - if (!this.TryFindScatterCell(map, out intVec)) { - return; - } - this.ScatterAt(intVec, map, list[i]); - this.usedSpots.Add(intVec); + if (!this.allowInWaterBiome && map.TileInfo.WaterCovered) { + return; + } + int arg_AA_0 = base.CalculateFinalCount(map); + IntRange one; + if (this.thingDef.ingestible != null && this.thingDef.ingestible.IsMeal && this.thingDef.stackLimit <= 10) { + one = IntRange.one; + } + else if (this.thingDef.stackLimit > 5) { + one = new IntRange(Mathf.RoundToInt((float)this.thingDef.stackLimit * 0.5f), this.thingDef.stackLimit); + } + else { + one = new IntRange(this.thingDef.stackLimit, this.thingDef.stackLimit); + } + List list = GenStep_ScatterThings.CountDividedIntoStacks(arg_AA_0, one); + for (int i = 0; i < list.Count; i++) { + IntVec3 intVec; + if (!this.TryFindScatterCell(map, out intVec)) { + return; } - this.usedSpots.Clear(); - this.clusterCenter = IntVec3.Invalid; - this.leftInCluster = 0; + this.ScatterAt(intVec, map, parms, list[i]); + this.usedSpots.Add(intVec); } + this.usedSpots.Clear(); + this.clusterCenter = IntVec3.Invalid; + this.leftInCluster = 0; } private bool IsRotationValid(IntVec3 loc, Rot4 rot, Map map) { return GenAdj.OccupiedRect(loc, rot, this.thingDef.size).InBounds(map) && !GenSpawn.WouldWipeAnythingWith(loc, rot, this.thingDef, map, (Thing x) => x.def == this.thingDef || (x.def.category != ThingCategory.Plant && x.def.category != ThingCategory.Filth)); } - protected override void ScatterAt(IntVec3 loc, Map map, int stackCount = 1) { + protected override void ScatterAt(IntVec3 loc, Map map, GenStepParams parms, int stackCount = 1) { Rot4 rot; if (!this.TryGetRandomValidRotation(loc, map, out rot)) { - Log.Warning("Could not find any valid rotation for " + this.thingDef); + Log.Warning("Could not find any valid rotation for " + this.thingDef, false); + return; } - else { - if (this.clearSpaceSize > 0) { - foreach (IntVec3 current in GridShapeMaker.IrregularLump(loc, map, this.clearSpaceSize)) { - Building edifice = current.GetEdifice(map); + if (this.clearSpaceSize > 0) { + using (IEnumerator enumerator = GridShapeMaker.IrregularLump(loc, map, this.clearSpaceSize).GetEnumerator()) { + while (enumerator.MoveNext()) { + Building edifice = enumerator.Current.GetEdifice(map); if (edifice != null) { edifice.Destroy(DestroyMode.Vanish); } } } - Thing thing = ThingMaker.MakeThing(this.thingDef, this.stuff); - if (this.thingDef.Minifiable) { - thing = thing.MakeMinified(); - } - if (thing.def.category == ThingCategory.Item) { - thing.stackCount = stackCount; - thing.SetForbidden(true, false); - Thing thing2; - GenPlace.TryPlaceThing(thing, loc, map, ThingPlaceMode.Near, out thing2, null); - if (this.nearPlayerStart && thing2 != null && thing2.def.category == ThingCategory.Item && TutorSystem.TutorialMode) { - Find.TutorialState.AddStartingItem(thing2); - } - } - else { - // TODO: Evaluate new WipeMode and 5th argument - GenSpawn.Spawn(thing, loc, map, rot, WipeMode.Vanish, false); + } + Thing thing = ThingMaker.MakeThing(this.thingDef, this.stuff); + if (this.thingDef.Minifiable) { + thing = thing.MakeMinified(); + } + if (thing.def.category == ThingCategory.Item) { + thing.stackCount = stackCount; + thing.SetForbidden(true, false); + Thing thing2; + GenPlace.TryPlaceThing(thing, loc, map, ThingPlaceMode.Near, out thing2, null, null, default(Rot4)); + if (this.nearPlayerStart && thing2 != null && thing2.def.category == ThingCategory.Item && TutorSystem.TutorialMode) { + Find.TutorialState.AddStartingItem(thing2); + return; } } + else { + GenSpawn.Spawn(thing, loc, map, rot, WipeMode.Vanish, false); + } } protected override bool TryFindScatterCell(Map map, out IntVec3 result) { - bool result2; if (this.clusterSize > 1) { if (this.leftInCluster <= 0) { if (!base.TryFindScatterCell(map, out this.clusterCenter)) { - Log.Error("Could not find cluster center to scatter " + this.thingDef); + Log.Error("Could not find cluster center to scatter " + this.thingDef, false); } this.leftInCluster = this.clusterSize; } this.leftInCluster--; - // EdB: Replaced hard-coded value of 4 with the added radius field. - // result = CellFinder.RandomClosewalkCellNear(this.clusterCenter, map, 4, delegate (IntVec3 x) { + // EdB: Replaced the hard-coded value of 4 with the new radius field + //result = CellFinder.RandomClosewalkCellNear(this.clusterCenter, map, 4, delegate (IntVec3 x) { result = CellFinder.RandomClosewalkCellNear(this.clusterCenter, map, radius, delegate (IntVec3 x) { Rot4 rot; return this.TryGetRandomValidRotation(x, map, out rot); }); - result2 = result.IsValid; - } - else { - result2 = base.TryFindScatterCell(map, out result); + return result.IsValid; } - return result2; + return base.TryFindScatterCell(map, out result); } private bool TryGetRandomValidRotation(IntVec3 loc, Map map, out Rot4 rot) { List possibleRotations = this.PossibleRotations; for (int i = 0; i < possibleRotations.Count; i++) { if (this.IsRotationValid(loc, possibleRotations[i], map)) { - // EdB: Changed the class name to match. + // EdB: Changed class name to match //GenStep_ScatterThings.tmpRotations.Add(possibleRotations[i]); GenStep_CustomScatterThings.tmpRotations.Add(possibleRotations[i]); } } - bool result; - // EdB: Changed the class name to match. + // EdB: Changed class name to match //if (GenStep_ScatterThings.tmpRotations.TryRandomElement(out rot)) { - // GenStep_ScatterThings.tmpRotations.Clear(); - // result = true; - //} if (GenStep_CustomScatterThings.tmpRotations.TryRandomElement(out rot)) { + // EdB: Changed class name to match + //GenStep_ScatterThings.tmpRotations.Clear(); GenStep_CustomScatterThings.tmpRotations.Clear(); - result = true; - } - else { - rot = Rot4.Invalid; - result = false; + return true; } - return result; + rot = Rot4.Invalid; + return false; } } } diff --git a/Source/HarmonyPatches.cs b/Source/HarmonyPatches.cs index 3d99084..94062a6 100644 --- a/Source/HarmonyPatches.cs +++ b/Source/HarmonyPatches.cs @@ -1,4 +1,4 @@ -using Harmony; +using HarmonyLib; using RimWorld; using System; using System.Collections.Generic; @@ -11,79 +11,87 @@ namespace EdB.PrepareCarefully { - [StaticConstructorOnStartup] - internal class HarmonyPatches { - static HarmonyPatches() { - try { - Type pageConfigureStartingPawnsType = ReflectionUtil.TypeByName("RimWorld.Page_ConfigureStartingPawns"); - Type gameType = ReflectionUtil.TypeByName("Verse.Game"); - HarmonyInstance harmony = HarmonyInstance.Create("EdB.PrepareCarefully"); - if (pageConfigureStartingPawnsType != null) { - if (harmony.Patch(pageConfigureStartingPawnsType.GetMethod("PreOpen"), - new HarmonyMethod(null), - new HarmonyMethod(typeof(HarmonyPatches).GetMethod("PreOpenPostfix"))) == null) { - Log.Warning("Prepare Carefully did not successfully patch the Page_ConfigureStartingPawns.PreOpen method. The Prepare Carefully button may not appear properly."); - } - if (harmony.Patch(pageConfigureStartingPawnsType.GetMethod("DoWindowContents"), - new HarmonyMethod(null), - new HarmonyMethod(typeof(HarmonyPatches).GetMethod("DoWindowContentsPostfix"))) == null) { - Log.Warning("Prepare Carefully did not successfully patch the Page_ConfigureStartingPawns.DoWindowContentsPostfix method. The Prepare Carefully button may not appear properly."); - } - } - else { - Log.Warning("Could not add the Prepare Carefully button to the configure pawns page. Could not find the required type."); - } - if (gameType != null) { - if (harmony.Patch(gameType.GetMethod("InitNewGame"), - new HarmonyMethod(null), - new HarmonyMethod(typeof(HarmonyPatches).GetMethod("InitNewGamePostfix"))) == null) { - Log.Warning("Prepare Carefully did not successfully patch the Game.InitNewGame method. Prepare Carefully may not properly spawn pawns and items onto the map."); - } + namespace HarmonyPatches { + [StaticConstructorOnStartup] + internal static class Main { + static Main() { + //LongEventHandler.ExecuteWhenFinished(() => { + var harmony = new Harmony("EdB.PrepareCarefully"); + harmony.PatchAll(Assembly.GetExecutingAssembly()); + + HashSet> patchedMethods = new HashSet>(); + foreach (var m in harmony.GetPatchedMethods()) { + patchedMethods.Add((m.DeclaringType, m.Name)); } - else { - Log.Warning("Could not modify the game initialization routine as needed for Prepare Carefully. Could not find the required type."); + if (patchedMethods.Count != 3 + || !patchedMethods.Contains((typeof(Page_ConfigureStartingPawns), "PreOpen")) + || !patchedMethods.Contains((typeof(Page_ConfigureStartingPawns), "DoWindowContents")) + || !patchedMethods.Contains((typeof(Verse.Game), "InitNewGame")) + ) { + String methodsMessage = String.Join(", ", harmony.GetPatchedMethods().Select(i => i.DeclaringType + "." + i.Name)); + Log.Warning("[Prepare Carefully] Did not patch all of the expected methods. The following patched methods were found: " + + (!methodsMessage.NullOrEmpty() ? methodsMessage : "none")); } } - catch (Exception e) { - Log.Warning("Failed to patch the game code as needed for Prepare Carefully. There was an unexpected exception. \n" + e.StackTrace); - } - } - - // Clear the original scenario when opening the Configure Starting Pawns page. This makes - // sure that the workaround static variable gets cleared if you quit to the main menu from - // gameplay and then start a new game. - public static void PreOpenPostfix() { - PrepareCarefully.ClearOriginalScenario(); } - // Removes the customized scenario (with PrepareCarefully-specific scenario parts) and replaces - // it with a vanilla-friendly version that was prepared earlier. This is a workaround to avoid - // creating a dependency between a saved game and the mod. See Controller.PrepareGame() for - // more details. - public static void InitNewGamePostfix() { - if (PrepareCarefully.OriginalScenario != null) { - Current.Game.Scenario = PrepareCarefully.OriginalScenario; + [HarmonyPatch(typeof(Page_ConfigureStartingPawns))] + [HarmonyPatch("PreOpen")] + [HarmonyPatch(new Type[0])] + class ClearOriginalScenarioPatch { + [HarmonyPostfix] + static void Postfix() { PrepareCarefully.ClearOriginalScenario(); } } - // Draw the "Prepare Carefully" button at the bottom of the Configure Starting Pawns page. - public static void DoWindowContentsPostfix(Rect rect, Page_ConfigureStartingPawns __instance) { - Vector2 BottomButSize = new Vector2(150f, 38f); - float num = rect.height + 45f; - Rect rect4 = new Rect(rect.x + rect.width / 2f - BottomButSize.x / 2f, num, BottomButSize.x, BottomButSize.y); - if (Widgets.ButtonText(rect4, "EdB.PC.Page.Button.PrepareCarefully".Translate(), true, false, true)) { - try { - PrepareCarefully.Instance.Initialize(); - PrepareCarefully.Instance.OriginalPage = __instance; - Page_PrepareCarefully page = new Page_PrepareCarefully(); - PrepareCarefully.Instance.State.Page = page; - Find.WindowStack.Add(page); + [HarmonyPatch(typeof(Verse.Game))] + [HarmonyPatch("InitNewGame")] + [HarmonyPatch(new Type[0])] + class ReplaceScenarioPatch { + [HarmonyPostfix] + static void Postfix() { + if (PrepareCarefully.OriginalScenario != null) { + Current.Game.Scenario = PrepareCarefully.OriginalScenario; + PrepareCarefully.ClearOriginalScenario(); } - catch (Exception e) { - Find.WindowStack.Add(new DialogInitializationError()); - SoundDefOf.ClickReject.PlayOneShot(null); - throw e; + } + } + + [HarmonyPatch(typeof(Page_ConfigureStartingPawns))] + [HarmonyPatch("DoWindowContents")] + [HarmonyPatch(new[] { typeof(Rect) })] + class PrepareCarefullyButtonPatch { + static void Postfix(Page_ConfigureStartingPawns __instance, ref Rect rect) { + Vector2 BottomButSize = new Vector2(150f, 38f); + float num = rect.height + 45f; + Rect rect4 = new Rect(rect.x + rect.width / 2f - BottomButSize.x / 2f, num, BottomButSize.x, BottomButSize.y); + if (Widgets.ButtonText(rect4, "EdB.PC.Page.Button.PrepareCarefully".Translate(), true, false, true)) { + try { + ReflectionCache.Instance.Initialize(); + + PrepareCarefully prepareCarefully = PrepareCarefully.Instance; + if (prepareCarefully == null) { + Log.Error("Could not open Prepare Carefully screen, because we failed to get the Prepare Carefully singleton."); + return; + } + prepareCarefully.Initialize(); + prepareCarefully.OriginalPage = __instance; + Page_PrepareCarefully page = new Page_PrepareCarefully(); + + State state = prepareCarefully.State; + if (state == null) { + Log.Error("Could not open Prepare Carefully screen, because the Prepare Carefully state was null."); + return; + } + state.Page = page; + Find.WindowStack.Add(page); + } + catch (Exception e) { + Find.WindowStack.Add(new DialogInitializationError(e)); + SoundDefOf.ClickReject.PlayOneShot(null); + throw new InitializationException("Prepare Carefully failed to initialize", e); + } } } } diff --git a/Source/InitializationException.cs b/Source/InitializationException.cs new file mode 100644 index 0000000..b3fa295 --- /dev/null +++ b/Source/InitializationException.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdB.PrepareCarefully { + public class InitializationException : Exception { + public InitializationException() { + } + + public InitializationException(string message) + : base(message) { + } + + public InitializationException(string message, Exception inner) + : base(message, inner) { + } + } +} diff --git a/Source/Injury.cs b/Source/Injury.cs index 0d1f10a..175140c 100644 --- a/Source/Injury.cs +++ b/Source/Injury.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Reflection; @@ -104,7 +104,8 @@ public override void AddToPawn(CustomPawn customPawn, Pawn pawn) { HediffComp_GetsPermanent getsPermanent = hediff.TryGetComp(); if (getsPermanent != null) { getsPermanent.IsPermanent = true; - ReflectionUtil.SetNonPublicField(getsPermanent, "painFactor", painFactor == null ? 0 : painFactor.Value); + Reflection.HediffComp_GetsPermanent.SetPainCategory(getsPermanent, PainCategoryForFloat(painFactor == null ? 0 : painFactor.Value)); + //ReflectionUtil.SetNonPublicField(getsPermanent, "painFactor", painFactor == null ? 0 : painFactor.Value); } pawn.health.AddHediff(hediff, BodyPartRecord, null); @@ -119,6 +120,24 @@ public override void AddToPawn(CustomPawn customPawn, Pawn pawn) { pawn.health.capacities.Clear(); } + // 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. + 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); diff --git a/Source/Logger.cs b/Source/Logger.cs index a084921..3cbd533 100644 --- a/Source/Logger.cs +++ b/Source/Logger.cs @@ -1,42 +1,27 @@ -using System; +using System; using Verse; namespace EdB.PrepareCarefully { - public class Logger { - private bool debugEnabled = false; + public static class Logger { + private static readonly string Prefix = "[Prepare Carefully] "; + private static readonly bool DebugEnabled = false; - public bool DebugEnabled { - get { - return debugEnabled; - } - set { - debugEnabled = value; - } - } - - public Logger() { - } - - public Logger(bool debugEnabled) { - this.debugEnabled = debugEnabled; - } - - public void Debug(string message) { - if (debugEnabled) { + public static void Debug(string message) { + if (DebugEnabled) { Log.Message(message); } } - public void Message(string message) { - Log.Message(message); + public static void Message(string message) { + Log.Message(Prefix + message); } - public void Warning(string message) { - Log.Warning(message); + public static void Warning(string message) { + Log.Warning(Prefix + message); } - public void Error(string message) { - Log.Error(message); + public static void Error(string message) { + Log.Error(Prefix + message); } } } diff --git a/Source/OptionsHealth.cs b/Source/OptionsHealth.cs index dd65916..bf22f89 100644 --- a/Source/OptionsHealth.cs +++ b/Source/OptionsHealth.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -158,7 +158,7 @@ public List ImplantRecipes { } public void Sort() { implantRecipes.Sort((RecipeDef a, RecipeDef b) => { - return a.LabelCap.CompareTo(b.LabelCap); + return a.LabelCap.Resolve().CompareTo(b.LabelCap); }); injuryOptions.Sort((InjuryOption a, InjuryOption b) => { return a.Label.CompareTo(b.Label); diff --git a/Source/PanelAppearance.cs b/Source/PanelAppearance.cs index 869a0b2..8781c3f 100644 --- a/Source/PanelAppearance.cs +++ b/Source/PanelAppearance.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -93,7 +93,7 @@ public PanelAppearance() { return 1; } else { - return a.LabelCap.CompareTo(b.LabelCap); + return a.LabelCap.Resolve().CompareTo(b.LabelCap); } } else { diff --git a/Source/PanelBackstory.cs b/Source/PanelBackstory.cs index 653c1ce..e087266 100644 --- a/Source/PanelBackstory.cs +++ b/Source/PanelBackstory.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using UnityEngine; using Verse; using Verse.Sound; @@ -13,7 +14,7 @@ public class PanelBackstory : PanelBase { public event UpdateBackstoryHandler BackstoryUpdated; public event RandomizeBackstoriesHandler BackstoriesRandomized; - private ProviderBackstories providerBackstories = new ProviderBackstories(); + private ProviderBackstories providerBackstories = PrepareCarefully.Instance.Providers.Backstories; private Rect RectAdulthoodLabel; private Rect RectChildhoodLabel; private Rect RectAdulthoodField; @@ -90,7 +91,7 @@ protected override void DrawPanelContent(State state) { else { FieldChildhood.Label = null; } - FieldChildhood.Tip = pawn.Childhood.FullDescriptionFor(pawn.Pawn); + FieldChildhood.Tip = pawn.Childhood.CheckedDescriptionFor(pawn.Pawn); FieldChildhood.ClickAction = () => { ShowBackstoryDialog(pawn, BackstorySlot.Childhood); }; @@ -104,7 +105,7 @@ protected override void DrawPanelContent(State state) { FieldAdulthood.Enabled = isAdult; if (isAdult) { FieldAdulthood.Label = pawn.Adulthood.TitleCapFor(pawn.Gender); - FieldAdulthood.Tip = pawn.Adulthood.FullDescriptionFor(pawn.Pawn); + FieldAdulthood.Tip = pawn.Adulthood.CheckedDescriptionFor(pawn.Pawn); FieldAdulthood.ClickAction = () => { ShowBackstoryDialog(pawn, BackstorySlot.Adulthood); }; @@ -151,14 +152,14 @@ protected void ShowBackstoryDialog(CustomPawn customPawn, BackstorySlot slot) { Filter filterToRemove = null; bool filterListDirtyFlag = true; List fullOptionsList = slot == BackstorySlot.Childhood ? - this.providerBackstories.GetChildhoodBackstoriesForPawn(customPawn) : this.providerBackstories.GetAdulthoodBackstoriesForPawn(customPawn); + this.providerBackstories.AllChildhookBackstories : this.providerBackstories.AllAdulthookBackstories; List filteredBackstories = new List(fullOptionsList.Count); Dialog_Options dialog = new Dialog_Options(filteredBackstories) { NameFunc = (Backstory backstory) => { return backstory.TitleCapFor(customPawn.Gender); }, DescriptionFunc = (Backstory backstory) => { - return backstory.FullDescriptionFor(customPawn.Pawn); + return backstory.CheckedDescriptionFor(customPawn.Pawn); }, SelectedFunc = (Backstory backstory) => { return selectedBackstory == backstory; diff --git a/Source/PanelEquipmentSelected.cs b/Source/PanelEquipmentSelected.cs index 12905ca..ecbcbfa 100644 --- a/Source/PanelEquipmentSelected.cs +++ b/Source/PanelEquipmentSelected.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -139,11 +139,6 @@ public override void Resize(Rect rect) { protected override void DrawPanelContent(State state) { base.DrawPanelContent(state); - // TODO: Why were we doing it this way? - //IEnumerable entries = PrepareCarefully.Instance.Equipment.Select((EquipmentSelection equipment) => { - // return FindEntry(equipment); - //}).Where((EquipmentSelection equipment) => { return equipment != null; }); - IEnumerable entries = SelectedEquipment; if (table.Selected == null) { diff --git a/Source/PanelPawnList.cs b/Source/PanelPawnList.cs index 6609044..0cd1867 100644 --- a/Source/PanelPawnList.cs +++ b/Source/PanelPawnList.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -291,7 +291,7 @@ protected override void DrawPanelContent(State state) { GUI.color = Color.white; Text.Font = GameFont.Tiny; if (Widgets.ButtonText(RectButtonAdd, "EdB.PC.Common.Add".Translate(), true, false, true)) { - SoundDefOf.SelectDesignator.PlayOneShotOnCamera(); + SoundDefOf.Click.PlayOneShotOnCamera(); AddingPawn(StartingPawns); } if (Widgets.ButtonText(RectButtonAdvancedAdd, "...", true, false, true)) { @@ -353,7 +353,7 @@ protected void ShowPawnKindDialog() { RowGroups = rowGroups, DisabledOptions = disabled, CloseAction = () => { - SoundDefOf.SelectDesignator.PlayOneShotOnCamera(); + SoundDefOf.Click.PlayOneShotOnCamera(); if (selected != null) { PrepareCarefully.Instance.State.LastSelectedPawnKindDef = selected; AddingPawnWithPawnKind(selected, StartingPawns); diff --git a/Source/PanelRelationshipsOther.cs b/Source/PanelRelationshipsOther.cs index c209ab3..0535f6c 100644 --- a/Source/PanelRelationshipsOther.cs +++ b/Source/PanelRelationshipsOther.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -38,7 +38,7 @@ public PanelRelationshipsOther() { if (def.familyByBloodRelation) { return false; } - MethodInfo info = def.workerClass.GetMethod("CreateRelation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + MethodInfo info = ReflectionUtil.Method(def.workerClass, "CreateRelation"); if (info == null) { return false; } diff --git a/Source/PanelSkills.cs b/Source/PanelSkills.cs index de8e274..be40b36 100644 --- a/Source/PanelSkills.cs +++ b/Source/PanelSkills.cs @@ -224,7 +224,7 @@ public static void FillableBar(Rect rect, float fillPercent, Texture2D fillTex) private void DrawSkill(CustomPawn customPawn, SkillRecord skill, Rect rect) { int level = skill.Level; - bool disabled = customPawn.IsSkillDisabled(skill.def); + bool disabled = skill.TotallyDisabled; if (!disabled) { float barSize = (level > 0 ? (float)level : 0) / 20f; FillableBar(rect, barSize, Textures.TextureSkillBarFill); diff --git a/Source/PanelTraits.cs b/Source/PanelTraits.cs index 6464225..d67814f 100644 --- a/Source/PanelTraits.cs +++ b/Source/PanelTraits.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -62,6 +62,7 @@ protected override void DrawPanelContent(State state) { } base.DrawPanelContent(state); + Action clickAction = null; float cursor = 0; GUI.color = Color.white; GUI.BeginGroup(RectScrollFrame); @@ -141,10 +142,16 @@ protected override void DrawPanelContent(State state) { Find.WindowStack.Add(dialog); }; field.PreviousAction = () => { - SelectPreviousTrait(currentPawn, index); + var capturedIndex = index; + clickAction = () => { + SelectPreviousTrait(currentPawn, capturedIndex); + }; }; field.NextAction = () => { - SelectNextTrait(currentPawn, index); + var capturedIndex = index; + clickAction = () => { + SelectNextTrait(currentPawn, capturedIndex); + }; }; field.Draw(); @@ -175,6 +182,11 @@ protected override void DrawPanelContent(State state) { GUI.color = Color.white; + if (clickAction != null) { + clickAction(); + clickAction = null; + } + // Randomize traits button. Rect randomizeRect = new Rect(PanelRect.width - 32, 9, 22, 22); if (randomizeRect.Contains(Event.current.mousePosition)) { diff --git a/Source/PawnColorUtils.cs b/Source/PawnColorUtils.cs index 846cb32..0a7918d 100644 --- a/Source/PawnColorUtils.cs +++ b/Source/PawnColorUtils.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Reflection; @@ -27,11 +27,6 @@ public class PawnColorUtils { public static void InitializeColors() { List values = new List(); - // Get the private GetSkinDataLeftIndexByWhiteness() method from the PawnSkinColors class. - MethodInfo getSkinDataIndexOfMelaninMethod = typeof(PawnSkinColors) - .GetMethod("GetSkinDataIndexOfMelanin", - BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(float) }, null); - // Iterate all values from 0.0f to 1.0f, using increments of 0.01f, to get the left index for each value. // Use this technique to construct a list of all of the indexes and their values. Once we have the list // of indexes and their values, we can use the GetSkinColor() method to get the actual colors. @@ -40,7 +35,7 @@ public static void InitializeColors() { float f = 0.01f; int counter = 1; while (f < 1.0f) { - int result = (int)getSkinDataIndexOfMelaninMethod.Invoke(null, new object[] { f }); + int result = Reflection.PawnSkinColors.GetSkinDataIndexOfMelanin(f); if (result != currentIndex) { currentIndex = result; values.Add(f); diff --git a/Source/PawnGenerationRequestWrapper.cs b/Source/PawnGenerationRequestWrapper.cs index b387f6d..fd40f7f 100644 --- a/Source/PawnGenerationRequestWrapper.cs +++ b/Source/PawnGenerationRequestWrapper.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -17,9 +17,16 @@ class PawnGenerationRequestWrapper { private float? fixedChronologicalAge = null; private Gender? fixedGender = null; private bool worldPawnFactionDoesntMatter = false; + private bool mustBeCapableOfViolence = false; public PawnGenerationRequestWrapper() { } private PawnGenerationRequest CreateRequest() { + //public PawnGenerationRequest ( + + //string fixedBirthName = null, + //RoyalTitleDef fixedTitle = null) + + return new PawnGenerationRequest( kindDef, // PawnKindDef kind faction, // Faction faction = null @@ -30,23 +37,31 @@ private PawnGenerationRequest CreateRequest() { false, //bool allowDead = false, false, //bool allowDowned = false, false, //bool canGeneratePawnRelations = true, - false, //bool mustBeCapableOfViolence = false, + mustBeCapableOfViolence, //bool mustBeCapableOfViolence = false, 0f, //float colonistRelationChanceFactor = 1f, false, //bool forceAddFreeWarmLayerIfNeeded = false, true, //bool allowGay = true, false, //bool allowFood = true, + false, //bool allowAddictions = true, false, // bool inhabitant = false false, // bool certainlyBeenInCryptosleep = false false, // bool forceRedressWorldPawnIfFormerColonist = false worldPawnFactionDoesntMatter, // bool worldPawnFactionDoesntMatter = false + 0f, //float biocodeWeaponChance = 0f, + null, //Pawn extraPawnForExtraRelationChance = null, + 1f, //float relationWithExtraPawnChanceFactor = 1f, null, // Predicate < Pawn > validatorPreGear = null null, // Predicate < Pawn > validatorPostGear = null + Enumerable.Empty(), //IEnumerable forcedTraits = null, + Enumerable.Empty(), //IEnumerable prohibitedTraits = null, null, // float ? minChanceToRedressWorldPawn = null fixedBiologicalAge, // float ? fixedBiologicalAge = null null, // float ? fixedChronologicalAge = null fixedGender, // Gender ? fixedGender = null null, // float ? fixedMelanin = null - null // string fixedLastName = null + null, // string fixedLastName = null + null, //string fixedBirthName = null, + null //RoyalTitleDef fixedTitle = null ); } public PawnGenerationRequest Request { @@ -89,5 +104,10 @@ public Gender? FixedGender { fixedGender = value; } } + public bool MustBeCapableOfViolence { + set { + mustBeCapableOfViolence = value; + } + } } } diff --git a/Source/PrepareCarefully.cs b/Source/PrepareCarefully.cs index 509155f..455d5c4 100644 --- a/Source/PrepareCarefully.cs +++ b/Source/PrepareCarefully.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -170,6 +170,7 @@ protected void InitializeProviders() { Providers.Factions = new ProviderFactions(); Providers.PawnLayers = new ProviderPawnLayers(); Providers.AgeLimits = new ProviderAgeLimits(); + Providers.Backstories = new ProviderBackstories(); } // TODO: @@ -260,11 +261,10 @@ record = AddNonStandardScenarioEquipmentEntry(key); private static PawnKindDef RandomPet(ScenPart_StartingAnimal startingAnimal) { FieldInfo animalKindField = typeof(ScenPart_StartingAnimal).GetField("animalKind", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo randomPetsMethod = typeof(ScenPart_StartingAnimal).GetMethod("RandomPets", BindingFlags.Instance | BindingFlags.NonPublic); PawnKindDef animalKindDef = (PawnKindDef)animalKindField.GetValue(startingAnimal); if (animalKindDef == null) { - IEnumerable animalKindDefs = (IEnumerable)randomPetsMethod.Invoke(startingAnimal, null); + IEnumerable animalKindDefs = Reflection.ScenPart_StartingAnimal.RandomPets(startingAnimal); animalKindDef = animalKindDefs.RandomElementByWeight((PawnKindDef td) => td.RaceProps.petness); } return animalKindDef; diff --git a/Source/PresetFiles.cs b/Source/PresetFiles.cs index 52b0c63..19df8eb 100644 --- a/Source/PresetFiles.cs +++ b/Source/PresetFiles.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.IO; @@ -10,7 +10,7 @@ public static class PresetFiles { public static string SavedPresetsFolderPath { get { try { - return (string)typeof(GenFilePaths).GetMethod("FolderUnderSaveData", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { "PrepareCarefully" }); + return Reflection.GenFilePaths.FolderUnderSaveData("PrepareCarefully"); } catch (Exception e) { Log.Error("Failed to get preset save directory"); diff --git a/Source/PresetLoader.cs b/Source/PresetLoader.cs index 8a9dcfe..6240ba4 100644 --- a/Source/PresetLoader.cs +++ b/Source/PresetLoader.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Reflection; @@ -41,6 +41,16 @@ public static bool LoadFromFile(PrepareCarefully loadout, string presetName) { return result; } + + public static void ClearSaveablesAndCrossRefs() { + // I don't fully understand how these cross-references and saveables are resolved, but + // if we don't clear them out, we get null pointer exceptions. + Reflection.PostLoadIniter.ClearSaveablesToPostLoad(Scribe.loader.initer); + if (Scribe.loader.crossRefs.crossReferencingExposables != null) { + Scribe.loader.crossRefs.crossReferencingExposables.Clear(); + } + Scribe.loader.FinalizeLoading(); + } } } diff --git a/Source/ProviderBackstories.cs b/Source/ProviderBackstories.cs index 9a86345..3b8127a 100644 --- a/Source/ProviderBackstories.cs +++ b/Source/ProviderBackstories.cs @@ -12,18 +12,41 @@ public class ProviderBackstories { protected Dictionary> childhoodBackstoryLookup = new Dictionary>(); protected Dictionary> adulthoodBackstoryLookup = new Dictionary>(); + protected Dictionary> backstoryHashSetLookup = new Dictionary>(); public List GetChildhoodBackstoriesForPawn(CustomPawn pawn) { - if (!childhoodBackstoryLookup.ContainsKey(pawn.Pawn.kindDef.defName)) { - InitializeBackstoriesForPawnKind(pawn.Pawn.kindDef); - } - return childhoodBackstoryLookup[pawn.Pawn.kindDef.defName]; + return GetChildhoodBackstoriesForPawnKindDef(pawn.OriginalKindDef); } public List GetAdulthoodBackstoriesForPawn(CustomPawn pawn) { - if (!adulthoodBackstoryLookup.ContainsKey(pawn.Pawn.kindDef.defName)) { - InitializeBackstoriesForPawnKind(pawn.Pawn.kindDef); + return GetAdulthoodBackstoriesForPawnKindDef(pawn.OriginalKindDef); + } + public List GetChildhoodBackstoriesForPawnKindDef(PawnKindDef kindDef) { + if (!backstoryHashSetLookup.ContainsKey(kindDef.defName)) { + InitializeBackstoriesForPawnKind(kindDef); + } + return childhoodBackstoryLookup[kindDef.defName]; + } + public List GetAdulthoodBackstoriesForPawnKindDef(PawnKindDef kindDef) { + if (!backstoryHashSetLookup.ContainsKey(kindDef.defName)) { + InitializeBackstoriesForPawnKind(kindDef); + } + return adulthoodBackstoryLookup[kindDef.defName]; + } + public HashSet BackstoriesForPawnKindDef(PawnKindDef kindDef) { + if (!backstoryHashSetLookup.ContainsKey(kindDef.defName)) { + InitializeBackstoriesForPawnKind(kindDef); + } + return backstoryHashSetLookup[kindDef.defName]; + } + public List AllChildhookBackstories { + get { + return sortedChildhoodBackstories; + } + } + public List AllAdulthookBackstories { + get { + return sortedAdulthoodBackstories; } - return adulthoodBackstoryLookup[pawn.Pawn.kindDef.defName]; } public ProviderBackstories() { // Go through all of the backstories and mark them as childhood or adult. @@ -45,18 +68,87 @@ public ProviderBackstories() { } private void InitializeBackstoriesForPawnKind(PawnKindDef def) { - HashSet pawnKindBackstoryCategories = new HashSet(def.backstoryCategories); + HashSet categories = BackstoryCategoriesForPawnKindDef(def); List childhood = BackstoryDatabase.allBackstories.Values.Where((b) => { - return (b.slot == BackstorySlot.Childhood); + if (b.slot != BackstorySlot.Childhood) { + return false; + } + foreach (var c in b.spawnCategories) { + if (categories.Contains(c)) { + return true; + } + } + return false; }).ToList(); childhood.Sort((b1, b2) => b1.TitleCapFor(Gender.Male).CompareTo(b2.TitleCapFor(Gender.Male))); childhoodBackstoryLookup[def.defName] = childhood; List adulthood = BackstoryDatabase.allBackstories.Values.Where((b) => { - return (b.slot == BackstorySlot.Adulthood); + if (b.slot != BackstorySlot.Adulthood) { + return false; + } + foreach (var c in b.spawnCategories) { + if (categories.Contains(c)) { + return true; + } + } + return false; }).ToList(); adulthood.Sort((b1, b2) => b1.TitleCapFor(Gender.Male).CompareTo(b2.TitleCapFor(Gender.Male))); adulthoodBackstoryLookup[def.defName] = adulthood; + + HashSet backstorySet = new HashSet(childhood); + backstorySet.AddRange(adulthood); + this.backstoryHashSetLookup[def.defName] = backstorySet; + } + + public HashSet BackstoryCategoriesForPawnKindDef(PawnKindDef kindDef) { + Faction faction = PrepareCarefully.Instance.Providers.Factions.GetFaction(kindDef); + var filters = GetBackstoryCategoryFiltersFor(kindDef, faction != null ? faction.def : null); + return AllBackstoryCategoriesFromFilterList(filters); + } + + // EVERY RELEASE: + // Evaluate to make sure the logic in PawnBioAndNameGenerator.GetBackstoryCategoryFiltersFor() has not changed in a way + // that invalidates this rewrite. This is a modified version of that method but with the first argument a PawnKindDef + // instead of a Pawn and with logging removed. + private List GetBackstoryCategoryFiltersFor(PawnKindDef kindDef, FactionDef faction) { + if (!kindDef.backstoryFiltersOverride.NullOrEmpty()) { + return kindDef.backstoryFiltersOverride; + } + List list = new List(); + if (kindDef.backstoryFilters != null) { + list.AddRange(kindDef.backstoryFilters); + } + if (faction != null && !faction.backstoryFilters.NullOrEmpty()) { + for (int i = 0; i < faction.backstoryFilters.Count; i++) { + BackstoryCategoryFilter item = faction.backstoryFilters[i]; + if (!list.Contains(item)) { + list.Add(item); + } + } + } + if (!list.NullOrEmpty()) { + return list; + } + return new List { + new BackstoryCategoryFilter { + categories = new List { + "Civil" + }, + commonality = 1f + } + }; + } + + private HashSet AllBackstoryCategoriesFromFilterList(List filterList) { + HashSet result = new HashSet(); + foreach (var filter in filterList) { + foreach (var category in filter.categories) { + result.Add(category); + } + } + return result; } } } diff --git a/Source/ProviderFactions.cs b/Source/ProviderFactions.cs index 9b28ecb..68aef4c 100644 --- a/Source/ProviderFactions.cs +++ b/Source/ProviderFactions.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; diff --git a/Source/ProviderHeadTypes.cs b/Source/ProviderHeadTypes.cs index 481182e..e2a03c0 100644 --- a/Source/ProviderHeadTypes.cs +++ b/Source/ProviderHeadTypes.cs @@ -82,8 +82,7 @@ protected OptionsHeadType InitializeHeadTypes(ThingDef race) { return result; } protected OptionsHeadType InitializeHumanHeadTypes() { - MethodInfo headGraphicsMethod = typeof(GraphicDatabaseHeadRecords).GetMethod("BuildDatabaseIfNecessary", BindingFlags.Static | BindingFlags.NonPublic); - headGraphicsMethod.Invoke(null, null); + Reflection.GraphicDatabaseHeadRecords.BuildDatabaseIfNecessary(); string[] headsFolderPaths = new string[] { "Things/Pawn/Humanlike/Heads/Male", "Things/Pawn/Humanlike/Heads/Female" diff --git a/Source/ProviderHealthOptions.cs b/Source/ProviderHealthOptions.cs index cbb6f96..e82a31d 100644 --- a/Source/ProviderHealthOptions.cs +++ b/Source/ProviderHealthOptions.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -159,8 +159,7 @@ protected void InitializeInjuryOptions(OptionsHealth options, ThingDef pawnThing // Get all of the hediffs that can be added via the "forced hediff" scenario part and // add them to a hash set so that we can quickly look them up. ScenPart_ForcedHediff scenPart = new ScenPart_ForcedHediff(); - IEnumerable scenPartDefs = (IEnumerable)typeof(ScenPart_ForcedHediff) - .GetMethod("PossibleHediffs", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(scenPart, null); + IEnumerable scenPartDefs = Reflection.ScenPart_ForcedHediff.PossibleHediffs(scenPart); HashSet scenPartDefSet = new HashSet(scenPartDefs); // Add injury options. diff --git a/Source/Providers.cs b/Source/Providers.cs index 4828db8..9f15593 100644 --- a/Source/Providers.cs +++ b/Source/Providers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,6 +11,9 @@ public ProviderAlienRaces AlienRaces { public ProviderApparel Apparel { get; set; } + public ProviderBackstories Backstories { + get; set; + } public ProviderBodyTypes BodyTypes { get; set; } diff --git a/Source/Randomizer.cs b/Source/Randomizer.cs index 05cbe29..5741235 100644 --- a/Source/Randomizer.cs +++ b/Source/Randomizer.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -105,11 +105,13 @@ public static Backstory RandomAdulthood(CustomPawn customPawn) { if (factionDef == null) { factionDef = Faction.OfPlayer.def; } - MethodInfo method = typeof(PawnBioAndNameGenerator).GetMethod("FillBackstorySlotShuffled", BindingFlags.Static | BindingFlags.NonPublic); - object[] arguments = new object[] { customPawn.Pawn, BackstorySlot.Adulthood, null, kindDef.backstoryCategories, factionDef }; - method.Invoke(null, arguments); - Backstory result = arguments[2] as Backstory; - return result; + + List backstoryCategoryFiltersFor = Reflection.PawnBioAndNameGenerator + .GetBackstoryCategoryFiltersFor(customPawn.Pawn, factionDef); + if (!Reflection.PawnBioAndNameGenerator.TryGetRandomUnusedSolidBioFor(backstoryCategoryFiltersFor, kindDef, customPawn.Gender, null, out PawnBio pawnBio)) { + return customPawn.Adulthood; + } + return pawnBio.adulthood; } public void RandomizeName(CustomPawn customPawn) { diff --git a/Source/Reflection.cs b/Source/Reflection.cs new file mode 100644 index 0000000..abbf3fc --- /dev/null +++ b/Source/Reflection.cs @@ -0,0 +1,86 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Verse; + +namespace EdB.PrepareCarefully { + namespace Reflection { + public static class CharacterCardUtility { + public static IEnumerable WorkTagsFrom(WorkTags workTags) { + return (IEnumerable)ReflectionCache.Instance.CharacterCardUtility_WorkTagsFrom.Invoke(null, new object[] { workTags }); + } + } + public static class ScenPart_StartingAnimal { + public static IEnumerable RandomPets(RimWorld.ScenPart_StartingAnimal scenPart) { + return (IEnumerable)ReflectionCache.Instance.ScenPart_StartingAnimal_RandomPets.Invoke(scenPart, null); + } + } + public static class GenFilePaths { + public static string FolderUnderSaveData(string name) { + return (string)ReflectionCache.Instance.GenFilePaths_FolderUnderSaveData.Invoke(null, new object[] { name }); + } + } + public static class ScenPart_ForcedHediff { + public static IEnumerable PossibleHediffs(RimWorld.ScenPart_ForcedHediff scenPart) { + return (IEnumerable)ReflectionCache.Instance.ScenPart_ForcedHediff_PossibleHediffs.Invoke(scenPart, null); + } + } + public static class PawnSkinColors { + public static int GetSkinDataIndexOfMelanin(float value) { + return (int)ReflectionCache.Instance.PawnSkinColors_GetSkinDataIndexOfMelanin.Invoke(null, new object[] { value }); + } + } + public static class GraphicDatabaseHeadRecords { + public static void BuildDatabaseIfNecessary() { + ReflectionCache.Instance.GraphicDatabaseHeadRecords_BuildDatabaseIfNecessary.Invoke(null, null); + } + } + public static class Pawn { + public static void ClearCachedDisabledWorkTypes(Verse.Pawn pawn) { + ReflectionCache.Instance.Pawn_CachedDisabledWorkTypes.SetValue(pawn, null); + } + public static void ClearCachedDisabledWorkTypesPermanent(Verse.Pawn pawn) { + ReflectionCache.Instance.Pawn_CachedDisabledWorkTypesPermanent.SetValue(pawn, null); + } + } + public static class PawnBioAndNameGenerator { + public static void FillBackstorySlotShuffled(Verse.Pawn pawn, BackstorySlot slot, ref Backstory backstory, Backstory backstoryOtherSlot, List backstoryCategories, FactionDef factionType) { + ReflectionCache.Instance.PawnBioAndNameGenerator_FillBackstorySlotShuffled.Invoke(null, + new object[] { + pawn, slot, backstory, backstoryOtherSlot, backstoryCategories, factionType + } + ); + } + public static List GetBackstoryCategoryFiltersFor(Verse.Pawn pawn, FactionDef faction) { + return (List)ReflectionCache.Instance.PawnBioAndNameGenerator_GetBackstoryCategoryFiltersFor.Invoke(null, + new object[] { pawn, faction } + ); + } + public static bool TryGetRandomUnusedSolidBioFor(List backstoryCategories, PawnKindDef kind, Gender gender, string requiredLastName, out PawnBio result) { + Object[] args = new object[] { backstoryCategories, kind, gender, requiredLastName, null }; + bool value = (bool)ReflectionCache.Instance.PawnBioAndNameGenerator_TryGetRandomUnusedSolidBioFor.Invoke(null, args); + result = args[4] as PawnBio; + return value; + } + } + public static class PostLoadIniter { + public static void ClearSaveablesToPostLoad(Verse.PostLoadIniter initer) { + HashSet saveables = (HashSet)ReflectionCache.Instance.PostLoadIniter_SaveablesToPostLoad.GetValue(Scribe.loader.initer); + if (saveables != null) { + saveables.Clear(); + } + } + } + + public static class HediffComp_GetsPermanent { + public static void SetPainCategory(Verse.HediffComp_GetsPermanent comp, PainCategory painCategory) { + ReflectionCache.Instance.HediffComp_GetsPermanent_PainCategory.SetValue(comp, painCategory); + } + } + + } +} diff --git a/Source/ReflectionCache.cs b/Source/ReflectionCache.cs new file mode 100644 index 0000000..d3f68eb --- /dev/null +++ b/Source/ReflectionCache.cs @@ -0,0 +1,63 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Verse; + +namespace EdB.PrepareCarefully { + public class ReflectionCache { + private static ReflectionCache instance; + + public MethodInfo GraphicDatabaseHeadRecords_BuildDatabaseIfNecessary { get; set; } + public MethodInfo CharacterCardUtility_WorkTagsFrom { get; set; } + public MethodInfo GenFilePaths_FolderUnderSaveData { get; set; } + public MethodInfo PawnBioAndNameGenerator_FillBackstorySlotShuffled { get; set; } + public MethodInfo PawnBioAndNameGenerator_GetBackstoryCategoryFiltersFor { get; set; } + public MethodInfo PawnBioAndNameGenerator_TryGetRandomUnusedSolidBioFor { get; set; } + public MethodInfo PawnSkinColors_GetSkinDataIndexOfMelanin { get; set; } + public MethodInfo ScenPart_ForcedHediff_PossibleHediffs { get; set; } + public MethodInfo ScenPart_StartingAnimal_RandomPets { get; set; } + + public FieldInfo Pawn_CachedDisabledWorkTypes { get; set; } + public FieldInfo Pawn_CachedDisabledWorkTypesPermanent { get; set; } + public FieldInfo PostLoadIniter_SaveablesToPostLoad { get; set; } + public FieldInfo HediffComp_GetsPermanent_PainCategory { get; set; } + + public static ReflectionCache Instance { + get { + if (instance == null) { + instance = new ReflectionCache(); + } + return instance; + } + } + + public void Initialize() { + CharacterCardUtility_WorkTagsFrom = ReflectionUtil.RequiredMethod(typeof(CharacterCardUtility), "WorkTagsFrom"); + GraphicDatabaseHeadRecords_BuildDatabaseIfNecessary = ReflectionUtil.RequiredMethod(typeof(GraphicDatabaseHeadRecords), "BuildDatabaseIfNecessary"); + GenFilePaths_FolderUnderSaveData = ReflectionUtil.RequiredMethod(typeof(GenFilePaths), "FolderUnderSaveData", new Type[] { typeof(string) }); + + PawnBioAndNameGenerator_FillBackstorySlotShuffled = ReflectionUtil.RequiredMethod(typeof(PawnBioAndNameGenerator), "FillBackstorySlotShuffled", + new Type[] { typeof(Pawn), typeof(BackstorySlot), typeof(Backstory).MakeByRefType(), typeof(Backstory), typeof(List), typeof(FactionDef) }); + + PawnBioAndNameGenerator_GetBackstoryCategoryFiltersFor = ReflectionUtil.RequiredMethod(typeof(PawnBioAndNameGenerator), "GetBackstoryCategoryFiltersFor", + new Type[] { typeof(Pawn), typeof(FactionDef) }); + + PawnBioAndNameGenerator_TryGetRandomUnusedSolidBioFor = ReflectionUtil.RequiredMethod(typeof(PawnBioAndNameGenerator), "TryGetRandomUnusedSolidBioFor", + new Type[] { typeof(List), typeof(PawnKindDef), typeof(Gender), typeof(string), typeof(PawnBio).MakeByRefType() }); + + + PawnSkinColors_GetSkinDataIndexOfMelanin = ReflectionUtil.RequiredMethod(typeof(PawnSkinColors), "GetSkinDataIndexOfMelanin", new Type[] { typeof(float) }); + ScenPart_StartingAnimal_RandomPets = ReflectionUtil.RequiredMethod(typeof(ScenPart_StartingAnimal), "RandomPets"); + ScenPart_ForcedHediff_PossibleHediffs = ReflectionUtil.RequiredMethod(typeof(ScenPart_ForcedHediff), "PossibleHediffs"); + + Pawn_CachedDisabledWorkTypes = ReflectionUtil.RequiredField(typeof(Pawn), "cachedDisabledWorkTypes"); + Pawn_CachedDisabledWorkTypesPermanent = ReflectionUtil.RequiredField(typeof(Pawn), "cachedDisabledWorkTypesPermanent"); + PostLoadIniter_SaveablesToPostLoad = ReflectionUtil.RequiredField(typeof(PostLoadIniter), "saveablesToPostLoad"); + HediffComp_GetsPermanent_PainCategory = ReflectionUtil.RequiredField(typeof(HediffComp_GetsPermanent), "painCategory"); + } + } +} diff --git a/Source/ReflectionUtil.cs b/Source/ReflectionUtil.cs index fab2785..200c7a5 100644 --- a/Source/ReflectionUtil.cs +++ b/Source/ReflectionUtil.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -8,6 +8,81 @@ namespace EdB.PrepareCarefully { public class ReflectionUtil { + + public static MethodInfo Method(Type type, string name) { + MethodInfo info = type.GetMethod(name, BindingFlags.Public | BindingFlags.Instance); + if (info != null) { + return info; + } + info = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance); + if (info != null) { + return info; + } + info = type.GetMethod(name, BindingFlags.Public | BindingFlags.Static); + if (info != null) { + return info; + } + info = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static); + if (info != null) { + return info; + } + Log.Warning("Prepare Carefully could not find the method " + type.Name + "." + name + " via reflection"); + return null; + } + + public static MethodInfo MethodWithParameters(Type type, string name, Type[] argumentTypes) { + MethodInfo info = type.GetMethod(name, BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, argumentTypes, null); + if (info != null) { + return info; + } + info = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance, null, CallingConventions.Any, argumentTypes, null); + if (info != null) { + return info; + } + info = type.GetMethod(name, BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Any, argumentTypes, null); + if (info != null) { + return info; + } + info = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static, null, CallingConventions.Any, argumentTypes, null); + if (info != null) { + return info; + } + Log.Warning("Prepare Carefully could not find the method " + type.Name + "." + name + " with the given parameters via reflection"); + return null; + } + + public static MethodInfo RequiredMethod(Type type, string name, Type[] argumentTypes = null) { + MethodInfo info = argumentTypes == null ? Method(type, name) : MethodWithParameters(type, name, argumentTypes); + if (info != null) { + return info; + } + throw new MissingMethodException("Prepare Carefully requires but could not find the method " + type.Name + "." + + name + " via reflection"); + } + + public static FieldInfo Field(Type type, string name) { + FieldInfo info = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); + if (info != null) { + return info; + } + info = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Static); + if (info != null) { + return info; + } + Log.Warning("Prepare Carefully could not find the field " + type.Name + "." + name + " via reflection"); + return null; + } + + public static FieldInfo RequiredField(Type type, string name) { + FieldInfo info = Field(type, name); + if (info != null) { + return info; + } + throw new MissingFieldException("Prepare Carefully requires but could not find the field " + type.Name + "." + + name + " via reflection"); + } + + public static FieldInfo GetNonPublicField(Type type, string name) { FieldInfo info = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); if (info == null) { diff --git a/Source/Version3/ColonistLoaderVersion3.cs b/Source/Version3/ColonistLoaderVersion3.cs index b9ae735..258fb4c 100644 --- a/Source/Version3/ColonistLoaderVersion3.cs +++ b/Source/Version3/ColonistLoaderVersion3.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -33,13 +33,7 @@ public CustomPawn Load(PrepareCarefully loadout, string name) { throw e; } finally { - // I don't fully understand how these cross-references and saveables are resolved, but - // if we don't clear them out, we get null pointer exceptions. - HashSet saveables = (HashSet)(typeof(PostLoadIniter).GetField("saveablesToPostLoad", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.initer)); - saveables.Clear(); - List crossReferencingExposables = (List)(typeof(CrossRefHandler).GetField("crossReferencingExposables", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.crossRefs)); - crossReferencingExposables.Clear(); - Scribe.loader.FinalizeLoading(); + PresetLoader.ClearSaveablesAndCrossRefs(); } if (pawnRecord == null) { diff --git a/Source/Version3/PresetLoaderVersion3.cs b/Source/Version3/PresetLoaderVersion3.cs index a718654..b174c87 100644 --- a/Source/Version3/PresetLoaderVersion3.cs +++ b/Source/Version3/PresetLoaderVersion3.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -215,17 +215,7 @@ public bool Load(PrepareCarefully loadout, string presetName) { throw e; } finally { - // I don't fully understand how these cross-references and saveables are resolved, but - // if we don't clear them out, we get null pointer exceptions. - HashSet saveables = (HashSet)(typeof(PostLoadIniter).GetField("saveablesToPostLoad", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.initer)); - if (saveables != null) { - saveables.Clear(); - } - List crossReferencingExposables = (List)(typeof(CrossRefHandler).GetField("crossReferencingExposables", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.crossRefs)); - if (crossReferencingExposables != null) { - crossReferencingExposables.Clear(); - } - Scribe.loader.FinalizeLoading(); + PresetLoader.ClearSaveablesAndCrossRefs(); } List allPawns = new List(); diff --git a/Source/Version3/SaveRecordPawnV3.cs b/Source/Version3/SaveRecordPawnV3.cs index bac5a73..b7bdf87 100644 --- a/Source/Version3/SaveRecordPawnV3.cs +++ b/Source/Version3/SaveRecordPawnV3.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -41,80 +41,7 @@ public class SaveRecordPawnV3 : IExposable { public List injuries = new List(); public SaveRecordPawnV3() { - - } - - /* - public SaveRecordPawnV3(CustomPawn pawn) { - this.id = pawn.Id; - this.thingDef = pawn.Pawn.def.defName; - this.pawnKindDef = pawn.Pawn.kindDef.defName; - this.gender = pawn.Gender; - if (pawn.Adulthood != null) { - this.adulthood = pawn.Adulthood.identifier; - } - else { - this.adulthood = pawn.LastSelectedAdulthoodBackstory.identifier; - } - this.childhood = pawn.Childhood.identifier; - this.skinColor = pawn.Pawn.story.SkinColor; - this.melanin = pawn.Pawn.story.melanin; - this.hairDef = pawn.HairDef.defName; - this.hairColor = pawn.GetColor(PrepareCarefully.Instance.Providers.PawnLayers.HairLayer); - this.headGraphicPath = pawn.HeadGraphicPath; - this.bodyType = pawn.BodyType.defName; // TODO: Enum.GetName(typeof(BodyTypeDef), pawn.BodyType); - this.firstName = pawn.FirstName; - this.nickName = pawn.NickName; - this.lastName = pawn.LastName; - this.age = 0; - this.biologicalAge = pawn.BiologicalAge; - this.chronologicalAge = pawn.ChronologicalAge; - foreach (var trait in pawn.Traits) { - if (trait != null) { - this.traitNames.Add(trait.def.defName); - this.traitDegrees.Add(trait.Degree); - } - } - foreach (var skill in pawn.Pawn.skills.skills) { - this.skillNames.Add(skill.def.defName); - this.skillValues.Add(pawn.GetUnmodifiedSkillLevel(skill.def)); - this.passions.Add(pawn.currentPassions[skill.def]); - this.originalPassions.Add(pawn.originalPassions[skill.def]); - } - for (int layer = 0; layer < PawnLayers.Count; layer++) { - ThingDef apparelThingDef = pawn.GetAcceptedApparel(layer); - ThingDef apparelStuffDef = pawn.GetSelectedStuff(layer); - Color color = pawn.GetColor(layer); - if (apparelThingDef != null) { - this.apparelLayers.Add(layer); - this.apparel.Add(apparelThingDef.defName); - this.apparelStuff.Add(apparelStuffDef != null ? apparelStuffDef.defName : ""); - this.apparelColors.Add(color); - } - } - OptionsHealth healthOptions = PrepareCarefully.Instance.Providers.Health.GetOptions(pawn); - foreach (Implant implant in pawn.Implants) { - var saveRecord = new SaveRecordImplantV3(implant); - if (implant.BodyPartRecord != null) { - UniqueBodyPart part = healthOptions.FindBodyPartsForRecord(implant.BodyPartRecord); - if (part != null && part.Index > 0) { - saveRecord.bodyPartIndex = part.Index; - } - } - this.implants.Add(saveRecord); - } - foreach (Injury injury in pawn.Injuries) { - var saveRecord = new SaveRecordInjuryV3(injury); - if (injury.BodyPartRecord != null) { - UniqueBodyPart part = healthOptions.FindBodyPartsForRecord(injury.BodyPartRecord); - if (part != null && part.Index > 0) { - saveRecord.bodyPartIndex = part.Index; - } - } - this.injuries.Add(saveRecord); - } } - */ public void ExposeData() { Scribe_Values.Look(ref this.id, "id", null, false); diff --git a/Source/Version4/ColonistLoaderVersion4.cs b/Source/Version4/ColonistLoaderVersion4.cs index 148c44d..d5a42b4 100644 --- a/Source/Version4/ColonistLoaderVersion4.cs +++ b/Source/Version4/ColonistLoaderVersion4.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -33,13 +33,7 @@ public CustomPawn Load(PrepareCarefully loadout, string name) { throw e; } finally { - // I don't fully understand how these cross-references and saveables are resolved, but - // if we don't clear them out, we get null pointer exceptions. - HashSet saveables = (HashSet)(typeof(PostLoadIniter).GetField("saveablesToPostLoad", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.initer)); - saveables.Clear(); - List crossReferencingExposables = (List)(typeof(CrossRefHandler).GetField("crossReferencingExposables", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.crossRefs)); - crossReferencingExposables.Clear(); - Scribe.loader.FinalizeLoading(); + PresetLoader.ClearSaveablesAndCrossRefs(); } if (pawnRecord == null) { diff --git a/Source/Version4/PresetLoaderVersion4.cs b/Source/Version4/PresetLoaderVersion4.cs index 8ccd22c..6b0fb20 100644 --- a/Source/Version4/PresetLoaderVersion4.cs +++ b/Source/Version4/PresetLoaderVersion4.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -162,17 +162,7 @@ public bool Load(PrepareCarefully loadout, string presetName) { throw e; } finally { - // I don't fully understand how these cross-references and saveables are resolved, but - // if we don't clear them out, we get null pointer exceptions. - HashSet saveables = (HashSet)(typeof(PostLoadIniter).GetField("saveablesToPostLoad", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.initer)); - if (saveables != null) { - saveables.Clear(); - } - List crossReferencingExposables = (List)(typeof(CrossRefHandler).GetField("crossReferencingExposables", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Scribe.loader.crossRefs)); - if (crossReferencingExposables != null) { - crossReferencingExposables.Clear(); - } - Scribe.loader.FinalizeLoading(); + PresetLoader.ClearSaveablesAndCrossRefs(); } List allPawns = new List(); @@ -330,10 +320,12 @@ public CustomPawn LoadPawn(SaveRecordPawnV4 record) { generationRequest.KindDef = pawnKindDef; } Pawn source = PawnGenerator.GeneratePawn(generationRequest.Request); - source.health.Reset(); + if (source.health != null) { + source.health.Reset(); + } CustomPawn pawn = new CustomPawn(source); - if (pawn.Id == null) { + if (record.id == null) { pawn.GenerateId(); } else { @@ -368,6 +360,11 @@ public CustomPawn LoadPawn(SaveRecordPawnV4 record) { pawn.NickName = record.nickName; pawn.LastName = record.lastName; + if (record.originalFactionDef != null) { + pawn.OriginalFactionDef = DefDatabase.GetNamedSilentFail(record.originalFactionDef); + } + pawn.OriginalKindDef = pawnKindDef; + if (pawn.Type == CustomPawnType.World) { if (record.faction != null) { if (record.faction.def != null) { @@ -417,7 +414,9 @@ public CustomPawn LoadPawn(SaveRecordPawnV4 record) { } pawn.HeadGraphicPath = record.headGraphicPath; - pawn.Pawn.story.hairColor = record.hairColor; + if (pawn.Pawn.story != null) { + pawn.Pawn.story.hairColor = record.hairColor; + } if (record.melanin >= 0.0f) { pawn.MelaninLevel = record.melanin; diff --git a/Source/Version4/SaveRecordPawnV4.cs b/Source/Version4/SaveRecordPawnV4.cs index d11dab1..7e4f7fe 100644 --- a/Source/Version4/SaveRecordPawnV4.cs +++ b/Source/Version4/SaveRecordPawnV4.cs @@ -1,4 +1,4 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -11,6 +11,7 @@ public class SaveRecordPawnV4 : IExposable { public string type; public SaveRecordFactionV4 faction; public string pawnKindDef; + public string originalFactionDef; public string thingDef; public Gender gender; public string adulthood; @@ -54,7 +55,8 @@ public SaveRecordPawnV4(CustomPawn pawn) { this.faction.index = pawn.Faction.Index; this.faction.leader = pawn.Faction.Leader; } - this.pawnKindDef = pawn.Pawn.kindDef.defName; + this.pawnKindDef = pawn.OriginalKindDef != null ? pawn.OriginalKindDef.defName : pawn.Pawn.kindDef.defName; + this.originalFactionDef = pawn.OriginalFactionDef != null ? pawn.OriginalFactionDef.defName : null; this.gender = pawn.Gender; if (pawn.Adulthood != null) { this.adulthood = pawn.Adulthood.identifier; @@ -140,6 +142,7 @@ public void ExposeData() { Scribe_Values.Look(ref this.type, "type", null, false); Scribe_Deep.Look(ref this.faction, "faction"); Scribe_Values.Look(ref this.pawnKindDef, "pawnKindDef", null, false); + Scribe_Values.Look(ref this.originalFactionDef, "originalFactionDef", null, false); Scribe_Values.Look(ref this.thingDef, "thingDef", ThingDefOf.Human.defName, false); Scribe_Values.Look(ref this.gender, "gender", Gender.Male, false); Scribe_Values.Look(ref this.childhood, "childhood", null, false); diff --git a/dist.bat b/dist.bat index 4e457d8..8ce0631 100644 --- a/dist.bat +++ b/dist.bat @@ -2,9 +2,12 @@ if exist bin\Release\EdBPrepareCarefully.dll ( robocopy Resources dist\EdBPrepareCarefully\ /e /MIR xcopy LICENSE dist\EdBPrepareCarefully\ /Y - xcopy bin\Release\EdBPrepareCarefully.dll dist\EdBPrepareCarefully\Assemblies\ /Y - xcopy Libraries\0Harmony.dll dist\EdBPrepareCarefully\Assemblies\ /Y + xcopy bin\Release\EdBPrepareCarefully.dll dist\EdBPrepareCarefully\1.1\Assemblies\ /Y + xcopy Libraries\0Harmony.dll dist\EdBPrepareCarefully\1.1\Assemblies\ /Y + xcopy THIRD-PARTY-LICENSES dist\EdBPrepareCarefully\1.1\Assemblies\ /Y xcopy THIRD-PARTY-LICENSES dist\EdBPrepareCarefully\Assemblies\ /Y + del dist\EdBPrepareCarefully\Assemblies\README.md + del dist\EdBPrepareCarefully\1.1\Assemblies\README.md exit /b 0 ) else ( echo Cannot build mod distribution directory. Release build not found in bin\Release directory.