From bf51c5bddf56ddb207a472ae91eea03fe0ef2a84 Mon Sep 17 00:00:00 2001 From: eedibee Date: Sat, 16 Jul 2016 16:34:37 -0700 Subject: [PATCH 01/29] Initial commit for Alpha 14 --- EdBPrepareCarefully.csproj | 144 + EdBPrepareCarefully.sln | 200 ++ Properties/AssemblyInfo.cs | 27 + README.md | 15 +- Resources/About/About.xml | 13 + Resources/CHANGELOG.txt | 376 +++ .../CarefullyPawnRelations_FamilyByBlood.xml | 85 + .../CarefullyPawnRelations_FamilyByChoice.xml | 69 + .../CarefullyPawnRelations_Misc.xml | 9 + .../Defs/MapGeneratorDefs/MapGenerators.xml | 61 + .../Defs/ThingDefs/EdBPrepareCarefully.xml | 13 + .../English/Keyed/EdBPrepareCarefully.xml | 132 + .../Textures/EdB/PrepareCarefully/Alert.png | Bin 0 -> 1477 bytes .../EdB/PrepareCarefully/AlertSmall.png | Bin 0 -> 1342 bytes .../EdB/PrepareCarefully/ButtonAdd.png | Bin 0 -> 961 bytes .../EdB/PrepareCarefully/ButtonClear.png | Bin 0 -> 1213 bytes .../EdB/PrepareCarefully/ButtonDelete.png | Bin 0 -> 1290 bytes .../EdB/PrepareCarefully/ButtonDeleteTab.png | Bin 0 -> 1034 bytes .../ButtonDeleteTabHighlight.png | Bin 0 -> 1156 bytes .../EdB/PrepareCarefully/ButtonNext.png | Bin 0 -> 980 bytes .../EdB/PrepareCarefully/ButtonPrevious.png | Bin 0 -> 977 bytes .../EdB/PrepareCarefully/ButtonRandom.png | Bin 0 -> 1064 bytes .../PrepareCarefully/ButtonRandomLarge.png | Bin 0 -> 1127 bytes .../EdB/PrepareCarefully/ButtonReset.png | Bin 0 -> 1269 bytes .../PrepareCarefully/CharMakerPortraitBG.png | Bin 0 -> 24823 bytes .../PrepareCarefully/DerivedRelationship.png | Bin 0 -> 982 bytes .../EdB/PrepareCarefully/FieldAtlas.png | Bin 0 -> 994 bytes .../EdB/PrepareCarefully/NoPassion.png | Bin 0 -> 989 bytes .../EdB/PrepareCarefully/SortAscending.png | Bin 0 -> 2817 bytes .../EdB/PrepareCarefully/SortDescending.png | Bin 0 -> 2816 bytes Source/AgeInjuryUtility.cs | 150 + Source/BackstoryModel.cs | 50 + Source/CarefullyPawnRelationDef.cs | 34 + Source/ColonistFiles.cs | 72 + Source/ColonistLoader.cs | 42 + Source/ColonistSaver.cs | 36 + Source/ColorValidator.cs | 58 + Source/Configuration.cs | 19 + Source/CostCalculator.cs | 272 ++ Source/CustomBodyPart.cs | 46 + Source/CustomMapInitData.cs | 234 ++ Source/CustomPawn.cs | 1218 ++++++++ Source/CustomPawnTest.cs | 75 + Source/CustomRelationship.cs | 63 + Source/Dialog_Colonist.cs | 121 + Source/Dialog_LoadColonist.cs | 27 + Source/Dialog_LoadPreset.cs | 38 + Source/Dialog_Options.cs | 235 ++ Source/Dialog_Preset.cs | 127 + Source/Dialog_SaveColonist.cs | 69 + Source/Dialog_SavePreset.cs | 70 + Source/DragSlider.cs | 183 ++ Source/EquipmentDatabase.cs | 408 +++ Source/EquipmentDatabaseEntry.cs | 96 + Source/EquipmentKey.cs | 50 + Source/Genstep_Colonists.cs | 295 ++ Source/Genstep_ScenParts.cs | 283 ++ Source/Genstep_SpawnStartingResources.cs | 144 + Source/GraphicsCache.cs | 130 + Source/HealthManager.cs | 84 + Source/IEquipment.cs | 25 + Source/Implant.cs | 164 ++ Source/ImplantManager.cs | 130 + Source/Injury.cs | 185 ++ Source/InjuryManager.cs | 128 + Source/InjuryOption.cs | 66 + Source/LoadableEquipment.cs | 37 + Source/Logger.cs | 47 + Source/ModController.cs | 148 + Source/ModInitializer.cs | 31 + Source/ModNotInstalledException.cs | 12 + Source/Page_ConfigureStartingPawns.cs | 123 + .../Page_ConfigureStartingPawnsCarefully.cs | 2509 +++++++++++++++++ Source/Page_Equipment.cs | 713 +++++ Source/Page_PrepareCarefully.cs | 179 ++ Source/Panel_Health.cs | 502 ++++ Source/Panel_Relations.cs | 294 ++ Source/PawnColorUtils.cs | 103 + Source/PawnLayers.cs | 124 + Source/PawnRelationWorker_Sibling.cs | 203 ++ Source/PrepareCarefully.cs | 464 +++ Source/PresetFiles.cs | 72 + Source/PresetLoader.cs | 47 + Source/PresetSaver.cs | 57 + Source/Randomizer.cs | 95 + Source/RelationshipList.cs | 30 + Source/RelationshipManager.cs | 302 ++ Source/ScrollView.cs | 98 + Source/SelectedEquipment.cs | 102 + Source/StandardEquipment.cs | 166 ++ Source/State.cs | 27 + Source/TabRecordExtensions.cs | 78 + Source/Textures.cs | 81 + Source/Version3/ColonistLoaderVersion3.cs | 55 + Source/Version3/PresetLoaderVersion3.cs | 474 ++++ Source/Version3/SaveRecordImplantV3.cs | 31 + Source/Version3/SaveRecordInjuryV3.cs | 42 + Source/Version3/SaveRecordPawnV3.cs | 278 ++ Source/Version3/SaveRecordRelationshipV3.cs | 36 + 99 files changed, 14129 insertions(+), 2 deletions(-) create mode 100644 EdBPrepareCarefully.csproj create mode 100644 EdBPrepareCarefully.sln create mode 100644 Properties/AssemblyInfo.cs create mode 100644 Resources/About/About.xml create mode 100644 Resources/CHANGELOG.txt create mode 100644 Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByBlood.xml create mode 100644 Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByChoice.xml create mode 100644 Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_Misc.xml create mode 100644 Resources/Defs/MapGeneratorDefs/MapGenerators.xml create mode 100644 Resources/Defs/ThingDefs/EdBPrepareCarefully.xml create mode 100644 Resources/Languages/English/Keyed/EdBPrepareCarefully.xml create mode 100644 Resources/Textures/EdB/PrepareCarefully/Alert.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/AlertSmall.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonAdd.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonClear.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonDelete.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonDeleteTab.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonDeleteTabHighlight.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonNext.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonPrevious.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonRandom.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonRandomLarge.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/ButtonReset.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/CharMakerPortraitBG.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/DerivedRelationship.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/FieldAtlas.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/NoPassion.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/SortAscending.png create mode 100644 Resources/Textures/EdB/PrepareCarefully/SortDescending.png create mode 100644 Source/AgeInjuryUtility.cs create mode 100644 Source/BackstoryModel.cs create mode 100644 Source/CarefullyPawnRelationDef.cs create mode 100644 Source/ColonistFiles.cs create mode 100644 Source/ColonistLoader.cs create mode 100644 Source/ColonistSaver.cs create mode 100644 Source/ColorValidator.cs create mode 100644 Source/Configuration.cs create mode 100644 Source/CostCalculator.cs create mode 100644 Source/CustomBodyPart.cs create mode 100644 Source/CustomMapInitData.cs create mode 100644 Source/CustomPawn.cs create mode 100644 Source/CustomPawnTest.cs create mode 100644 Source/CustomRelationship.cs create mode 100644 Source/Dialog_Colonist.cs create mode 100644 Source/Dialog_LoadColonist.cs create mode 100644 Source/Dialog_LoadPreset.cs create mode 100644 Source/Dialog_Options.cs create mode 100644 Source/Dialog_Preset.cs create mode 100644 Source/Dialog_SaveColonist.cs create mode 100644 Source/Dialog_SavePreset.cs create mode 100644 Source/DragSlider.cs create mode 100644 Source/EquipmentDatabase.cs create mode 100644 Source/EquipmentDatabaseEntry.cs create mode 100644 Source/EquipmentKey.cs create mode 100644 Source/Genstep_Colonists.cs create mode 100644 Source/Genstep_ScenParts.cs create mode 100644 Source/Genstep_SpawnStartingResources.cs create mode 100644 Source/GraphicsCache.cs create mode 100644 Source/HealthManager.cs create mode 100644 Source/IEquipment.cs create mode 100644 Source/Implant.cs create mode 100644 Source/ImplantManager.cs create mode 100644 Source/Injury.cs create mode 100644 Source/InjuryManager.cs create mode 100644 Source/InjuryOption.cs create mode 100644 Source/LoadableEquipment.cs create mode 100644 Source/Logger.cs create mode 100644 Source/ModController.cs create mode 100644 Source/ModInitializer.cs create mode 100644 Source/ModNotInstalledException.cs create mode 100644 Source/Page_ConfigureStartingPawns.cs create mode 100644 Source/Page_ConfigureStartingPawnsCarefully.cs create mode 100644 Source/Page_Equipment.cs create mode 100644 Source/Page_PrepareCarefully.cs create mode 100644 Source/Panel_Health.cs create mode 100644 Source/Panel_Relations.cs create mode 100644 Source/PawnColorUtils.cs create mode 100644 Source/PawnLayers.cs create mode 100644 Source/PawnRelationWorker_Sibling.cs create mode 100644 Source/PrepareCarefully.cs create mode 100644 Source/PresetFiles.cs create mode 100644 Source/PresetLoader.cs create mode 100644 Source/PresetSaver.cs create mode 100644 Source/Randomizer.cs create mode 100644 Source/RelationshipList.cs create mode 100644 Source/RelationshipManager.cs create mode 100644 Source/ScrollView.cs create mode 100644 Source/SelectedEquipment.cs create mode 100644 Source/StandardEquipment.cs create mode 100644 Source/State.cs create mode 100644 Source/TabRecordExtensions.cs create mode 100644 Source/Textures.cs create mode 100644 Source/Version3/ColonistLoaderVersion3.cs create mode 100644 Source/Version3/PresetLoaderVersion3.cs create mode 100644 Source/Version3/SaveRecordImplantV3.cs create mode 100644 Source/Version3/SaveRecordInjuryV3.cs create mode 100644 Source/Version3/SaveRecordPawnV3.cs create mode 100644 Source/Version3/SaveRecordRelationshipV3.cs diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj new file mode 100644 index 0000000..243891b --- /dev/null +++ b/EdBPrepareCarefully.csproj @@ -0,0 +1,144 @@ + + + + Debug + AnyCPU + {86C9833E-894B-4633-ACFA-EFE4157BBBE6} + Library + EdB.PrepareCarefully + EdBPrepareCarefully + 0.14.1.1 + False + v3.5 + ScenPart_PlayerPawnsArriveMethodCarefully.manifest + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + + + + + + full + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln new file mode 100644 index 0000000..185eeec --- /dev/null +++ b/EdBPrepareCarefully.sln @@ -0,0 +1,200 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdBPrepareCarefully", "EdBPrepareCarefully.csproj", "{86C9833E-894B-4633-ACFA-EFE4157BBBE6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {86C9833E-894B-4633-ACFA-EFE4157BBBE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86C9833E-894B-4633-ACFA-EFE4157BBBE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86C9833E-894B-4633-ACFA-EFE4157BBBE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86C9833E-894B-4633-ACFA-EFE4157BBBE6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.DotNetNamingPolicy = $1 + $1.DirectoryNamespaceAssociation = None + $1.ResourceNamePolicy = FileFormatDefault + $0.TextStylePolicy = $2 + $2.FileWidth = 120 + $2.TabsToSpaces = False + $2.EolMarker = Unix + $2.inheritsSet = null + $2.inheritsScope = text/plain + $2.scope = application/xml + $0.CSharpFormattingPolicy = $3 + $3.IndentSwitchBody = True + $3.ElseNewLinePlacement = NewLine + $3.CatchNewLinePlacement = NewLine + $3.FinallyNewLinePlacement = NewLine + $3.WhileNewLinePlacement = NewLine + $3.BeforeMethodDeclarationParentheses = False + $3.BeforeMethodCallParentheses = False + $3.BeforeConstructorDeclarationParentheses = False + $3.NewLineBeforeConstructorInitializerColon = SameLine + $3.NewLineAfterConstructorInitializerColon = SameLine + $3.BeforeDelegateDeclarationParentheses = False + $3.NewParentheses = False + $3.SpacesBeforeBrackets = False + $3.inheritsSet = Mono + $3.inheritsScope = text/x-csharp + $3.scope = text/x-csharp + $3.NewLineForElse = True + $3.NewLineForCatch = True + $3.NewLineForFinally = True + $3.SpacingAfterMethodDeclarationName = False + $3.SpaceAfterMethodCallName = False + $3.SpaceBeforeOpenSquareBracket = False + $0.TextStylePolicy = $4 + $4.FileWidth = 120 + $4.TabsToSpaces = False + $4.inheritsSet = VisualStudio + $4.inheritsScope = text/plain + $4.scope = text/plain + $0.TextStylePolicy = $5 + $5.inheritsSet = null + $5.scope = application/xml + $0.XmlFormattingPolicy = $6 + $6.inheritsSet = Mono + $6.inheritsScope = application/xml + $6.scope = application/xml + $0.StandardHeader = $7 + $7.Text = + $7.IncludeInNewFiles = True + $0.NameConventionPolicy = $8 + $8.Rules = $9 + $9.NamingRule = $10 + $10.Name = Type Parameters + $10.AffectedEntity = TypeParameter + $10.VisibilityMask = VisibilityMask + $10.NamingStyle = PascalCase + $10.IncludeInstanceMembers = True + $10.IncludeStaticEntities = True + $10.RequiredPrefixes = $32 + $32.String = T + $10.RequiredSuffixes = $33 + $33.String = Exception + $9.NamingRule = $11 + $11.Name = Types + $11.AffectedEntity = Class, Struct, Enum, Delegate + $11.VisibilityMask = Public + $11.NamingStyle = PascalCase + $11.IncludeInstanceMembers = True + $11.IncludeStaticEntities = True + $9.NamingRule = $12 + $12.Name = Interfaces + $12.RequiredPrefixes = $13 + $13.String = I + $12.AffectedEntity = Interface + $12.VisibilityMask = Public + $12.NamingStyle = PascalCase + $12.IncludeInstanceMembers = True + $12.IncludeStaticEntities = True + $9.NamingRule = $14 + $14.Name = Attributes + $14.RequiredSuffixes = $15 + $15.String = Attribute + $14.AffectedEntity = CustomAttributes + $14.VisibilityMask = Public + $14.NamingStyle = PascalCase + $14.IncludeInstanceMembers = True + $14.IncludeStaticEntities = True + $9.NamingRule = $16 + $16.Name = Event Arguments + $16.RequiredSuffixes = $17 + $17.String = EventArgs + $16.AffectedEntity = CustomEventArgs + $16.VisibilityMask = Public + $16.NamingStyle = PascalCase + $16.IncludeInstanceMembers = True + $16.IncludeStaticEntities = True + $9.NamingRule = $18 + $18.Name = Exceptions + $18.RequiredSuffixes = $19 + $19.String = Exception + $18.AffectedEntity = CustomExceptions + $18.VisibilityMask = VisibilityMask + $18.NamingStyle = PascalCase + $18.IncludeInstanceMembers = True + $18.IncludeStaticEntities = True + $9.NamingRule = $20 + $20.Name = Methods + $20.AffectedEntity = Methods + $20.VisibilityMask = Protected, Public + $20.NamingStyle = PascalCase + $20.IncludeInstanceMembers = True + $20.IncludeStaticEntities = True + $9.NamingRule = $21 + $21.Name = Static Readonly Fields + $21.AffectedEntity = ReadonlyField + $21.VisibilityMask = Protected, Public + $21.NamingStyle = PascalCase + $21.IncludeInstanceMembers = False + $21.IncludeStaticEntities = True + $9.NamingRule = $22 + $22.Name = Fields + $22.AffectedEntity = Field + $22.VisibilityMask = Protected, Public + $22.NamingStyle = PascalCase + $22.IncludeInstanceMembers = True + $22.IncludeStaticEntities = True + $9.NamingRule = $23 + $23.Name = ReadOnly Fields + $23.AffectedEntity = ReadonlyField + $23.VisibilityMask = Protected, Public + $23.NamingStyle = PascalCase + $23.IncludeInstanceMembers = True + $23.IncludeStaticEntities = False + $9.NamingRule = $24 + $24.Name = Constant Fields + $24.AffectedEntity = ConstantField + $24.VisibilityMask = Protected, Public + $24.NamingStyle = PascalCase + $24.IncludeInstanceMembers = True + $24.IncludeStaticEntities = True + $9.NamingRule = $25 + $25.Name = Properties + $25.AffectedEntity = Property + $25.VisibilityMask = Protected, Public + $25.NamingStyle = PascalCase + $25.IncludeInstanceMembers = True + $25.IncludeStaticEntities = True + $9.NamingRule = $26 + $26.Name = Events + $26.AffectedEntity = Event + $26.VisibilityMask = Protected, Public + $26.NamingStyle = PascalCase + $26.IncludeInstanceMembers = True + $26.IncludeStaticEntities = True + $9.NamingRule = $27 + $27.Name = Enum Members + $27.AffectedEntity = EnumMember + $27.VisibilityMask = VisibilityMask + $27.NamingStyle = PascalCase + $27.IncludeInstanceMembers = True + $27.IncludeStaticEntities = True + $9.NamingRule = $28 + $28.Name = Parameters + $28.AffectedEntity = Parameter + $28.VisibilityMask = VisibilityMask + $28.NamingStyle = CamelCase + $28.IncludeInstanceMembers = True + $28.IncludeStaticEntities = True + $9.NamingRule = $29 + $29.Name = Type Parameters + $29.RequiredPrefixes = $30 + $30.String = T + $29.AffectedEntity = TypeParameter + $29.VisibilityMask = VisibilityMask + $29.NamingStyle = PascalCase + $29.IncludeInstanceMembers = True + $29.IncludeStaticEntities = True + $0.VersionControlPolicy = $31 + $31.inheritsSet = Mono + version = 0.14.1.1 + EndGlobalSection +EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e3fffe5 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +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 Alpha 14 mod")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EdB Prepare Carefully Mod")] +[assembly: AssemblyCopyright("Copyright © 2014-2016")] +[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("0.14.1.1")] + +// 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("")] + diff --git a/README.md b/README.md index 27730df..740e917 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ -# PrepareCarefully -EdB Prepare Carefully, a RimWorld mod +# EdB Prepare Carefully + +Note that the solution has dependencies on the following DLLs: +- Assembly-CSharp.dll +- UnityEngine.dll + +The result of the build will be the following DLL: +- EdBPrepareCarefully.dll + +This DLL should be packaged with the contents of the Resources directory, inside the Assemblies directory. + +The build does not automate the creation of the mod distribution directory. + diff --git a/Resources/About/About.xml b/Resources/About/About.xml new file mode 100644 index 0000000..a64f6b4 --- /dev/null +++ b/Resources/About/About.xml @@ -0,0 +1,13 @@ + + + EdB Prepare Carefully + EdB + + 0.14.1234 + Customize your colonists, choose your gear and prepare carefully for your crash landing! + +If you get a starting setup that you like, save it as a preset so that you can start your game the same way later. + +[Version 0.14.1.1] + + \ No newline at end of file diff --git a/Resources/CHANGELOG.txt b/Resources/CHANGELOG.txt new file mode 100644 index 0000000..1c97852 --- /dev/null +++ b/Resources/CHANGELOG.txt @@ -0,0 +1,376 @@ + _____________________________________________________________________________ + + Version 0.14.1.1 (2016-07-16) + _____________________________________________________________________________ + + - Added support for Alpha 14. + - Removed point system. + + _____________________________________________________________________________ + + Version 0.13.1.1 (2016-06-13) + _____________________________________________________________________________ + + - Switched versioning scheme. + - Added support for Alpha 13. + - Added "Relations" tab to the colonist portrait section. + - Redesigned the "Health" tab. You can now add injuries to your colonists. + - Added "Implants" and "Furniture" sections to the "Equipment/Resources" page. + - Re-labeled the alternate points option to "Fixed Points". Fixed points + is no longer the default. + - Starting points value is now saved with presets to avoid inconsistent + point values when loading a preset. + + _____________________________________________________________________________ + + Version 1.10.1 (2015-08-22) + _____________________________________________________________________________ + + - Added support for Alpha 12. + - Added an Animals tab on the Equipment page. + - Added "No Extra Points" option. + - Rebalanced point costs. + + _____________________________________________________________________________ + + Version 1.9.4 (2015-07-04) + _____________________________________________________________________________ + + - Added support for Alpha 11b. + - Added community-provided Ukrainian translation. + - Added community-provided French translation. + + _____________________________________________________________________________ + + Version 1.9.3 (2015-06-13) + _____________________________________________________________________________ + + - Removed unnecessary initialization when loading a game, cleaning up some + warnings that were being logged. + + _____________________________________________________________________________ + + Version 1.9.2 (2015-06-10) + _____________________________________________________________________________ + + - Fixed errors when saving presets. + + _____________________________________________________________________________ + + Version 1.9.1 (2015-06-10) + _____________________________________________________________________________ + + - Added support for Alpha 11. + + _____________________________________________________________________________ + + Version 1.8.3 (2015-04-19) + _____________________________________________________________________________ + + - Changed the way that weapons are categorized so that they don't appear in + the Resources tab under certain conditions. + - More error-checking for the Accessory layer to avoid errors with mods. + - Re-ordered the GUI elements to avoid text field focus problems. + + _____________________________________________________________________________ + + Version 1.8.2 (2015-04-18) + _____________________________________________________________________________ + + - BUGFIX: Added better error-handling when trying to figure out in which + layer apparel should be displayed. Should avoid issues with mods that add + non-standard apparel layers. + - BUGFIX: Fixed the delete confirmation dialogs in the colonist and preset + save/load dialogs. + - BUGFIX: Fixed head type selection. + + _____________________________________________________________________________ + + Version 1.8.1 (2015-04-18) + _____________________________________________________________________________ + + - Added support for Alpha 10. + - Tweaked the way that points are calculated again. + + _____________________________________________________________________________ + + Version 1.7.3 (2015-04-01) + _____________________________________________________________________________ + + - Added back in the button to clear skills and passions by setting everything + to zero. + - BUGFIX: Adjusted the positioning of the passion buttons/indicators to take + into account translated text so that they don't overlap the skill names. + + _____________________________________________________________________________ + + Version 1.7.2 (2015-02-22) + _____________________________________________________________________________ + + - Added a Korean-language translation. Credit to Latta for providing this. + - BUGFIX: Fixed problem with thing definitions that don't define any + categories. + + _____________________________________________________________________________ + + Version 1.7.1 (2015-02-20) + _____________________________________________________________________________ + + - Added support for Alpha 9. + - Rearranged the UI elements on the first page. + - Split the age field into separate biological and chronological fields. + - Added Randomize All button. + - Added sorting buttons on the equipment page to allow you to sort by name + or by cost. + - Changed the "clear skills" button to behave like a "reset skills" button. + It now resets skill values and passions to their original values, instead + of setting everything to zero. Changed the button icon to reflect the + change in behavior. + - Adjusted point balancing to make passions more expensive and synthread + clothing added to your colonists less expensive. + - BUGFIX: The next button on the equipment page is now labeled "Start". + + _____________________________________________________________________________ + + Version 1.6.4 (2015-01-23) + _____________________________________________________________________________ + + - BUGFIX: Removed RGB sliders from head type. + - BUGFIX: Better error-checking around invalid thing/stuff combinations. + This should better handle cases where you load a preset that was created + with a mod that is not currently enabled. + + _____________________________________________________________________________ + + Version 1.6.3 (2015-01-14) + _____________________________________________________________________________ + + - Error-checking fix/compatibility fix for The Metal Age mod. + + _____________________________________________________________________________ + + Version 1.6.2 (2015-01-11) + _____________________________________________________________________________ + + - Made sure that Prepare Carefully resets itself properly when going back to + the CharMaker page and after map generation. + - Tweaked the sensitivity of the resource drag sliders. + + _____________________________________________________________________________ + + Version 1.6.1 (2015-01-04) + _____________________________________________________________________________ + + - Customize a colonist's date of birth by right-clicking on their age. + - Resources are no longer added in fixed increments. Clicking to increase + or decrease quantities will always modify the count by one. Click and + drag the number to change the values more quickly. Shift-click the + buttons or shift-click and drag to increase the rate further. + - Click and drag to change the age value. Shift-click the arrows to change + the age by increments of ten. + - The cost of resources is now computed using fractional values. The total + cost remains a whole number, rounded up. + - The default number of resources is now randomized to match the vanilla + game. + - Added Polish-language translation. Credit to malejpl for providing this. + - Significant reworking behind the scenes to add support for the new + EdB Scenarios mod. + + _____________________________________________________________________________ + + Version 1.5.2 (2014-12-19) + _____________________________________________________________________________ + + - BUGFIX: Fixed a dumb math error that made silver cost zero points and + other things just not add up correctly. + + _____________________________________________________________________________ + + Version 1.5.1 (2014-12-19) + _____________________________________________________________________________ + + - BUGFIX: Added non-cloth materials back to the character customization + options. + - BUGFIX: Apparel without a color generator no longer crashes the character + customization screen. + - Tweaked the way that your color selections are remembered as you browse + through apparel and accompanying material selections. + - Hover over the point total number to see a simple breakdown of the point + costs by category. + - Right-click on an item in the equipment and resources screen to open the + info dialog for it. + - Rebalanced point calculations. + + _____________________________________________________________________________ + + Version 1.4.1 (2014-12-13) + _____________________________________________________________________________ + + - Added alpha 8 compatibility. + - Changed the point value and added a handful of point balancing math to + try to deal with the changes to various market values in the vanilla + game. + - Melee weapons now reflect the materials from which they are built. + - Options for bionics now respect the body part hierarchy, i.e. if you + replace a right arm with a bionic arm, you can't replace the + corresponding right hand. + - Reworked the colonist and preset save/load screens to match the new + look-feel. + - Changed the way the mod checks to see if it's active. You can now rename + the mod folder without affecting this check. + + _____________________________________________________________________________ + + Version 1.3.1 (2014-11-08) + _____________________________________________________________________________ + + - Added a "Body Parts" option above the colonist portrait. Use it to: + - Add bionics to your colonists. + - Disable old injury generation on your colonists. + - Save and load individual colonists. + - You can now click on the skill bar to set your skill level. + - Traits and backstories are now sorted alphabetically when you open the + dialog to view the full list. + - BUGFIX: Initial work priorities are now correctly assigned based on chosen + skill levels + - BUGFIX: Selecting a backstory that has duplicate names (like "Assassin") + now chooses the correct one in the Backstory selection dialog. + + _____________________________________________________________________________ + + Version 1.2.3 (2014-10-19) + _____________________________________________________________________________ + + - Changed the multipliers for resources so that you can add smaller stacks + and therefore have less expensive options. + - BUGFIX: Traits are now correctly loaded from presets. This should fix the + random changes to the total point value when loading a preset. + - BUGFIX: Old injuries are now correctly generated based on age. + + _____________________________________________________________________________ + + Version 1.2.2 (2014-10-08) + _____________________________________________________________________________ + + - BUGFIX: Resized and repositioned the preset buttons to match the other + buttons in the bottom button row. + - BUGFIX: The randomize apparel button should no longer break the mod. + + _____________________________________________________________________________ + + Version 1.2.1 (2014-10-06) + _____________________________________________________________________________ + + - The apparel customization UI now lets you select a material for apparel + that can be crafted using "stuff." For this type of apparel, the color + that you select blends with the default color of the material to determine + the final apparel color. + - Apparel in the equipment screen is now listed separately by material. + - Added a third trait slot. + - Tweaked the maximum number of points and the colonist point costs. + - Right-clicking the passion indicator lowers the passion level instead of + increasing it. + - Added some additional error handling so that the mod gives up when there's + a fatal error instead of continuing to log an error every frame. May not + cover every scenario, but it should be better. + - Skills no longer go below zero (since negative numbers no longer impact a + colonist's "market value"). + - Removed the neurotrainer. It wasn't working correctly, and it doesn't + really make sense to add it when you can just increase your colonists + skill on the character screen--for far fewer points. + - Changed the preset file format so that it is now more forgiving when you + enable and disable mods. You'll see an error message telling you that + some definitions failed to load. To see which definitions, look at the + console. Old preset files should still work, but no promises. I + recommend re-saving them so that they are stored in the new file format. + - BUGFIX: No longer tries to create apparel out of synthread if that apparel + cannot be "made of stuff." + + _____________________________________________________________________________ + + Version 1.1.0 (2014-10-02) + _____________________________________________________________________________ + + - Added Russian-language translation (credit to Kvarfax for putting this + together). + - BUGFIX: Made the "Age" label localized and a little wider to better fit + localized text. + - Added support for Alpha 7. + - Added a "Pants" layer to custom apparel interface. + - Changed the cost calculations to use the new base market value numbers. + Items that do not have these values are not available to add to your + starting equipment. + - Click on a trait or on a backstory to open a dialog with a list of all + options available for selection. + - You can now start with up to ten colonists. + + _____________________________________________________________________________ + + Version 1.0.6 (2014-09-27) + _____________________________________________________________________________ + + - BUGFIX: The Incapable Of section now correctly resets to "None" when you + change backstories. + + _____________________________________________________________________________ + + Version 1.0.5 (2014-09-27) + _____________________________________________________________________________ + + - Added all backstories. A backstory will now show a warning if its + description may not match the name or gender of the colonist that you + selected. + - Added a checkbox option to the lower-left corner of the window that allows + you disable the point system. This replaces the option you used to see + when right-clicking the points label. + - The Load/Save Preset dialogs do a better job of remembering where you + were when you close them. + - BUGFIX: Skill levels are now correctly saved to presets and are correctly + reflected when the game starts. + - BUGFIX: Head type color and age are now correctly applied to colonists when + the game begins. + - BUGFIX: Skill point tooltips now immediately reflect changes. + - BUGFIX: Some fixes to the way that body type is selected as you change + your backstory and gender. The body type applied to your colonists when + the game begins should now match the one shown in the mod UI. + + _____________________________________________________________________________ + + Version 1.0.4 (2014-09-22) + _____________________________________________________________________________ + + - You can now disable the point system. Right-click the points label and + select the option to toggle between using points and not using points. + - BUGFIX: Names in backstory tooltips are updated when a colonist's name or + gender is changed. + - BUGFIX: Changing backstories now correctly updates the "Incapable Of" + section. + + _____________________________________________________________________________ + + Version 1.0.3 (2014-09-21) + _____________________________________________________________________________ + + - BUGFIX: You can now delete colonists beyond the third one. + + _____________________________________________________________________________ + + Version 1.0.2 (2014-09-21) + _____________________________________________________________________________ + + - Changed the mod controller so that it does not replace the RootMap if + the mod is disabled. + - BUGFIX: Passions and traits are now set correctly on colonists. + + _____________________________________________________________________________ + + Version 1.0.1 (2014-09-21) + _____________________________________________________________________________ + + - Initial release + - Added a "Prepare Carefully" button to the Colonist selection screen. + Clicking it takes you into the Colonist Customization and Equipment + selection screens. + _____________________________________________________________________________ + + \ No newline at end of file diff --git a/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByBlood.xml b/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByBlood.xml new file mode 100644 index 0000000..cf842c5 --- /dev/null +++ b/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByBlood.xml @@ -0,0 +1,85 @@ + + + + + Parent + Child + + + + Child + Parent + + + + Sibling + Sibling + EdB.PrepareCarefully.PawnRelationWorker_Sibling + + + + HalfSibling + HalfSibling + + + + Grandparent + Grandchild + + + + Grandparent + Grandchild + + + + NephewOrNiece + UncleOrAunt + + + + UncleOrAunt + NephewOrNiece + + + + Cousin + Cousin + + + + GreatGrandparent + GreatGrandchild + + + + GreatGrandchild + GreatGrandparent + + + + GranduncleOrGrandaunt + GrandnephewOrGrandniece + + + + GrandnephewOrGrandniece + GranduncleOrGrandaunt + + + + CousinOnceRemoved + CousinOnceRemoved + + + + SecondCousin + SecondCousin + + + + Kin + Kin + + + \ No newline at end of file diff --git a/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByChoice.xml b/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByChoice.xml new file mode 100644 index 0000000..49425a8 --- /dev/null +++ b/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_FamilyByChoice.xml @@ -0,0 +1,69 @@ + + + + + Spouse + Spouse + +
  • Lover
  • +
  • ExSpouse
  • +
  • ExLover
  • +
    +
    + + + Fiance + Fiance + + + + Lover + Lover + +
  • Spouse
  • +
  • ExSpouse
  • +
  • ExLover
  • +
    +
    + + + Stepparent + Stepchild + + + + Stepchild + Stepparent + + + + + + ParentInLaw + ChildInLaw + + + + ChildInLaw + ParentInLaw + + + + + + ExSpouse + ExSpouse + +
  • ExLover
  • +
    +
    + + + ExLover + ExLover + +
  • ExSpouse
  • +
    +
    + +
    \ No newline at end of file diff --git a/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_Misc.xml b/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_Misc.xml new file mode 100644 index 0000000..019b410 --- /dev/null +++ b/Resources/Defs/CarefullyPawnRelationDefs/CarefullyPawnRelations_Misc.xml @@ -0,0 +1,9 @@ + + + + + Bond + true + + + \ No newline at end of file diff --git a/Resources/Defs/MapGeneratorDefs/MapGenerators.xml b/Resources/Defs/MapGeneratorDefs/MapGenerators.xml new file mode 100644 index 0000000..6b413fc --- /dev/null +++ b/Resources/Defs/MapGeneratorDefs/MapGenerators.xml @@ -0,0 +1,61 @@ + + + + + MainMapGenerator + + + +
  • + + +
  • +
  • +
  • + + +
  • + 24 +
  • + + +
  • + 0.120.25 +
  • + + +
  • + SteamGeyser + 25 + 4 + + 0.7 + 1 + + 30 + Heavy + +
  • + 4 + Heavy +
  • +
  • + 4 +
  • + + + +
  • + +
  • + +
  • + +
  • + +
  • + + + + + \ No newline at end of file diff --git a/Resources/Defs/ThingDefs/EdBPrepareCarefully.xml b/Resources/Defs/ThingDefs/EdBPrepareCarefully.xml new file mode 100644 index 0000000..be8a812 --- /dev/null +++ b/Resources/Defs/ThingDefs/EdBPrepareCarefully.xml @@ -0,0 +1,13 @@ + + + + EdBPrepareCarefully + Building + true + + EdB.PrepareCarefully.ModInitializer + +
  • EdB.PrepareCarefully.ModInitializer
  • + + + \ No newline at end of file diff --git a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml new file mode 100644 index 0000000..1ec0145 --- /dev/null +++ b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml @@ -0,0 +1,132 @@ + + + Prepare Carefully + Add Resources and Equipment + Appearance + Body Type + Head Type + Pants + Hair + Bottom Clothing Layer + Middle Clothing Layer + Top Clothing Layer + Hat + Injuries and Body Parts + Are you sure that you want to remove this colonist? + Your colonist cannot wear the selected {0} with the following apparel: + - {0} + Load Preset + Save Preset + Delete this Preset + Are you sure that you want to delete this preset? + Loaded preset {0} + Could not fully load preset. Check that all required mods are enabled. + Are you sure that you do not want to Prepare Carefully? Any changes that you made will be lost. + Heavyset + Thin + Hulk + Average + None + Resources + Food + Weapons + Apparel + Add + Remove + Points Remaining: {0} + Points Spent: {0} + Unlimited + Use Points + Enable points if you want to maintain a balanced start by paying for skills, additional colonists and equipment from a fixed number of points. Or disable points entirely to set up your starting colonists however you'd like! + Cost + You've spent too many points. + Failed to load colonists from the preset. Check that all required mods are enabled. + Failed to load colonists from the preset. Was the preset created with an earlier version of the game? + Disable points + Enable points + The description for this backstory may not match the name and/or gender that you've selected for this colonist. + Age: + New Colonist + Load Colonist + Save Colonist + Load + Save + Delete this Colonist + Are you sure that you want to delete this colonist? + Loaded colonist {0} + Failed to load colonist. Check that all required mods are enabled. + Cannot start with more than {0} colonists. + Body Parts + Normal + Generate Old Injuries + Could not fully load colonist. Check that all required mods are enabled. + Total Spent: {0} + Equipment: {0} + Apparel: {0} + Implants: {0} + {0}: {1} + + Customize Date of Birth... + Choose a Date of Birth + Month: + Date: + You are not allowed to have more than {0} colonists. + You are not allowed to have more than 1 colonist. + You must have at least {0} colonists. + You must have at least 1 colonist. + Randomly generate a new colonist + Randomly generate a new name + Randomly choose new traits + Randomly choose a new backstory + Randomly generate a new appearance + Reset skills and passions to their original values + Name + + + Animals + + + Use Points + Enable points if you want to maintain a balanced start by paying for skills, additional colonists and equipment from a pool of available points. Disable points to prepare your starting colonists with no limitations. + Fixed Points + Enable this option to use a fixed number of {0} points instead of using a number of points that matches your random starting colonists. + Add Relation... + Select a Colonist + Select a Relationship + You must select a relationship + You must select a colonist + Random Relationships + You must remove all relationships from the colonist to enable random relationship generation. + Next + Add + Cancel + Close + This relationship cannot be deleted because it was automatically added due to another relationship. Delete the original relationship to remove this one as well. + Random Injuries + You must remove all injuries from the colonist to enable random injury generation. Injuries will be determined based on the colonist's age. + Add Implant... + Select an Implant Procedure + You must select an implant procedure + Select a location + You must select a location + Select a severity level + You must select a severity level + Add Injury... + Select an Injury + You must select an injury + {0} ({1}) + Implants + Furniture + 2 Damage + 3 Damage + 4 Damage + 5 Damage + 6 Damage + {0} {1} + {0}, {1} + {0} + Pre-alpha 13 versions of presets are not supported. + Pre-alpha 13 versions of saved colonists are not supported. + Implants: {0} + + \ No newline at end of file diff --git a/Resources/Textures/EdB/PrepareCarefully/Alert.png b/Resources/Textures/EdB/PrepareCarefully/Alert.png new file mode 100644 index 0000000000000000000000000000000000000000..4d4d64ed498e1cfe8b19b0e621fef37ff760d9fc GIT binary patch literal 1477 zcmeAS@N?(olHy`uVBq!ia0vp^DnP8j!3HF&Cf@1;Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?_nZLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ2+zz*$uBR~ z1grP;werj>E=kNwPW5!LRRWrzmzkMjH{644~kf%h=vIPQxAvSx~$|>QPI(9;QPvZ!DVrG;vx16^&my4jRgKA*;v`*Qy6=rwgGpWeBr_T)?XuUf`MnNmGVG`I@1a<7OAx0m}vUI{%a6ur)^GC6!&^+Z7@t4N*oi+(V+I&r#ujJ22N znRFrVeq!vB>5)sW^mbin%v<~A=nhG(UCYzk_>>h}O1dJXHqPMs`G-&7Q*NzWE?>x3 zv0r~>_Ub4teYRfkP+G=u^^*A~=lGf~wilXrvAV6VCnF*3!o;_aqNXpA^0>5kW#>aF z&q;DSk{c>h%4P?r_s-hbmlILFSG7-q!_27r?B65O%swr5a^AUi=(FA6w7hl4-^TEP z_lfh*;#!)HH}ETp&Hin-QI7ff8AF8+H*8j@tUM*dyiU1z^G_4T_Y-@Mh%w(&=1%z> zd9*=UQGfPlp`>oca;F%x`K~-ZK(DUev2yp#JKdASWw%Le5H!lEv)S@fbNboC#~L2w zeOCV#Zzv@#J555OpXdK%!<#m5PN*4f=rJtF?Ph#^Vn912s|LZ pvT?R)%vAfFB}P8EJAW!4U=ZOBirLlk+7nc;db;|#taD0e0st}G7tH_w literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/AlertSmall.png b/Resources/Textures/EdB/PrepareCarefully/AlertSmall.png new file mode 100644 index 0000000000000000000000000000000000000000..97d59bfbeb34feacdb1a344d8dfc12a6193923b2 GIT binary patch literal 1342 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2v2cW~I!Kh>{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ{v)=f4rG*mD%(=#+N zH8V5RQ7|$vG}1Q!A~Rh>6Dw0QDv55FG|-pw6wGYnPFt43sj+7T$xvrSfQI&tPC^3CAB!YD6^m>Ge1uOWMX1cerbuV z640d(FXR@$jm;~D1`{yA^eYkz^bPe4Kwg3=^!3HBG&dKny0|1L72#g21{a4^7NqJ2 zr55Lx79|5CE=?I^Re_arQEFmIeo;t%ehw@Y12XbU@{2R_3lyA#%@j1kGxJjN%ZoL^ z>V18!JoAc667!N%JzZ>-fF|f=W~NxV7&|$d8kjh{ni&}z8oHV|ni;!Vm^-<+SXx-P zSQuKs^t$9Hm*%GCmB93-AoRN6)C)=qxdlL*T~doO%TiO^it=+6z+Se>#O)SGoaRCG zrr>sq6HdMQK*#8VA{Hs4VM4&v17gCHEsz6G_^Elo)LsNk)*Q3CgZF_ROY?!pu_{ukxle8m^1q-{&rw%qzhBvR-#bNJduHBB>7UKfjN^)mPcFK{DmR z?()LvGiU6}TXNv^nj9S+eQ8w{HDeDCpQMDu%BvhD%+fd6{{22aPgTjt{r~s(*Lf6p zlK%faJ<)SosGLIODw3@d;Bxw-h|_xzJ{rUPJUpHsX;@6$n*_+7^SRYMVGLj?f!r8$Em5mv%bGu`{&bpd2Sz$p8tQ7f2?1{ zbx^@5 zQ~7bxIkppz9QiZMGHUMc?Kb7E0cxCW{D~`~fq|JJ;>w3fyG8G;1C?l=u6{1-oD!M< DT5YxskUARDh?3HaH*svKnNs>gXxSU z!Mi{dpvVjKm69+mXCzt7g4x5;Xo2088(Mi5i>?&5Plzw`{Ae`dMkx*lJs#$AxmZIK zlN6B*#~v}Gq!%XU3K|Nn!10NLJrFCJ9XuooOEWz=g6l72y>M10S};B`eI9Z`e55&0 z*Z&W7-32-%4Ro6CKZQeM>?6K`LOcvC+PH2a4&}>gfJ}k|1LNI!73+OWaM;H_P@59i zY&o8dN8z0*Uf1P{7ZTI6P(@Q%s=zspEtd;hu&Bb6SllW>Sdxk&)XL>-L4`soElBE| zt6^*CB9F|u_9-{Llp9;Y^=V`c15*k;c`k6N-i&K6&qZ9y0)7g{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ{v)=f4rG*mD%(=#+N zH8V5RQ7|$vG}1Q!A~Rh>6Dw0QDv55FG|-pw6wGYnPFt43sj+7T$xvrSfQI&tPC^3CAB!YD6^m>Ge1uOWMX1cerbuV z640d(FXR@$jm;~D1`{yA^eYkz^bPe4Kwg3=^!3HBG&dKny0|1L72#g21{a4^7NqJ2 zr55Lx79|5CE=?I^Re_arQEFmIeo;t%ehw@Y12XbU@{2R_3lyA#%@j1kGxJjN%ZoL^ z>V18!JoAc667!N%JzZ>-fF|f=W~Nw~8XG&BnVPzo7#bTJ8oHWTni!fnIhmRo8yOlH zTDY0P^t$9Hm*%GCmB93-AoRN8)C)=qxdlL*T~doO%TiO^it=+6z+Se>#O)R*oaRCG zrr>sqGfutwK*#8VA{Hs4VM4&v17gCHEsz6G_^Elo)LsNk);qp4Z)RX%H1u?F45_%a zW~yP{Ap?olT=_;zVfGbAvNVhJ1FlSDEMk3kK=cw{tanx8Ingyc8M)^$f0nWJ?svLu zbymN&EA_YS&H8_THY&0dt8;ze@G@WyJ}2+Mz3+g!#a)fX3lkh!%N~BeQMf*o-*k3E zFGt@4QJn+Kkp=HGew$s1lZ-O7ZkGCyzdYRL`<$a!Rwb&Yy_Q?t_jcRG)#{hEB*i9e zwmRPPII1Y>h_Cm_ndc3eU;ffu?tT1{fll+@pNg}dDW7@npD80h?Xm4vL$Bjj#%tE< zY?hege{1ICMY-&=R+hc;@&9x2jh2kPx^lRD(zGq?>c@5e7j9aTJ6FZTjM@Icnc&-d zWp0ZuHhdpb!5HdyPQcW3W6eC-=nK-~;WN`-FIjke<#x*t-V#@2-16fWn?_iF(|@3J ZfT1bmu>GUYu2Vq;il?ie%Q~loCIDz$o^1dC literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/ButtonDelete.png b/Resources/Textures/EdB/PrepareCarefully/ButtonDelete.png new file mode 100644 index 0000000000000000000000000000000000000000..47e2a479d0dde26a2511584b335cd236afb13340 GIT binary patch literal 1290 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%u1Od5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y2=9ZF3nBND}m`vLFhHYsTY(KatnYqyQCInmZhe+73JqDfW2&$iPJ5PZaB?@ z>P^Az76Y7m^?{Dj2SqGWM8kxDsRzV_CtDx~p72xifT_I*n5+eVY1|J%h^$&tyoDdFhF3>u~Xbl5s@4R!#8|n)S2{LKsTqB^`cw!)(H(|jE+ZEup}i; z&~WOK(0{q~>-I@b>l8*KwJPi=DQaxLJ?T6XhQjJ5)ITgtrKEG26i{R7M%KC2N|SlKF+ z(8##k#`;solozKzRtuMSEEKU=5NsFRbys8lR&#^&_$PeN53csOl+^x8M>}b|;~BYQ z2iu-YP82v7a>rDnZ&3lK!Go8oH^mLtRT`{%6y*?qW%83ZesAn|lyL@TmTG@tT6HRr z?e4b3t3NsNRnMAbUgfH741Rbcv1#|q&RrixEX3{~oqzi?yWfV{U;WSh4`Mj%l^%0d zCUa_n_aC{Sv%2rXZ`^Ivd%Nl9{oDxExC{an^LB{Ts5 D55mW* literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/ButtonDeleteTab.png b/Resources/Textures/EdB/PrepareCarefully/ButtonDeleteTab.png new file mode 100644 index 0000000000000000000000000000000000000000..8ce2552136fefc9b8268ecbc73c57f1b9eeba4bf GIT binary patch literal 1034 zcmaJ=PfXKL94>#Ngt$aw)M!k6DKWu6YuByoY^5Ts8!Vw@ju{rhfv$b92HIEJ2P;Gk za4>Q+9=t<5;6?G`#RNm5iF)v8i1EVJi;D3gB=|bE;lbIoeSh+O-|zQ+zwa$)2m3

    fx8N#5iZI`$SZ_7UWyA+Gy$p)O`^HRxRg`U)mUUD(>GEXf#X^VQ4}OY$6*3idqrPV3{VF9vs1P>aw<1EfXmi z>uV0nF_GX%HK3~gA8J{3w1;!>FyDU)d-*vBvN`CX8CNHb8}A50Ia1Pv8b)p&p{ZIG zvn7O)S3(X*4hf)h*t88)_AXWMsw!n{4{Np#Gm1-LR%Ymk9KDB>ua|cKddxo!Rq!X*a*E@?JuroUyR~kTgRyFS+GI9*~{K(x_p1- z@rgb@+WdnDpB8oyHGTTs=95->x@q9)nRWBy=E(!$CTi?!XvF<8vK!paOlnYh);qTJ2T)u~bpQYW literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/ButtonDeleteTabHighlight.png b/Resources/Textures/EdB/PrepareCarefully/ButtonDeleteTabHighlight.png new file mode 100644 index 0000000000000000000000000000000000000000..860b848b60b90522e1ed9c0377a1ab04a44baf2b GIT binary patch literal 1156 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDkcV&H6M=4Nba>}+mgXy|HUX=rBdXkh4KWa{YV zWM*jq)9aF-T$-DjR|3v4~Pj*wm=R%;iu*SQ+p9GSxfxaXa{DAAD%9bAr-gQ zOg7|eau9GmIIE>Aii=zK&Dn2{SS?yFy74C}&;7`B&#{@kVcHQVVd=e%^BDe5==bZ8 znp`?bye$3M`Axew=S5EQJFH~D#P;Qdbra8$g!PkSLlszrFQg~AEj;Yh*2tzU&NREb$*B1Bu}O^twOGI z3IY2X_O1NXsW#D(kFEEr*a8MS%@>y~7oB`6mS=I=Y{%={jM*2S{$V**^i1nG`}Jv` zZ|*x9m$tiAO#0Z0S(khaC5ocU`;w1k8BhHte?8-_N7&k&zcXTPKmQ|;z@W0>=Zz4p Rhc`jxf~TvW%Q~loCIF3HiZK8H literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/ButtonNext.png b/Resources/Textures/EdB/PrepareCarefully/ButtonNext.png new file mode 100644 index 0000000000000000000000000000000000000000..537a73d820f7c7986fad646998b4c92647c7f800 GIT binary patch literal 980 zcmaJ=&ui0A9M2{UDGbIyK|y_H9wyi%FKx3WERL+{Y|xC+7FO^u)8uUpTk_(|+nNo9 zxr-@0d5c<~QV!Hb~tAb8pkyy(T7hzF1JrE5D6){x}A_vQ2Xe1EL$v%=Pq^gB7k{uh27I&3*ZOLbbsOSI(>#2FrLAYcW~GO8fQ+qk`lra0~d@7Lu0nP9n!$K@>F5vLEXFk5_Ma;Cnj1P_QLCDx#)e-Xy5HIahNx zS6tQOr>}ykL}LO0q7F!c)iBl)op0-EY@eD%9<(8JMdy!#s@wCRhzSA(A(wR}2!Wyq zFkesE3ltqcXN*3m|f}+V2p!4v|8}XL3iaFcy#ZEfkq%_h*vDIn`t(<_#vItdG zO*LdW%MjUkBcx7}4dbz{f{9|6_!0GS2vS9-f!CCoe8CSr+=?%^@wTm5VN9LSMV6`aOhNE{Pb;a??==BIemd#9v3=EWrV2r>yz?W0~x$*Wr5oV^RBZMHY;h zHukSZcTH@1(rs^S*`YW1C}h)4*jS%?xfif2QMOE@mVAD_V`MlmGPwW!aPgV&^z?Sw zI{5bR@!*F~JS-y{~X-E7he3h|L)+?^{oMJcQu>dDde5xVocUtr*7hIWBQmW3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/ButtonPrevious.png b/Resources/Textures/EdB/PrepareCarefully/ButtonPrevious.png new file mode 100644 index 0000000000000000000000000000000000000000..6665daa2914c2aa230455baf06b4a39013794780 GIT binary patch literal 977 zcmaJ=&2G~`5VjHtwG~AOsX`P4EB68w@lSpnV#O^XPN-2Gg(OmQK)}Y{Bvxv#u{YSs z3DE2i&fnxu}~B!C`b3MOtLpxLm4Y6zNx7vE5xVdAXSXp*MB zDI3`4V1(g9+of!V$*+elGS>{dn>bhn7Q*2d67^&DE8R2zZ)*PQe$3U8@uvCGwEK^qRl?6c(q*OsIiDD^D&2%wG=WDBuqr#pm z^yG+kPqdx3Ej6Z%j~v?V9&PKL_|qNwBubiEXoSB$?}?Wfa547w_~hBz)vw&r6|Fgc zBli7pwf>XYe;0F?e{X(*ui|@0+111QyYr8PYOHnr{hJ?42gh>)Q~R=T^TT8GBzk08 LsiN*bc>ek?fT zpaJ6pCMuHnf)8j65Bh=+U_vmwfEwe4Ctr-$NO%w>#OUc@!v|;6_FVFP-~XTg|IX#s zmSdH+-8Ow{h@cER0U1-pB!~l1&YqnG zeuCIcDTxl$AvALmR2`y)ab#2-vkAiAn9)Tk1rXT@l8P3f79USiq#_5XcCX+R^f2gF znsNqc%e5q=TuSoERO3<7pW(283Xn)<)E>>`G65>D%i+1TO;cnZf>Hr$EvXKnl?+1z zkY0z|E;$*7WLXE}@vDgXuH|4>yeqD>SB8~OfI*i2+~K*xazdkqOUuB*-prE_5e zhzJ@92zv@uZ0&{!n%z(*!)+|NuU*k(m^Sz4@q)m`G!u!M1Y&%E!U_&WkvSJ1jD|d3 z-W>{s7$)Kgd3c6z@CA8y#Lck5aDmH1saFLWDsbfuZlomFvVyAP$UHEVb3l$7P$lz6 z=9JR8gi7kIbLG;xgiCU1oD6L>_OC`4wy=AwY0s#-IEBH#>j`6`n zX5r=a=e8-bB9bgy`F%QdTpKKxz#Lms)pu=TAy{=IyEHz2_|*O3p=U$6xw0{RXFb&x zeL51YovMk{4fL;0d}*(le^cFGzdSfTJ@H|2#kNZA85(Gwzxe3t=)3UDt6jCW*B{$Y z%+lpQ;HR5oKfhHS=uQ5*^m*IfdP literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/ButtonRandomLarge.png b/Resources/Textures/EdB/PrepareCarefully/ButtonRandomLarge.png new file mode 100644 index 0000000000000000000000000000000000000000..159ae7222b231244e76f9017fb4c809a74f3b4b0 GIT binary patch literal 1127 zcmaJ=TWkzb7@k-~B$Y;aC>6()AneY}-fBlzZD+Qun5Nw>-2{p0%;|QtGiN$;YNwST zZVeGUctGMoqM;8Y;u0d1h=?c&60}4JiI=hx4@d}S+HQHUCNpy``M&S}&;NgCe|_Dm z;;FNyQWRC3SS=>WIFEegQznsrj(S#3hB6#a<3`kiv$6%Ln2MSqNa%6~CZVi$b=-$l z6y>3{R2rwHwY-9KpX_3Mj&2e*MO9ThrmVC<44Pp^Gop0vdOr;`HA**yBvvv7*s87W zwqRp-T}tV0Qz9x|y#iD@JQ2_#mVu+U8#eDm=@DI?%-wB<1|tyM7Ntj%N=x-XKo$fc zU%;!d90$T-9~TUT!`xEfXE{H^k~i$-f_x~<`zyiNLz8Hh+QKKr+Ob$<6{TA-HhG50 zX0yI*z=y01!$l$ySHti35`@?8GO+A;4Z9+*AVOQQG!tvc0Is6kj5=|YCYg>NK{v-` z4STFiq+pCAn+)e;-6Q3JlJtM5u8*T_oP>Y!{im><>M|jdgf{B56wIK-55{W5WsyLzGQdT`Ayx?Fxgt_Jb!gx`SN+3{7v#ED&`lCqgqF4ys|6H_V+Po~;1Ffc1+hD4M^`1)8S=jZArg4F0$^BXQ!4Z zB&DWj=GiK}-@RW+Av48RDcsc8z_-9TH6zobswg$M$}c3jDm&RSMakYy!KT6rXh3di zNuokUZcbjYRfVk**jy_h8zii+qySb@l5ML5aa4qFfP!;=QL2Kep0RGSfuW&-nVFuU ziK&^Hp^k!)fuWJU0T7w#8k$&{npqi{D?ot~(6*wKG^-#NH>h1eo~=?wNlAf~zJ7Um zxn8-kUVc%!zM-Y1CCCgTBVC{h-Qvo;lEez#ykcdT2`;I{$wiq3C7Jno3Lp~`lk!VT zY?Xj6g?J&i0B&qvF*KNf0j6J(SfFpHX8`gNOrftYexnUy@&(kzb(T9Bihb5uTZsl3!k| z30CjxYvq|&T#}fVoa*Ufs{}MbFEca6%E`dY)Y!tv*wNY5+0f9{#KP3o#M#ot(!|if z(AChz0;bm`Ke;qFHLnDwHwB^B1gBn5Qpha;+U$~Alv$RV;#QQOs{r=0RVHq?7~(V! zsy79kkPB8Z5swdfq~>V}5TB^j(?gSJ@fnWwv1d zMfsAL+yX7umy>gzzdQctRZm zoa;Sn*~B#m_)3i5NLWAl*5}h<-(=9D!VEi6q_wllKl;FihJ=0WU zelwkaa5&w2(V7bOotwNT8M{SI`H>UZec*A=hMI$cPu8xTRyb#YPtRJ>-wnar)8BX; zR-M;Xn7Q}$-`VwEYagD9>L@IAJR1^q_(O}xg3Sq4+D&#lFPY1_JDKGs#9yu-@T|V=b5P6S1EN%^#2>LeMde=OWivE*n7R@ lwIk&xd`03v^ZgY~VBnV9IO|DT6{t{S@O1TaS?83{1OPcG(*ghh literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/CharMakerPortraitBG.png b/Resources/Textures/EdB/PrepareCarefully/CharMakerPortraitBG.png new file mode 100644 index 0000000000000000000000000000000000000000..c67cd91c98eb611c5d5e20ac120a043aba12858b GIT binary patch literal 24823 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 SCl_i$a?|wEGJH3G|LGsnSTo50 literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/FieldAtlas.png b/Resources/Textures/EdB/PrepareCarefully/FieldAtlas.png new file mode 100644 index 0000000000000000000000000000000000000000..759842bf16a2799f5ac84bbf4b03dd10696eea4b GIT binary patch literal 994 zcmaJ=PiWIn91aY|81AT`r$+{&V3WMI$(pytQJdBUmNDDniYMQa*EMv>%aS*n)tfhA zD0tJW2lb#J9%OphSwXyr!Vo-(7i9=T*hycyw)0>O$@`b@`+mRo`*VAF>GIU%nMpwq zrmO|C%4d*%6XSf0yWImmoo3|*tI;Ow;D`uCm#z|E`FM>~33j(O-jF##7!$pEgEj0$ zn_!uZrjma zasDEhOAr_E3Bw@q*Mk@(hS<|Ze4QT4BIrR_+YpbOYS_!5L?Z%JDVK2+2!WD+f_QqMV3OF0ypVY`0I4EgrJKwUxu-{0@WY{O5ck8x112Xpl%b@gR~i6q z`~OhiAEIMcCCC2$Q#h_~g+#8Bm~KW6Ph4|4H5H;#L@=XKoznF|6qj3+(YQrJP^xL* z+=>^tv=g82;cXjPLCkRA5X&?~t{{1yiy+j?mAtO!v`P-ba=xe*6jRBm1+9?H70c#; zYf@*^CjlF9-D7TXBsVPuKjfB85_va>TZyO-dRIo?XkTVV>h-zqXkQ8=xiWV~P80iA zq6bI3deY^vZ24wb_$1)f9`Ulizp=N&TjHu^7VF90_fOBKc(0jP@HqZ`$R5d*{oy+OKQ(R%YL}A1%Jv|Ei(~W4(?V4hK&^+;LxieDWE-zblLh6Aw=w TK30C7O8;BsCG**(>$m;@M0+=0 literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/NoPassion.png b/Resources/Textures/EdB/PrepareCarefully/NoPassion.png new file mode 100644 index 0000000000000000000000000000000000000000..df42853f313518da3a5774ff2d5afe589aba41a4 GIT binary patch literal 989 zcmaJ=J#W)c6m?rbsHH4OsD(;AIZ#m*e6LP$s~AO>JUEQ|~Y!BD9@r%B2{ux!5%opbJe_nvoedFjgd=&4bP zqQ*;$TA7R!@ptqH`F{!3-(;A?g&JN#YdAD~NaZY4hoIyb>#z(BYisij%u>_{ZC7fz zre9S|LXxN1kCeAqI80Zo4Y|{n-l|*p^CHQ#!AEdDyTQ+df=rFICKT z)67`({3Vc$6e8e2Y=Fqwa04Y$>5i^K=J7U1gAN2YReC?En!XJ3$cG@sN{q=10+3}^ zNTy_2xBx_65ILT_G9x6Fl&pwzp!?7yns2QsWv$qaMOG@^z}QnbE(}99lvw1ib3!JQ zi8Vx#AqXbea!-IVPDI8R`Jjj({fLgvu8n>2+LwQQxhXzJ|1)+^z6_*r)#%<9aZfz$+mQY_VI%=V>`#DKMmvUYf~3rr}5X@A66eV&r0v!|J+U-zpFEqUqd$@&YXL`I{R`2`)fr>OE literal 0 HcmV?d00001 diff --git a/Resources/Textures/EdB/PrepareCarefully/SortAscending.png b/Resources/Textures/EdB/PrepareCarefully/SortAscending.png new file mode 100644 index 0000000000000000000000000000000000000000..820ab3db0cad4813dc8c4bd1f30a3a7b29b936a1 GIT binary patch literal 2817 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 literal 0 HcmV?d00001 diff --git a/Source/AgeInjuryUtility.cs b/Source/AgeInjuryUtility.cs new file mode 100644 index 0000000..ca4c705 --- /dev/null +++ b/Source/AgeInjuryUtility.cs @@ -0,0 +1,150 @@ +using RimWorld; +using RimWorld.Planet; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + internal static class AgeInjuryUtility + { + // + // Static Fields + // + private const int MaxOldInjuryAge = 100; + + // + // Static Methods + // + public static void GenerateRandomOldAgeInjuries(Pawn pawn, bool tryNotToKillPawn) + { + int num = 0; + for (int i = 10; i < pawn.ageTracker.AgeBiologicalYears; i += 10) { + if (Rand.Value < 0.15) { + num++; + } + } + for (int j = 0; j < num; j++) { + DamageDef dam = AgeInjuryUtility.RandomOldInjuryDamageType(); + int num2 = Rand.RangeInclusive(2, 6); + IEnumerable source = from x in pawn.health.hediffSet.GetNotMissingParts(null, null) + where x.depth == BodyPartDepth.Outside && !Mathf.Approximately(x.def.oldInjuryBaseChance, 0) && !pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(x) + select x; + if (source.Any()) { + BodyPartRecord bodyPartRecord = source.RandomElementByWeight((BodyPartRecord x) => x.absoluteFleshCoverage); + HediffDef hediffDefFromDamage = HealthUtility.GetHediffDefFromDamage(dam, pawn, bodyPartRecord); + if (bodyPartRecord.def.oldInjuryBaseChance > 0 && hediffDefFromDamage.CompPropsFor(typeof(HediffComp_GetsOld)) != null) { + Hediff_Injury hediff_Injury = (Hediff_Injury)HediffMaker.MakeHediff(hediffDefFromDamage, pawn, null); + hediff_Injury.Severity = (float)num2; + hediff_Injury.TryGetComp().IsOld = true; + pawn.health.AddHediff(hediff_Injury, bodyPartRecord, null); + } + } + } + for (int k = 1; k < pawn.ageTracker.AgeBiologicalYears; k++) { + foreach (HediffGiver_Birthday current in AgeInjuryUtility.RandomHediffsToGainOnBirthday(pawn, k)) { + current.TryApplyAndSimulateSeverityChange(pawn, (float)k, tryNotToKillPawn); + } + } + } + + public static void LogOldInjuryCalculations() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("=======Theoretical injuries========="); + for (int i = 0; i < 10; i++) { + stringBuilder.AppendLine("#" + i + ":"); + List list = new List(); + for (int j = 0; j < 100; j++) { + foreach (HediffGiver_Birthday current in AgeInjuryUtility.RandomHediffsToGainOnBirthday(ThingDefOf.Human, j)) { + if (!list.Contains(current.hediff)) { + list.Add(current.hediff); + stringBuilder.AppendLine(string.Concat(new object[] { + " age ", + j, + " - ", + current.hediff + })); + } + } + } + } + Log.Message(stringBuilder.ToString()); + stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("=======Actual injuries========="); + for (int k = 0; k < 200; k++) { + // TODO: Look this up to see how it's changed. + //Pawn pawn = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfPlayer); + Pawn pawn = new Randomizer().GenerateColonist(); + + if (pawn.ageTracker.AgeBiologicalYears >= 40) { + stringBuilder.AppendLine(pawn.Name + " age " + pawn.ageTracker.AgeBiologicalYears); + foreach (Hediff current2 in pawn.health.hediffSet.hediffs) { + stringBuilder.AppendLine(" - " + current2); + } + } + Find.WorldPawns.PassToWorld(pawn, PawnDiscardDecideMode.Discard); + } + Log.Message(stringBuilder.ToString()); + } + + public static IEnumerable RandomHediffsToGainOnBirthday(Pawn pawn, int age) + { + return AgeInjuryUtility.RandomHediffsToGainOnBirthday(pawn.def, age); + } + + // EdB: Interpretation of bad decompilation + //[DebuggerHidden] + //private static IEnumerable RandomHediffsToGainOnBirthday(ThingDef raceDef, int age) + //{ + // AgeInjuryUtility.c__Iterator92 c__Iterator = new AgeInjuryUtility.c__Iterator92(); + // c__Iterator.raceDef = raceDef; + // c__Iterator.age = age; + // c__Iterator.<$>raceDef = raceDef; + // c__Iterator.<$>age = age; + // AgeInjuryUtility.c__Iterator92 expr_23 = c__Iterator; + // expr_23.$PC = -2; + // return expr_23; + //} + + private static IEnumerable RandomHediffsToGainOnBirthday(ThingDef raceDef, int age) + { + List result = new List(); + List giverSets = raceDef.race.hediffGiverSets; + if (giverSets != null) { + foreach (var set in giverSets) { + foreach (var giver in set.hediffGivers) { + float ageFractionOfLifeExpectancy = (float)age / raceDef.race.lifeExpectancy; + HediffGiver_Birthday birthdayGiver = (giver as HediffGiver_Birthday); + if (birthdayGiver != null && Rand.Value < birthdayGiver.ageFractionChanceCurve.Evaluate(ageFractionOfLifeExpectancy)) { + result.Add(birthdayGiver); + } + } + } + } + return result; + } + + private static DamageDef RandomOldInjuryDamageType() + { + switch (Rand.RangeInclusive(0, 3)) { + case 0: + return DamageDefOf.Bullet; + case 1: + return DamageDefOf.Scratch; + case 2: + return DamageDefOf.Bite; + case 3: + return DamageDefOf.Stab; + default: + throw new Exception(); + } + } + } +} diff --git a/Source/BackstoryModel.cs b/Source/BackstoryModel.cs new file mode 100644 index 0000000..8f3ef86 --- /dev/null +++ b/Source/BackstoryModel.cs @@ -0,0 +1,50 @@ +using RimWorld; +using System; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class BackstoryModel + { + protected Backstory backstory; + protected bool nameSpecific; + protected bool genderSpecific; + protected PawnBio bio; + + public BackstoryModel() + { + } + + public BackstoryModel(PawnBio bio, BackstorySlot slot) + { + this.bio = bio; + this.backstory = slot == BackstorySlot.Childhood ? bio.childhood : bio.adulthood; + + } + + public Backstory Backstory { + get { + return backstory; + } + } + + public bool IsNameSpecific { + get { + return nameSpecific; + } + } + + public bool IsGenderSpecific { + get { + return bio.gender != GenderPossibility.Either; + } + } + + public Gender Gender { + get { + return bio.gender == GenderPossibility.Male ? Gender.Male : (bio.gender == GenderPossibility.Female ? Gender.Female : Gender.None); + } + } + } +} + diff --git a/Source/CarefullyPawnRelationDef.cs b/Source/CarefullyPawnRelationDef.cs new file mode 100644 index 0000000..cb9a0c9 --- /dev/null +++ b/Source/CarefullyPawnRelationDef.cs @@ -0,0 +1,34 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class CarefullyPawnRelationDef : Def + { + public string inverse = null; + + public bool animal = false; + + public List conflicts = null; + + public Type workerClass = null; + + [Unsaved] + private PawnRelationWorker worker; + + public PawnRelationWorker Worker { + get { + if (this.workerClass != null && this.worker == null) { + PawnRelationDef pawnRelationDef = DefDatabase.GetNamedSilentFail(this.defName); + if (pawnRelationDef != null) { + this.worker = (PawnRelationWorker)Activator.CreateInstance(this.workerClass); + this.worker.def = pawnRelationDef; + } + } + return this.worker; + } + } + } +} diff --git a/Source/ColonistFiles.cs b/Source/ColonistFiles.cs new file mode 100644 index 0000000..193182d --- /dev/null +++ b/Source/ColonistFiles.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + 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" }); + } + catch (Exception e) { + Log.Error("Failed to get colonist save directory"); + throw e; + } + } + } + + public static string FilePathForSavedColonist(string colonistName) + { + return Path.Combine(SavedColonistsFolderPath, colonistName + ".pcc"); + } + + // + // Static Properties + // + public static IEnumerable AllFiles { + get { + DirectoryInfo directoryInfo = new DirectoryInfo(SavedColonistsFolderPath); + if (!directoryInfo.Exists) { + directoryInfo.Create(); + } + return from f in directoryInfo.GetFiles() + where f.Extension == ".pcc" + orderby f.LastWriteTime descending + select f; + } + } + + // + // Static Methods + // + public static bool HaveColonistNamed(string colonistName) + { + foreach (string current in from f in AllFiles + select Path.GetFileNameWithoutExtension(f.Name)) { + if (current == colonistName) { + return true; + } + } + return false; + } + + public static string UnusedDefaultName() + { + string text = string.Empty; + int num = 1; + do { + text = "Colonist" + num.ToString(); + num++; + } + while (HaveColonistNamed(text)); + return text; + } + } +} + diff --git a/Source/ColonistLoader.cs b/Source/ColonistLoader.cs new file mode 100644 index 0000000..66ee68c --- /dev/null +++ b/Source/ColonistLoader.cs @@ -0,0 +1,42 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class ColonistLoader + { + public static bool LoadFromFile(PrepareCarefully loadout, Page_ConfigureStartingPawnsCarefully charMakerPage, string colonistName) + { + string version = ""; + bool result = false; + try { + Scribe.InitLoading(ColonistFiles.FilePathForSavedColonist(colonistName)); + Scribe_Values.LookValue(ref version, "version", "unknown", false); + } + catch (Exception e) { + Log.Error("Failed to load preset file"); + throw e; + } + finally { + Scribe.mode = LoadSaveMode.Inactive; + } + + if ("2".Equals(version)) { + Messages.Message("EdB.PrepareCarefully.SavedColonistVersionNotSupported".Translate(), MessageSound.SeriousAlert); + return false; + } + else if ("3".Equals(version)) { + result = new ColonistLoaderVersion3().Load(loadout, charMakerPage, colonistName); + } + else { + throw new Exception("Invalid preset version"); + } + + return result; + } + } +} + diff --git a/Source/ColonistSaver.cs b/Source/ColonistSaver.cs new file mode 100644 index 0000000..5a1b07a --- /dev/null +++ b/Source/ColonistSaver.cs @@ -0,0 +1,36 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using Verse; + +namespace EdB.PrepareCarefully +{ + public static class ColonistSaver + { + // + // Static Methods + // + public static void SaveToFile(PrepareCarefully loadout, Page_ConfigureStartingPawnsCarefully page, string colonistName) + { + try { + Scribe.InitWriting(ColonistFiles.FilePathForSavedColonist(colonistName), "colonist"); + string versionStringFull = "3"; + Scribe_Values.LookValue(ref versionStringFull, "version", null, false); + string modString = GenText.ToCommaList(Enumerable.Select(LoadedModManager.RunningMods, (Func) (mod => mod.Name)), true); + Scribe_Values.LookValue(ref modString, "mods", null, false); + + SaveRecordPawnV3 pawn = new SaveRecordPawnV3(page.SelectedPawn); + Scribe_Deep.LookDeep(ref pawn, "colonist"); + } + catch (Exception e) { + Log.Error("Failed to save preset file"); + throw e; + } + finally { + Scribe.FinalizeWriting(); + Scribe.mode = LoadSaveMode.Inactive; + } + } + } +} diff --git a/Source/ColorValidator.cs b/Source/ColorValidator.cs new file mode 100644 index 0000000..01f6fcc --- /dev/null +++ b/Source/ColorValidator.cs @@ -0,0 +1,58 @@ +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public static class ColorValidator + { + public static Color ColorEmpty = new Color(-1f, -1f, -1f, -1f); + + public static bool Validate(ColorGenerator generator, Color color) { + if (typeof(ColorGenerator_Options).Equals(generator.GetType())) { + return Validate((ColorGenerator_Options)generator, color); + } + else if (typeof(ColorGenerator_Single).Equals(generator.GetType())) { + return Validate((ColorGenerator_Single)generator, color); + } + else if (typeof(ColorGenerator_StandardApparel).Equals(generator.GetType())) { + return Validate((ColorGenerator_StandardApparel)generator, color); + } + else if (typeof(ColorGenerator_White).Equals(generator.GetType())) { + return Validate((ColorGenerator_White)generator, color); + } + else { + return true; + } + } + + public static bool Validate(ColorGenerator_Single generator, Color color) { + return color == generator.color; + } + + public static bool Validate(ColorGenerator_Options generator, Color color) { + foreach (ColorOption allowedColor in generator.options) { + if (allowedColor.only != ColorEmpty) { + if (color == allowedColor.only) { + return true; + } + } + else if (color.r >= allowedColor.min.r && color.g >= allowedColor.min.g && color.b >= allowedColor.min.b && color.a >= allowedColor.min.a + && color.r <= allowedColor.max.r && color.g <= allowedColor.max.g && color.b <= allowedColor.max.b && color.a <= allowedColor.max.a) + { + return true; + } + } + return false; + } + + public static bool Validate(ColorGenerator_StandardApparel generator, Color color) { + return true; + } + + public static bool Validate(ColorGenerator_White generator, Color color) { + return (color == Color.white); + } + } +} + diff --git a/Source/Configuration.cs b/Source/Configuration.cs new file mode 100644 index 0000000..48adeaf --- /dev/null +++ b/Source/Configuration.cs @@ -0,0 +1,19 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Configuration + { + public bool showPoints = true; + public int points = 12000; + public int minColonists = 1; + public int maxColonists = 12; + public Nullable hardMaxColonists = null; + public bool pointsEnabled = false; + public bool fixedPointsEnabled = false; + } +} + diff --git a/Source/CostCalculator.cs b/Source/CostCalculator.cs new file mode 100644 index 0000000..68684fe --- /dev/null +++ b/Source/CostCalculator.cs @@ -0,0 +1,272 @@ +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 marketValue = 0; + public void Clear() { + total = 0; + passions = 0; + traits = 0; + apparel = 0; + bionics = 0; + marketValue = 0; + } + public void ComputeTotal() { + total = Math.Ceiling(passions + traits + apparel + bionics + marketValue); + } + 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 Pawn pawn = null; + public void Clear(int colonistCount) { + total = 0; + equipment = 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); + total = equipment; + 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) + { + cost.Clear(pawns.Count); + + int i = 0; + foreach (var pawn in pawns) { + 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; + + cost.marketValue = pawn.Pawn.MarketValue; + cost.marketValue += 300; + + if (pawn.RandomInjuries) { + float ageMultiplier = pawn.BiologicalAge; + if (ageMultiplier > 100) { + ageMultiplier = 100; + } + float injuryValue = Mathf.Pow(ageMultiplier, 1.177455f); + injuryValue = injuryValue / 10f; + injuryValue = Mathf.Floor(injuryValue) * 10f; + cost.marketValue -= injuryValue; + } + + double skillCount = pawn.passions.Keys.Count(); + double passionLevelCount = 0; + double passionLevelCost = 20; + double passionateSkillCount = 0; + foreach (SkillDef def in pawn.passions.Keys) + { + Passion passion = pawn.passions[def]; + int level = pawn.GetSkillLevel(def); + + if (passion == Passion.Major) { + passionLevelCount += 2.0; + passionateSkillCount += 1.0; + } + else if (passion == Passion.Minor) { + passionLevelCount += 1.0; + passionateSkillCount += 1.0; + } + } + + if (passionLevelCount > 8) { + double penalty = passionLevelCount - 8; + cost.marketValue += (passionLevelCost + 10) * penalty; + passionLevelCount -= 8; + } + cost.marketValue += passionLevelCost * passionLevelCount; + + for (int layer = 0; layer < PawnLayers.Count; layer++) { + if (PawnLayers.IsApparelLayer(layer)) { + var def = pawn.GetAcceptedApparel(layer); + SelectedEquipment customPawn = new SelectedEquipment(); + customPawn.def = def; + customPawn.count = 1; + if (def != null) { + var stuff = pawn.GetSelectedStuff(layer); + customPawn.stuffDef = stuff; + } + double c = CalculateEquipmentCost(customPawn); + if (def != null) { + if (customPawn.stuffDef != null) { + if (customPawn.stuffDef.defName == "Synthread") { + if (freeApparel.Contains(customPawn.def.defName)) { + c = 0; + } + else if (cheapApparel.Contains(customPawn.def.defName)) { + c = c * 0.15d; + } + } + } + } + cost.apparel += c; + } + } + + foreach (Implant option in pawn.Implants) { + + // Check if there are any ancestor parts that override the selection. + if (PrepareCarefully.Instance.HealthManager.ImplantManager.AncestorIsImplant(option.BodyPartRecord, pawn)) { + 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 == ThingDefOf.Medicine) { + skip = true; + break; + } + count++; + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[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); + + cost.Multiply(1.0); + + cost.ComputeTotal(); + } + + public double CalculateEquipmentCost(SelectedEquipment customPawn) + { + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[customPawn.EquipmentKey]; + if (entry != null) { + return (double) customPawn.count * entry.cost; + } + else { + return 0; + } + } + + public double GetBaseThingCost(ThingDef def, ThingDef stuffDef) + { + if (def.BaseMarketValue > 0) { + if (stuffDef == null) { + return def.BaseMarketValue; + } + else { + Thing thing = ThingMaker.MakeThing(def, stuffDef); + return thing.MarketValue; + } + } + 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/CustomBodyPart.cs b/Source/CustomBodyPart.cs new file mode 100644 index 0000000..29bafa6 --- /dev/null +++ b/Source/CustomBodyPart.cs @@ -0,0 +1,46 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public abstract class CustomBodyPart + { + public abstract BodyPartRecord BodyPartRecord { + get; + set; + } + + public virtual string PartName { + get { + return BodyPartRecord != null ? BodyPartRecord.def.LabelCap : "No part!"; + } + } + + abstract public string ChangeName { + get; + } + + abstract public Color LabelColor { + get; + } + + abstract public void AddToPawn(CustomPawn customPawn, Pawn pawn); + + public virtual bool HasTooltip { + get { + return false; + } + } + + public virtual string Tooltip { + get { + return ""; + } + } + + } +} + diff --git a/Source/CustomMapInitData.cs b/Source/CustomMapInitData.cs new file mode 100644 index 0000000..7ad55aa --- /dev/null +++ b/Source/CustomMapInitData.cs @@ -0,0 +1,234 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class CustomMapInitData + { + protected static CustomMapInitData instance; + + public static CustomMapInitData Instance + { + get { + if (instance == null) { + instance = new CustomMapInitData(); + } + return instance; + } + } + + protected List colonists = new List(); + protected List equipment = new List(); + protected List bodyModifications = new List(); + protected List mapGenerationSteps = new List(); + protected string startText = null; + + public CustomMapInitData() + { + InitializeDefaultEquipment(); + } + + public void Clear() + { + ClearColonists(); + ClearEquipment(); + mapGenerationSteps.Clear(); + InitializeDefaultEquipment(); + startText = null; + } + + public string StartText + { + get { + return startText; + } + set { + startText = value; + } + } + + public List BodyModifications + { + get { + return bodyModifications; + } + set { + if (value != null) { + bodyModifications = value; + } + else { + bodyModifications.Clear(); + } + } + } + + public List MapGenerationSteps + { + get { + return mapGenerationSteps; + } + set { + if (value != null) { + mapGenerationSteps = value; + } + else { + mapGenerationSteps.Clear(); + } + } + } + + public List Colonists + { + get { + return colonists; + } + set { + bodyModifications.Clear(); + if (value != null) { + colonists = value; + foreach (Pawn p in value) { + bodyModifications.Add(new BodyPartModificationList()); + } + } + else { + colonists.Clear(); + } + } + } + + public void ClearColonists() + { + colonists.Clear(); + bodyModifications.Clear(); + } + + public bool HasColonists() { + return colonists != null && colonists.Count > 0; + } + + public void AddColonist(Pawn pawn) + { + colonists.Add(pawn); + bodyModifications.Add(new BodyPartModificationList()); + } + + public void AddBodyModification(Pawn pawn, BodyPartRecord bodyPartRecord, RecipeDef recipeDef) { + int index = colonists.IndexOf(pawn); + if (index > -1) { + BodyPartModification option = new BodyPartModification(); + option.recipe = recipeDef; + option.bodyPartRecord = bodyPartRecord; + option.label = option.AddedBodyPart.LabelCap; + bodyModifications[index].modifications.Add(option); + } + } + + public List Equipment + { + get { + return equipment; + } + set { + equipment = value; + } + } + + public void ClearEquipment() + { + equipment.Clear(); + } + + public void AddEquipment(string thingDefName, string stuffDefName, int count) + { + ThingDef thingDef = DefDatabase.GetNamedSilentFail(thingDefName); + ThingDef stuffDef = null; + if (stuffDefName != null) { + stuffDef = DefDatabase.GetNamedSilentFail(stuffDefName); + } + equipment.Add(new StandardEquipment(thingDef, stuffDef, count)); + } + + public void AddEquipmentStacks(string thingDefName, string stuffDefName, int stackCount, int minStackSize, int maxStackSize) + { + ThingDef thingDef = DefDatabase.GetNamedSilentFail(thingDefName); + ThingDef stuffDef = null; + if (stuffDefName != null) { + stuffDef = DefDatabase.GetNamedSilentFail(stuffDefName); + } + equipment.Add(new StandardEquipment(thingDef, stuffDef, stackCount, minStackSize, maxStackSize)); + } + + public void InitializeDefaultEquipment() { + ThingDef def; + if (ThingDefOf.Silver != null) { + equipment.Add(new StandardEquipment(ThingDefOf.Silver, null, 6, 40, 60)); + equipment.Add(new StandardEquipment(ThingDefOf.Silver, null, 6, 40, 60)); + equipment.Add(new StandardEquipment(ThingDefOf.Silver, null, 3, 40, 60)); + equipment.Add(new StandardEquipment(ThingDefOf.Silver, null, 3, 40, 60)); + } + if (ThingDefOf.Steel != null) { + equipment.Add(new StandardEquipment(ThingDefOf.Steel, null, 6, 40, 60)); + } + if (ThingDefOf.WoodLog != null) { + equipment.Add(new StandardEquipment(ThingDefOf.WoodLog, null, 6, 40, 60)); + } + if (ThingDefOf.Medicine != null) { + equipment.Add(new StandardEquipment(ThingDefOf.Medicine, 18)); + } + if (ThingDefOf.MealSurvivalPack != null) { + equipment.Add(new StandardEquipment(ThingDefOf.MealSurvivalPack, 24)); + } + def = DefDatabase.GetNamedSilentFail("Gun_Pistol"); + if (def != null) { + equipment.Add(new StandardEquipment(def, 1)); + } + def = DefDatabase.GetNamedSilentFail("Gun_LeeEnfield"); + if (def != null) { + equipment.Add(new StandardEquipment(def, 1)); + } + def = DefDatabase.GetNamedSilentFail("MeleeWeapon_Knife"); + if (def != null) { + equipment.Add(new StandardEquipment(def, ThingDefOf.Plasteel, 1)); + } + } + + // EdB: Copied from MapInitData.AnyoneCanDoBasicWork() and changed from a static to an instance method. + public bool AnyoneCanDoBasicWorks() + { + if (colonists.Count == 0) { + return false; + } + WorkTypeDef[] array = new WorkTypeDef[] { + WorkTypeDefOf.Hauling, + WorkTypeDefOf.Construction, + WorkTypeDefOf.Mining, + WorkTypeDefOf.Growing, + WorkTypeDefOf.Cleaning, + WorkTypeDefOf.PlantCutting, + WorkTypeDefOf.Repair + }; + WorkTypeDef[] array2 = array; + WorkTypeDef wt; + for (int i = 0; i < array2.Length; i++) { + wt = array2[i]; + if (!Colonists.Any((Pawn col) => !col.story.WorkTypeIsDisabled(wt))) { + return false; + } + } + return true; + } + + // EdB: Copied from MapInitData.RegeneratePawn() and changed from a static to an instance method. + public Pawn RegeneratePawn(Pawn p) + { + Pawn pawn = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); + //MapInitData.colonists[MapInitData.colonists.IndexOf(p)] = pawn; + Colonists[Colonists.IndexOf(p)] = pawn; + return pawn; + } + } +} + diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs new file mode 100644 index 0000000..c76a65e --- /dev/null +++ b/Source/CustomPawn.cs @@ -0,0 +1,1218 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Verse; +using System.Text; + +namespace EdB.PrepareCarefully +{ + public class ApparelConflict + { + public ThingDef def; + public ThingDef conflict; + } + + public class CustomPawn + { + public Dictionary baseSkillLevels = new Dictionary(); + public Dictionary requiredSkillAdjustments = new Dictionary(); + protected Dictionary skillAdjustments = new Dictionary(); + protected Dictionary originalSkillAdjustments = new Dictionary(); + public Dictionary disabledSkills = new Dictionary(); + public Dictionary passions = new Dictionary(); + public Dictionary originalPassions = new Dictionary(); + public bool randomRelations = false; + protected string incapable = null; + protected Pawn pawn; + + protected static readonly int TraitCount = 3; + protected List traits = new List(TraitCount); + + protected const int LayerCount = PawnLayers.Count; + + public List graphics = new List(LayerCount); + protected List colors = new List(LayerCount); + + protected List selectedApparel = new List(LayerCount); + protected List acceptedApparel = new List(LayerCount); + protected List selectedStuff = new List(LayerCount); + protected Dictionary colorCache = new Dictionary(); + protected string apparelConflictText = null; + protected List apparelConflicts = new List(); + protected bool randomInjuries = false; + + // The tick of the year when the pawn was born. + protected long birthTicks = 0; + + protected bool hasRelationships = false; + + protected List implants = new List(); + protected List injuries = new List(); + public List bodyParts = new List(); + + public CustomPawn() + { + } + + public CustomPawn(Pawn pawn) + { + InitializeWithPawn(pawn); + } + + public bool RandomRelations { + get { + return randomRelations; + } + set { + randomRelations = value; + } + } + + public bool HasRelationships { + get { + return hasRelationships; + } + set { + hasRelationships = value; + } + } + + public bool HasCustomBodyParts { + get { + return bodyParts.Count > 0; + } + } + + public List Injuries { + get { return injuries; } + set { injuries = value; } + } + + public List BodyParts { + get { + return bodyParts; + } + } + + public void InitializeWithPawn(Pawn pawn) + { + this.pawn = this.CopyPawn(pawn); + + this.birthTicks = this.pawn.ageTracker.BirthAbsTicks % 3600000L; + + // Set the traits. + this.traits.Clear(); + for (int i = 0; i < TraitCount; i++) { + this.traits.Add(null); + } + List pawnTraits = pawn.story.traits.allTraits; + if (pawnTraits.Count > 0) { + this.traits[0] = pawnTraits[0]; + } + if (pawnTraits.Count > 1 && this.traits[0] != pawnTraits[1]) { + this.traits[1] = pawnTraits[1]; + } + if (pawnTraits.Count > 2 && this.traits[0] != pawnTraits[2] && this.traits[1] != pawnTraits[2] ) { + this.traits[2] = pawnTraits[2]; + } + + // Set the skills. + ComputeBaseSkillLevels(); + foreach (SkillRecord record in pawn.skills.skills) { + skillAdjustments[record.def] = record.level - baseSkillLevels[record.def] - requiredSkillAdjustments[record.def]; + originalSkillAdjustments[record.def] = skillAdjustments[record.def]; + passions[record.def] = record.passion; + originalPassions[record.def] = record.passion; + } + ComputePawnSkillLevels(); + + graphics.Clear(); + colors.Clear(); + PawnGraphicSet pawnGraphics = pawn.Drawer.renderer.graphics; + + graphics.Add(GraphicGetter_NakedHumanlike.GetNakedBodyGraphic(pawn.story.BodyType, ShaderDatabase.CutoutSkin, pawn.story.SkinColor)); + colors.Add(pawn.story.SkinColor); + + graphics.Add(null); + colors.Add(Color.white); + graphics.Add(null); + colors.Add(Color.white); + graphics.Add(null); + colors.Add(Color.white); + graphics.Add(null); + colors.Add(Color.white); + + graphics.Add(GraphicDatabaseHeadRecords.GetHeadNamed(pawn.story.HeadGraphicPath, pawn.story.SkinColor)); + colors.Add(pawn.story.SkinColor); + ResetHead(); + + graphics.Add(GraphicsCache.Instance.GetHair(pawn.story.hairDef)); + colors.Add(pawn.story.hairColor); + + graphics.Add(null); + colors.Add(Color.white); + + for (int i = 0; i < PawnLayers.Count; i++) { + selectedApparel.Add(null); + acceptedApparel.Add(null); + selectedStuff.Add(null); + } + foreach (Apparel current in this.pawn.apparel.WornApparel) { + Graphic graphic = GraphicsCache.Instance.GetApparel(current.def, pawn.story.BodyType); + Color color = current.DrawColor; + int layer = PawnLayers.ToPawnLayerIndex(current.def.apparel); + if (layer != -1) { + graphics[layer] = graphic; + SetSelectedApparel(layer, current.def); + acceptedApparel[layer] = current.def; + SetSelectedStuff(layer, current.Stuff); + if (ApparelIsTintedByDefault(current.def, current.Stuff)) { + SetColor(layer, color); + } + } + } + + pawn.health.capacities.Clear(); + } + + protected bool ApparelIsTintedByDefault(ThingDef def, ThingDef stuffDef) + { + if (stuffDef == null) { + if (def.colorGenerator != null) { + return true; + } + else { + return false; + } + } + else { + if (stuffDef.stuffProps.allowColorGenerators) { + return true; + } + else { + return false; + } + } + } + + public NameTriple Name { + get { + return pawn.Name as NameTriple; + } + set { + pawn.Name = value; + } + } + + public string FirstName { + get { + NameTriple nameTriple = pawn.Name as NameTriple; + if (nameTriple != null) { + return nameTriple.First; + } + else { + return null; + } + } + set { + pawn.Name = new NameTriple(value, NickName, LastName); + } + } + + public string NickName { + get { + NameTriple nameTriple = pawn.Name as NameTriple; + if (nameTriple != null) { + return nameTriple.Nick; + } + else { + return null; + } + } + set { + pawn.Name = new NameTriple(FirstName, value, LastName); + } + } + + public string LastName { + get { + NameTriple nameTriple = pawn.Name as NameTriple; + if (nameTriple != null) { + return nameTriple.Last; + } + else { + return null; + } + } + set { + pawn.Name = new NameTriple(FirstName, NickName, value); + } + } + + public Pawn Pawn { + get { + return pawn; + } + } + + public string Label { + get { + NameTriple name = pawn.Name as NameTriple; + if (pawn.story.adulthood == null) { + return name.Nick; + } + return name.Nick + ", " + pawn.story.adulthood.titleShort; + } + } + + public bool RandomInjuries { + get { + return randomInjuries; + } + set { + randomInjuries = value; + } + } + + public IEnumerable Implants { + get { + return implants; + } + } + + public bool IsBodyPartReplaced(BodyPartRecord record) { + Implant implant = implants.FirstOrDefault((Implant i) => { + return i.BodyPartRecord == record; + }); + return implant != null; + } + + public void IncreasePassion(SkillDef def) { + if (IsDisabled(def)) { + return; + } + if (passions[def] == Passion.None) { + passions[def] = Passion.Minor; + } + else if (passions[def] == Passion.Minor) { + passions[def] = Passion.Major; + } + else if (passions[def] == Passion.Major) { + passions[def] = Passion.None; + } + pawn.skills.GetSkill(def).passion = passions[def]; + } + + public void DecreasePassion(SkillDef def) { + if (IsDisabled(def)) { + return; + } + if (passions[def] == Passion.None) { + passions[def] = Passion.Major; + } + else if (passions[def] == Passion.Minor) { + passions[def] = Passion.None; + } + else if (passions[def] == Passion.Major) { + passions[def] = Passion.Minor; + } + pawn.skills.GetSkill(def).passion = passions[def]; + } + + public List AllAcceptedApparel { + get { + List result = new List(); + for (int i = 0; i < PawnLayers.Count; i++) { + ThingDef def = this.acceptedApparel[i]; + if (def != null) { + result.Add(def); + } + } + return result; + } + } + + public ThingDef GetAcceptedApparel(int layer) { + return this.acceptedApparel[layer]; + } + + public Color GetBlendedColor(int layer) { + Color color = this.colors[layer] * GetStuffColor(layer); + return color; + } + + public Color GetColor(int layer) { + return this.colors[layer]; + } + + public void ClearColorCache() { + colorCache.Clear(); + } + + public Color GetStuffColor(int layer) { + ThingDef apparelDef = this.selectedApparel[layer]; + if (apparelDef != null) { + Color color = this.colors[layer]; + if (apparelDef.MadeFromStuff) { + ThingDef stuffDef = this.selectedStuff[layer]; + if (!stuffDef.stuffProps.allowColorGenerators) { + return stuffDef.stuffProps.color; + } + } + } + return Color.white; + } + + public void SetColor(int layer, Color color) { + this.colors[layer] = color; + if (PawnLayers.IsApparelLayer(layer)) { + colorCache[new EquipmentKey(selectedApparel[layer], selectedStuff[layer])] = color; + } + if (layer == PawnLayers.BodyType) { + SkinColor = color; + } + else if (layer == PawnLayers.HeadType) { + SkinColor = color; + } + } + + public bool ColorMatches(Color a, Color b) { + if (a.r > b.r - 0.001f && a.r < b.r + 0.001f + && a.r > b.r - 0.001f && a.r < b.r + 0.001f + && a.r > b.r - 0.001f && a.r < b.r + 0.001f) + { + return true; + } + else { + return false; + } + } + + public ThingDef GetSelectedApparel(int layer) { + return this.selectedApparel[layer]; + } + + public void SetSelectedApparel(int layer, ThingDef def) { + if (layer < 0) { + return; + } + this.selectedApparel[layer] = def; + if (def != null) { + ThingDef stuffDef = this.GetSelectedStuff(layer); + this.graphics[layer] = GraphicsCache.Instance.GetApparel(def, BodyType); + EquipmentKey pair = new EquipmentKey(def, stuffDef); + if (colorCache.ContainsKey(pair)) { + this.colors[layer] = colorCache[pair]; + } + else { + if (stuffDef == null) { + if (def.colorGenerator != null) { + if (!ColorValidator.Validate(def.colorGenerator, this.colors[layer])) { + this.colors[layer] = def.colorGenerator.NewRandomizedColor(); + } + } + else { + this.colors[layer] = Color.white; + } + } + else { + if (stuffDef.stuffProps.allowColorGenerators) { + this.colors[layer] = stuffDef.stuffProps.color; + } + else { + this.colors[layer] = Color.white; + } + } + } + } + else { + this.graphics[layer] = null; + } + this.acceptedApparel[layer] = def; + ApparelAcceptanceTest(); + } + + public ThingDef GetSelectedStuff(int layer) { + return this.selectedStuff[layer]; + } + + public void SetSelectedStuff(int layer, ThingDef stuffDef) { + if (layer < 0) { + return; + } + this.selectedStuff[layer] = stuffDef; + if (stuffDef != null) { + ThingDef apparelDef = this.GetSelectedApparel(layer); + if (apparelDef != null) { + EquipmentKey pair = new EquipmentKey(apparelDef, stuffDef); + Color color; + if (colorCache.TryGetValue(pair, out color)) { + colors[layer] = color; + } + else { + if (stuffDef.stuffProps.allowColorGenerators) { + colors[layer] = stuffDef.stuffProps.color; + } + else { + colors[layer] = Color.white; + } + } + } + } + } + + protected void ApparelAcceptanceTest() { + apparelConflicts.Clear(); + for (int i = PawnLayers.TopClothingLayer; i >= PawnLayers.BottomClothingLayer; i--) { + this.acceptedApparel[i] = this.selectedApparel[i]; + } + + for (int i = PawnLayers.TopClothingLayer; i >= PawnLayers.BottomClothingLayer; i--) { + if (selectedApparel[i] == null) { + continue; + } + ThingDef apparel = selectedApparel[i]; + if (apparel.apparel != null && apparel.apparel.layers != null && apparel.apparel.layers.Count > 1) { + foreach (ApparelLayer layer in apparel.apparel.layers) { + if (layer == PawnLayers.ToApparelLayer(i)) { + continue; + } + int disallowedLayer = PawnLayers.ToPawnLayerIndex(layer); + if (this.selectedApparel[disallowedLayer] != null) { + ApparelConflict conflict = new ApparelConflict(); + conflict.def = selectedApparel[i]; + conflict.conflict = selectedApparel[disallowedLayer]; + apparelConflicts.Add(conflict); + this.acceptedApparel[disallowedLayer] = null; + } + } + } + } + + if (apparelConflicts.Count > 0) { + HashSet defs = new HashSet(); + foreach (ApparelConflict conflict in apparelConflicts) { + defs.Add(conflict.def); + } + List sortedDefs = new List(defs); + sortedDefs.Sort((ThingDef a, ThingDef b) => { + int c = PawnLayers.ToPawnLayerIndex(a.apparel); + int d = PawnLayers.ToPawnLayerIndex(b.apparel); + if (c > d) { + return -1; + } + else if (c < d) { + return 1; + } + else { + return 0; + } + }); + + StringBuilder builder = new StringBuilder(); + int index = 0; + foreach (ThingDef def in sortedDefs) { + string label = def.label; + string message = "EdB.ApparelConflictDescription".Translate(); + message = message.Replace("{0}", label); + builder.Append(message); + builder.AppendLine(); + foreach (ApparelConflict conflict in apparelConflicts.FindAll((ApparelConflict c) => { return c.def == def; })) { + builder.Append("EdB.ApparelConflictLineItem".Translate().Replace("{0}", conflict.conflict.label)); + builder.AppendLine(); + } + if (++index < sortedDefs.Count) { + builder.AppendLine(); + } + } + this.apparelConflictText = builder.ToString(); + } + else { + this.apparelConflictText = null; + } + } + + public string ApparelConflict + { + get { + return apparelConflictText; + } + } + + public Backstory Childhood { + get { + return pawn.story.childhood; + } + set { + pawn.story.childhood = value; + ComputePawnSkillLevels(); + this.pawn.health.capacities.Clear(); + } + } + + public Backstory Adulthood { + get { + return pawn.story.adulthood; + } + set { + pawn.story.adulthood = value; + ComputePawnSkillLevels(); + this.pawn.health.capacities.Clear(); + ResetBodyType(); + } + } + + protected void CheckSkills() { + foreach (var record in pawn.skills.skills) { + int value = GetSkillLevel(record.def); + if (value > 20) { + skillAdjustments[record.def] -= (value - 20); + } + if (IsDisabled(record.def)) { + record.passion = Passion.None; + } + else { + record.passion = passions[record.def]; + } + } + } + + public string HeadGraphicPath { + get { + return pawn.story.HeadGraphicPath; + } + set { + // Need to use reflection to set the private field. + typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, value); + ResetHead(); + } + } + + protected string FilterHeadPathForGender(string path) { + if (pawn.gender == Gender.Male) { + return path.Replace("Female", "Male"); + } + else { + return path.Replace("Male", "Female"); + } + } + + public Trait GetTrait(int index) { + return this.traits[index]; + } + + public void ClearTrait(int index) { + this.traits[index] = null; + ResetTraits(); + } + + public void SetTrait(int index, Trait trait) { + this.traits[index] = trait; + ResetTraits(); + } + + public IEnumerable Traits { + get { + return this.traits; + } + } + + protected void ResetTraits() { + pawn.story.traits.allTraits.Clear(); + foreach (Trait trait in this.traits) { + if (trait != null) { + pawn.story.traits.GainTrait(trait); + } + } + ResetIncapableOf(); + pawn.health.capacities.Clear(); + ComputePawnSkillLevels(); + } + + public bool HasTrait(Trait trait) { + return this.traits.Find((Trait t) => { + if (t == null && trait == null) { + return true; + } + else if (trait == null || t == null) { + return false; + } + else if (trait.Label.Equals(t.Label)) { + return true; + } + else { + return false; + } + }) != null; + } + + public string IncapableOf { + get { + return incapable; + } + } + + public BodyType BodyType { + get { + return pawn.story.BodyType; + } + } + + public Gender Gender { + get { + return pawn.gender; + } + set { + if (pawn.gender != value) { + pawn.gender = value; + ResetGender(); + } + } + } + + public Color SkinColor { + get { + return pawn.story.SkinColor; + } + set { + pawn.story.skinWhiteness = PawnColorUtils.GetSkinValue(value); + this.colors[PawnLayers.HeadType] = value; + this.colors[PawnLayers.BodyType] = value; + } + } + + public HairDef HairDef { + get { + return pawn.story.hairDef; + } + set { + pawn.story.hairDef = value; + if (value == null) { + graphics[PawnLayers.Hair] = null; + } + else { + graphics[PawnLayers.Hair] = GraphicsCache.Instance.GetHair(value); + } + } + } + + public int ChronologicalAge { + get { + return pawn.ageTracker.AgeChronologicalYears; + } + set { + long years = pawn.ageTracker.AgeChronologicalYears; + long diff = value - years; + pawn.ageTracker.BirthAbsTicks -= diff * 3600000L; + ClearCachedLifeStage(); + ClearCachedAbilities(); + } + } + + public int BiologicalAge { + get { + return pawn.ageTracker.AgeBiologicalYears; + } + set { + long years = pawn.ageTracker.AgeBiologicalYears; + long diff = value - years; + pawn.ageTracker.AgeBiologicalTicks += diff * 3600000L; + ClearCachedLifeStage(); + ClearCachedAbilities(); + } + } + + protected void ResetHead() + { + // Need to use reflection to set the private field. + typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, FilterHeadPathForGender(pawn.story.HeadGraphicPath)); + graphics[PawnLayers.HeadType] = GraphicDatabaseHeadRecords.GetHeadNamed(pawn.story.HeadGraphicPath, pawn.story.SkinColor); + } + + protected void ResetGender() + { + if (pawn.gender == Gender.Female) { + if (HairDef.hairGender == HairGender.Male) { + HairDef = DefDatabase.AllDefsListForReading.Find((HairDef def) => { + return def.hairGender != HairGender.Male; + }); + } + } + else { + if (HairDef.hairGender == HairGender.Female) { + HairDef = DefDatabase.AllDefsListForReading.Find((HairDef def) => { + return def.hairGender != HairGender.Female; + }); + } + } + + ResetHead(); + ResetBodyType(); + } + + protected void ResetBodyType() + { + graphics[PawnLayers.BodyType] = GraphicGetter_NakedHumanlike.GetNakedBodyGraphic(pawn.story.BodyType, ShaderDatabase.Cutout, pawn.story.SkinColor); + foreach (ThingDef def in selectedApparel) { + if (def != null) { + int layer = PawnLayers.ToPawnLayerIndex(def.apparel); + if (layer != -1) { + graphics[layer] = GraphicsCache.Instance.GetApparel(def, pawn.story.BodyType); + } + } + } + } + + public void ResetSkills() + { + foreach (var record in pawn.skills.skills) { + this.skillAdjustments[record.def] = this.originalSkillAdjustments[record.def]; + this.passions[record.def] = this.originalPassions[record.def]; + } + ComputePawnSkillLevels(); + } + + public void ClearSkills() + { + foreach (var record in pawn.skills.skills) { + this.skillAdjustments[record.def] = 0; + this.passions[record.def] = Passion.None; + } + ComputePawnSkillLevels(); + } + + public void ResetOriginalSkillsAndPassions() + { + foreach (var record in pawn.skills.skills) { + this.originalSkillAdjustments[record.def] = this.skillAdjustments[record.def]; + this.originalPassions[record.def] = this.passions[record.def]; + } + } + + protected void ComputePawnSkillLevels() { + ResetIncapableOf(); + ResetDisabledSkills(); + ComputeBaseSkillLevels(); + foreach (var record in pawn.skills.skills) { + SkillDef def = record.def; + /* + int level = baseSkillLevels[def]; + if (level < 0) { + level = 0; + } + level += skillAdjustments[def]; + if (level > 20) { + skillAdjustments[def] -= (level - 20); + level = 20; + } + if (level < 0) { + level = 0; + } + if (IsDisabled(def)) { + level = 0; + } + */ + pawn.skills.GetSkill(def).level = GetSkillLevel(def); + pawn.skills.GetSkill(def).passion = passions[def]; + } + } + + protected void ComputeBaseSkillLevels() + { + foreach (var record in pawn.skills.skills) { + baseSkillLevels[record.def] = 0; + } + foreach (SkillDef def in pawn.story.childhood.skillGainsResolved.Keys) { + baseSkillLevels[def] += pawn.story.childhood.skillGainsResolved[def]; + } + foreach (SkillDef def in pawn.story.adulthood.skillGainsResolved.Keys) { + baseSkillLevels[def] += pawn.story.adulthood.skillGainsResolved[def]; + } + foreach (Trait trait in pawn.story.traits.allTraits) { + foreach (TraitDegreeData data in trait.def.degreeDatas) { + foreach (var pair in data.skillGains) { + SkillDef def = pair.Key; + baseSkillLevels[def] += pair.Value; + } + } + } + foreach (var pair in baseSkillLevels) { + //requiredSkillAdjustments[pair.Key] = pair.Value >= -3 ? 0 : (-pair.Value - 3); + requiredSkillAdjustments[pair.Key] = pair.Value < 0 ? -pair.Value : 0; + } + } + + public int GetBaseSkillLevel(SkillDef def) + { + return baseSkillLevels[def]; + } + + public int GetSkillAdjustments(SkillDef def) + { + return skillAdjustments[def]; + } + + public int GetRequiredSkillAdjustments(SkillDef def) + { + return requiredSkillAdjustments[def]; + } + + public int GetSkillLevel(SkillDef def) + { + if (this.IsDisabled(def)) { + return 0; + } + else { + int level = baseSkillLevels[def] + requiredSkillAdjustments[def] + skillAdjustments[def]; + return level; + } + } + + public void IncreaseSkill(SkillDef def) + { + if (!IsDisabled(def) && GetSkillLevel(def) < 20) { + skillAdjustments[def]++; + pawn.skills.GetSkill(def).level = GetSkillLevel(def); + } + } + + public void SetSkillLevel(SkillDef def, int value) + { + value -= baseSkillLevels[def]; + if (value > 20) { + value = 20; + } + else if (value < 0) { + value = 0; + } + if (!IsDisabled(def)) { + skillAdjustments[def] = value; + pawn.skills.GetSkill(def).level = GetSkillLevel(def); + } + } + + // Maximum skill level is always 20. + protected int MaximumSkillLevel(SkillDef def) { + return 20; + } + + // Minimum skill level is normal 0, but it may be higher if backstories and traits adjust it. + protected int MinimumSkillLevel(SkillDef def) { + int value = UnadjustedSkillLevel(def); + if (value < 0) { + value = 0; + } + return value; + } + + protected int UnadjustedSkillLevel(SkillDef def) { + int value = 0; + value += pawn.story.childhood.skillGainsResolved[def]; + value += pawn.story.adulthood.skillGainsResolved[def]; + foreach (Trait trait in this.traits) { + if (trait != null) { + foreach (TraitDegreeData data in trait.def.degreeDatas) { + foreach (var pair in data.skillGains) { + SkillDef skillDef = pair.Key; + if (skillDef == def) { + value += pair.Value; + } + } + } + } + } + return value; + } + + public void SetSkillAdjustment(SkillDef def, int value) + { + if (value < 0) { + value = 0; + } + if (baseSkillLevels[def] + value > 20) { + value -= (baseSkillLevels[def] - 20); + } + skillAdjustments[def] = value; + pawn.skills.GetSkill(def).level = GetSkillLevel(def); + } + + public void DecreaseSkill(SkillDef def) + { + if (!IsDisabled(def) && skillAdjustments[def] > 0) { + skillAdjustments[def]--; + pawn.skills.GetSkill(def).level = GetSkillLevel(def); + } + } + + public string ResetIncapableOf() + { + List incapableList = new List(); + foreach (var tag in pawn.story.DisabledWorkTags) { + incapableList.Add(WorkTypeDefsUtility.LabelTranslated(tag)); + } + if (incapableList.Count > 0) { + incapable = string.Join(", ", incapableList.ToArray()); + } + else { + incapable = null; + } + return incapable; + } + + public void ResetDisabledSkills() + { + foreach (var record in pawn.skills.skills) { + disabledSkills[record.def] = false; + } + foreach (var record in pawn.skills.skills) { + if (CheckForDisabledSkill(record.def)) { + disabledSkills[record.def] = true; + } + } + } + + protected bool CheckForDisabledSkill(SkillDef def) + { + foreach (WorkTypeDef w in DefDatabase.AllDefs) { + using (List.Enumerator enumerator = w.relevantSkills.GetEnumerator()) { + while (enumerator.MoveNext()) { + if (enumerator.Current == def && pawn.story.WorkTypeIsDisabled(w)) { + return true; + } + } + } + } + return false; + } + + public bool IsDisabled(SkillDef skill) + { + return pawn.skills.GetSkill(skill).TotallyDisabled; + //return disabledSkills[skill]; + } + + public bool IsApparelConflict() + { + return false; + } + + protected Pawn CopyPawn(Pawn pawn) + { + // TODO: Evaluate + //Pawn result = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); + Pawn result = new Randomizer().GenerateColonist(); + + // Reset health to remove any old injuries. + result.health = new Pawn_HealthTracker(result); + + result.gender = pawn.gender; + + // Copy age. + result.ageTracker.BirthAbsTicks = pawn.ageTracker.BirthAbsTicks; + result.ageTracker.AgeBiologicalTicks = pawn.ageTracker.AgeBiologicalTicks; + + // Copy story. + result.story.adulthood = pawn.story.adulthood; + result.story.childhood = pawn.story.childhood; + result.story.traits = new TraitSet(result); + foreach (var t in pawn.story.traits.allTraits) { + result.story.traits.allTraits.Add(t); + } + result.story.skinWhiteness = pawn.story.skinWhiteness; + NameTriple name = pawn.Name as NameTriple; + result.Name = new NameTriple(name.First, name.Nick, name.Last); + result.story.hairDef = pawn.story.hairDef; + result.story.hairColor = pawn.story.hairColor; + // Need to use reflection to set the private graphic path field. + typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, pawn.story.HeadGraphicPath); + result.story.crownType = pawn.story.crownType; + + // Copy apparel. + List pawnApparelList = (List)typeof(Pawn_ApparelTracker).GetField("wornApparel", + BindingFlags.NonPublic | BindingFlags.Instance).GetValue(pawn.apparel); + List resultApparelList = (List)typeof(Pawn_ApparelTracker).GetField("wornApparel", + BindingFlags.NonPublic | BindingFlags.Instance).GetValue(result.apparel); + resultApparelList.Clear(); + foreach (var a in pawnApparelList) { + resultApparelList.Add(a); + } + + // Copy skills. + result.skills.skills.Clear(); + foreach (var s in pawn.skills.skills) { + SkillRecord record = new SkillRecord(result, s.def); + record.level = s.level; + record.passion = s.passion; + record.xpSinceLastLevel = s.xpSinceLastLevel; + result.skills.skills.Add(record); + } + + // Copy relationships + result.relations = pawn.relations; + + return result; + } + + public Pawn ConvertToPawn(bool resolveGraphics) { + // TODO: Evaluate + //Pawn pawn = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); + Pawn pawn = new Randomizer().GenerateColonist(); + + pawn.gender = this.pawn.gender; + pawn.story.adulthood = Adulthood; + pawn.story.childhood = Childhood; + TraitSet traitSet = new TraitSet(pawn); + traitSet.allTraits.Clear(); + foreach (Trait trait in traits) { + if (trait != null) { + traitSet.allTraits.Add(trait); + } + } + pawn.story.traits = traitSet; + pawn.story.skinWhiteness = this.pawn.story.skinWhiteness; + pawn.story.hairDef = this.pawn.story.hairDef; + pawn.story.hairColor = colors[PawnLayers.Hair]; + // Need to use reflection to set the private graphic path method. + typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, HeadGraphicPath); + pawn.Name = this.pawn.Name; + + pawn.ageTracker.BirthAbsTicks = this.pawn.ageTracker.BirthAbsTicks; + pawn.ageTracker.AgeBiologicalTicks = this.pawn.ageTracker.AgeBiologicalTicks; + + FieldInfo wornApparelField = typeof(Pawn_ApparelTracker).GetField("wornApparel", BindingFlags.Instance | BindingFlags.NonPublic); + List apparel = (List)wornApparelField.GetValue(pawn.apparel); + apparel.Clear(); + + AddApparel(PawnLayers.Pants, apparel); + AddApparel(PawnLayers.BottomClothingLayer, apparel); + AddApparel(PawnLayers.MiddleClothingLayer, apparel); + AddApparel(PawnLayers.TopClothingLayer, apparel); + AddApparel(PawnLayers.Hat, apparel); + + foreach (SkillRecord skill in pawn.skills.skills) { + int value = this.GetSkillLevel(skill.def); + if (value < 0) { + value = 0; + } + skill.level = value; + if (!IsDisabled(skill.def)) { + skill.passion = this.passions[skill.def]; + skill.xpSinceLastLevel = Rand.Range(skill.XpRequiredForLevelUp * 0.1f, skill.XpRequiredForLevelUp * 0.5f); + } + else { + skill.passion = Passion.None; + skill.xpSinceLastLevel = 0; + } + } + + if (resolveGraphics) { + pawn.Drawer.renderer.graphics.ResolveAllGraphics(); + } + + pawn.relations.ClearAllRelations(); + + return pawn; + } + + public Pawn ConvertToPawn() + { + return ConvertToPawn(true); + } + + public void AddApparel(int layer, List list) + { + if (acceptedApparel[layer] != null) { + Apparel a; + if (selectedApparel[layer].MadeFromStuff) { + a = (Apparel)ThingMaker.MakeThing(selectedApparel[layer], selectedStuff[layer]); + a.DrawColor = colors[layer] * GetStuffColor(layer); + } + else { + a = (Apparel)ThingMaker.MakeThing(selectedApparel[layer], null); + a.DrawColor = colors[layer]; + } + list.Add(a); + } + } + + public void AddInjury(Injury injury) { + injuries.Add(injury); + bodyParts.Add(injury); + SyncBodyParts(); + } + + protected void SyncBodyParts() { + this.pawn.health = new Pawn_HealthTracker(pawn); + foreach (var injury in injuries) { + injury.AddToPawn(this, pawn); + } + foreach (var implant in implants) { + implant.AddToPawn(this, pawn); + } + } + + public void RemoveCustomBodyParts(CustomBodyPart part) { + Implant implant = part as Implant; + Injury injury = part as Injury; + if (implant != null) { + implants.Remove(implant); + } + if (injury != null) { + injuries.Remove(injury); + } + bodyParts.Remove(part); + SyncBodyParts(); + } + + public void RemoveCustomBodyParts(BodyPartRecord part) { + bodyParts.RemoveAll((CustomBodyPart p) => { + return part == p.BodyPartRecord; + }); + implants.RemoveAll((Implant i) => { + return part == i.BodyPartRecord; + }); + SyncBodyParts(); + } + + public void AddImplant(Implant implant) { + if (implant != null && implant.BodyPartRecord != null) { + RemoveCustomBodyParts(implant.BodyPartRecord); + implants.Add(implant); + bodyParts.Add(implant); + SyncBodyParts(); + } + } + + public void RemoveImplant(Implant implant) { + implants.Remove(implant); + bodyParts.Remove(implant); + SyncBodyParts(); + } + + public bool IsImplantedPart(BodyPartRecord record) { + return FindImplant(record) != null; + } + + public Implant FindImplant(BodyPartRecord record) { + if (implants.Count == 0) { + return null; + } + return implants.FirstOrDefault((Implant i) => { + return i.BodyPartRecord == record; + }); + } + + public void ClearCachedAbilities() { + this.pawn.health.capacities.Clear(); + } + + public void ClearCachedLifeStage() { + FieldInfo field = typeof(Pawn_AgeTracker).GetField("cachedLifeStageIndex", BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(pawn.ageTracker, -1); + } + } +} + diff --git a/Source/CustomPawnTest.cs b/Source/CustomPawnTest.cs new file mode 100644 index 0000000..e2184bc --- /dev/null +++ b/Source/CustomPawnTest.cs @@ -0,0 +1,75 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class xxCustomPawn + { + protected Pawn pawn; + + protected Backstory childhood; + protected Backstory adulthood; + + public xxCustomPawn(Pawn pawn) + { + CopyPawn(pawn); + } + + public void CopyPawn(Pawn pawn) + { + this.pawn = pawn; + this.childhood = pawn.story.adulthood; + this.adulthood = pawn.story.childhood; + } + + public Pawn CreatePawn() { + return null; + } + + public Backstory Adulthood { + get { + return this.adulthood; + } + set { + this.adulthood = value; + } + } + + public Backstory Childhood { + get { + return this.childhood; + } + set { + this.childhood = value; + } + } + + public int GetSkillLevel(SkillDef def) { + int count = this.pawn.skills.skills.Count; + for (int i = 0; i < count; i++) { + if (this.pawn.skills.skills[i].def == def) { + SkillRecord record = this.pawn.skills.skills[i]; + return record.level; + } + } + throw new IndexOutOfRangeException(); + } + + public void SetSkillLevel(SkillDef def, int level) { + int count = this.pawn.skills.skills.Count; + for (int i = 0; i < count; i++) { + if (this.pawn.skills.skills[i].def == def) { + SkillRecord record = this.pawn.skills.skills[i]; + record.level = level; + return; + } + } + throw new IndexOutOfRangeException(); + } + + } +} + diff --git a/Source/CustomRelationship.cs b/Source/CustomRelationship.cs new file mode 100644 index 0000000..ba03764 --- /dev/null +++ b/Source/CustomRelationship.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using RimWorld; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class CustomRelationship + { + public CustomPawn source; + public CustomPawn target; + public PawnRelationDef def; + public PawnRelationDef inverseDef; + public bool removeable = true; + + public CustomRelationship() + { + } + + public CustomRelationship(PawnRelationDef def, CustomPawn source, CustomPawn target) + { + this.def = def; + this.inverseDef = null; + this.source = source; + this.target = target; + this.removeable = true; + } + + public CustomRelationship(PawnRelationDef def, PawnRelationDef inverseDef, CustomPawn source, CustomPawn target) + { + this.def = def; + this.inverseDef = inverseDef; + this.source = source; + this.target = target; + this.removeable = true; + } + + public CustomRelationship(PawnRelationDef def, PawnRelationDef inverseDef, CustomPawn source, CustomPawn target, bool removeable) + { + this.def = def; + this.inverseDef = inverseDef; + this.source = source; + this.target = target; + this.removeable = removeable; + } + + public CustomRelationship(PawnRelationDef def, CustomPawn source, CustomPawn target, bool removeable) + { + this.def = def; + this.source = source; + this.target = target; + this.removeable = removeable; + } + + public override string ToString() + { + return (source != null ? source.Name.ToStringShort : "null") + + (target != null ? target.Name.ToStringShort : "null") + + (def != null ? def.defName : "null"); + } + } +} + diff --git a/Source/Dialog_Colonist.cs b/Source/Dialog_Colonist.cs new file mode 100644 index 0000000..976718b --- /dev/null +++ b/Source/Dialog_Colonist.cs @@ -0,0 +1,121 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public abstract class Dialog_Colonist : Window + { + protected const float DeleteButtonSpace = 5; + protected const float MapDateExtraLeftMargin = 220; + + private static readonly Color ManualSaveTextColor = new Color(1, 1, 0.6f); + private static readonly Color AutosaveTextColor = new Color(0.75f, 0.75f, 0.75f); + + protected const float MapEntrySpacing = 8; + protected const float BoxMargin = 20; + protected const float MapNameExtraLeftMargin = 15; + protected const float MapEntryMargin = 6; + + private Vector2 scrollPosition = Vector2.zero; + + protected string interactButLabel = "Error"; + protected float bottomAreaHeight; + + public Dialog_Colonist() + { + this.closeOnEscapeKey = true; + this.doCloseButton = true; + this.doCloseX = true; + this.absorbInputAroundWindow = true; + this.forcePause = true; + } + + protected abstract void DoMapEntryInteraction(string mapName); + + protected virtual void DoSpecialSaveLoadGUI(Rect inRect) + { + } + + public override void PostClose() + { + if (CharMakerPage != null) { + CharMakerPage.Show(); + } + else { + Find.WindowStack.Add(new Page_ConfigureStartingPawnsCarefully()); + } + GUI.FocusControl(null); + } + + protected Page_ConfigureStartingPawnsCarefully CharMakerPage + { + get { + return Find.WindowStack.WindowOfType(); + } + } + + public override void DoWindowContents(Rect inRect) + { + Vector2 vector = new Vector2(inRect.width - 16, 36); + Vector2 vector2 = new Vector2(100, vector.y - 6); + inRect.height -= 45; + List list = ColonistFiles.AllFiles.ToList(); + float num = vector.y + 3; + float height = (float)list.Count * num; + Rect viewRect = new Rect(0, 0, inRect.width - 16, height); + Rect outRect = new Rect(inRect.AtZero()); + outRect.height -= this.bottomAreaHeight; + Widgets.BeginScrollView(outRect, ref this.scrollPosition, viewRect); + float num2 = 0; + int num3 = 0; + foreach (FileInfo current in list) { + Rect rect = new Rect(0, num2, vector.x, vector.y); + if (num3 % 2 == 0) { + GUI.DrawTexture(rect, Textures.TextureAlternateRow); + } + Rect innerRect = new Rect(rect.x + 3, rect.y + 3, rect.width - 6, rect.height - 6); + GUI.BeginGroup(innerRect); + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(current.Name); + GUI.color = ManualSaveTextColor; + Rect rect2 = new Rect(15, 0, innerRect.width, innerRect.height); + Text.Anchor = TextAnchor.MiddleLeft; + Text.Font = GameFont.Small; + Widgets.Label(rect2, fileNameWithoutExtension); + GUI.color = Color.white; + Rect rect3 = new Rect(250, 0, innerRect.width, innerRect.height); + Text.Font = GameFont.Tiny; + GUI.color = new Color(1, 1, 1, 0.5f); + Widgets.Label(rect3, current.LastWriteTime.ToString("g")); + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + Text.Font = GameFont.Small; + float num4 = vector.x - 6 - vector2.x - vector2.y; + Rect butRect = new Rect(num4, 0, vector2.x, vector2.y); + if (Widgets.ButtonText(butRect, this.interactButLabel, true, false, true)) { + this.DoMapEntryInteraction(Path.GetFileNameWithoutExtension(current.Name)); + } + Rect rect4 = new Rect(num4 + vector2.x + 5, 0, vector2.y, vector2.y); + if (Widgets.ButtonImage(rect4, Textures.TextureDeleteX)) { + FileInfo localFile = current; + Find.UIRoot.windows.Add(new Dialog_Confirm("EdB.ConfirmColonistDelete".Translate(new object[] { + localFile.Name + }), delegate { + localFile.Delete(); + }, true, null, true)); + } + TooltipHandler.TipRegion(rect4, "EdB.DeleteThisColonist".Translate()); + GUI.EndGroup(); + num2 += vector.y + 3; + num3++; + } + Widgets.EndScrollView(); + this.DoSpecialSaveLoadGUI(inRect.AtZero()); + } + } +} + diff --git a/Source/Dialog_LoadColonist.cs b/Source/Dialog_LoadColonist.cs new file mode 100644 index 0000000..1b46b9a --- /dev/null +++ b/Source/Dialog_LoadColonist.cs @@ -0,0 +1,27 @@ +using RimWorld; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Dialog_LoadColonist : Dialog_Colonist + { + public Dialog_LoadColonist() + { + this.interactButLabel = "EdB.DialogLoadColonistButton".Translate(); + } + + protected override void DoMapEntryInteraction(string colonistName) + { + bool result = ColonistLoader.LoadFromFile(PrepareCarefully.Instance, CharMakerPage, colonistName); + if (result) { + Messages.Message("EdB.LoadedColonist".Translate(new object[] { + colonistName + }), MessageSound.Standard); + } + Close(true); + } + } +} + diff --git a/Source/Dialog_LoadPreset.cs b/Source/Dialog_LoadPreset.cs new file mode 100644 index 0000000..de50b1a --- /dev/null +++ b/Source/Dialog_LoadPreset.cs @@ -0,0 +1,38 @@ +using RimWorld; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Dialog_LoadPreset : Dialog_Preset + { + public Dialog_LoadPreset() + { + this.interactButLabel = "EdB.LoadPresetButton".Translate(); + } + + protected override void DoMapEntryInteraction(string presetName) + { + bool result = PresetLoader.LoadFromFile(PrepareCarefully.Instance, presetName); + if (result) { + Messages.Message("EdB.LoadedPreset".Translate(new object[] { + presetName + }), MessageSound.Standard); + } + RemovePageFromStack(); + Close(true); + } + + protected void RemovePageFromStack() { + Window page = Find.WindowStack.WindowOfType(); + if (page == null) { + page = Find.WindowStack.WindowOfType(); + } + if (page != null) { + Find.WindowStack.TryRemove(page, true); + } + } + } +} + diff --git a/Source/Dialog_Options.cs b/Source/Dialog_Options.cs new file mode 100644 index 0000000..e66e73f --- /dev/null +++ b/Source/Dialog_Options.cs @@ -0,0 +1,235 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Dialog_Options : Window where T : class + { + protected Vector2 ContentMargin = new Vector2(10f, 18f); + protected Vector2 WindowSize = new Vector2(440f, 584f); + protected Vector2 ButtonSize = new Vector2(140f, 40f); + protected float HeaderHeight = 32; + protected Vector2 ContentSize; + protected float FooterHeight = 40f; + protected Rect ContentRect; + protected Rect ScrollRect; + protected Rect FooterRect; + protected Rect HeaderRect; + protected Rect CancelButtonRect; + protected Rect ConfirmButtonRect; + protected Rect SingleButtonRect; + protected ScrollView ScrollView = new ScrollView(); + protected IEnumerable options; + protected bool confirmButtonClicked = false; + protected float WindowPadding = 18; + + public bool IncludeNone = false; + + protected string headerLabel = null; + public string HeaderLabel { + get { + return headerLabel; + } + set { + headerLabel = value; + ComputeSizes(); + } + } + public string ConfirmButtonLabel = "EdB.PrepareCarefully.Close"; + + public string CancelButtonLabel = null; + + public Action Initialize = () => {}; + public Func NameFunc = (T) => { + return ""; + }; + public Func DescriptionFunc; + public Func SelectedFunc = (T) => { + return false; + }; + public Func EnabledFunc = (T) => { + return true; + }; + public Func ConfirmValidation = () => { + return null; + }; + public Action SelectAction = (T) => {}; + public Action CloseAction = () => {}; + + public Dialog_Options(IEnumerable options) + { + this.closeOnEscapeKey = true; + //this.doCloseButton = true; + this.doCloseX = true; + this.absorbInputAroundWindow = true; + this.forcePause = true; + + this.options = options; + + ComputeSizes(); + } + + public IEnumerable Options { + get { return options; } + set { options = value; } + } + + protected void ComputeSizes() + { + float headerSize = 0; + if (HeaderLabel != null) { + headerSize = HeaderHeight; + } + + ContentSize = new Vector2(WindowSize.x - WindowPadding * 2 - ContentMargin.x * 2, + WindowSize.y - WindowPadding * 2 - ContentMargin.y * 2 - FooterHeight - headerSize); + + ContentRect = new Rect(ContentMargin.x, ContentMargin.y + headerSize, ContentSize.x, ContentSize.y); + + ScrollRect = new Rect(0, 0, ContentRect.width, ContentRect.height); + + HeaderRect = new Rect(ContentMargin.x, ContentMargin.y, ContentSize.x, HeaderHeight); + + FooterRect = new Rect(ContentMargin.x, ContentRect.y + ContentSize.y + 20, + ContentSize.x, FooterHeight); + + SingleButtonRect = new Rect(ContentSize.x / 2 - ButtonSize.x / 2, + (FooterHeight / 2) - (ButtonSize.y / 2), + ButtonSize.x, ButtonSize.y); + + CancelButtonRect = new Rect(0, + (FooterHeight / 2) - (ButtonSize.y / 2), + ButtonSize.x, ButtonSize.y); + ConfirmButtonRect = new Rect(ContentSize.x - ButtonSize.x, + (FooterHeight / 2) - (ButtonSize.y / 2), + ButtonSize.x, ButtonSize.y); + + } + + + public override Vector2 InitialSize { + get { + return new Vector2(WindowSize.x, WindowSize.y); + } + } + + public override void DoWindowContents(Rect inRect) + { + GUI.color = Color.white; + if (HeaderLabel != null) { + Text.Font = GameFont.Medium; + Widgets.Label(HeaderRect, HeaderLabel.Translate()); + } + + Text.Font = GameFont.Small; + GUI.BeginGroup(ContentRect); + ScrollView.Begin(ScrollRect); + + float cursor = 0; + + if (IncludeNone) { + float height = Text.CalcHeight("EdB.None".Translate(), ContentSize.x - 32); + if (height < 30) { + height = 30; + } + bool isEnabled = EnabledFunc(null); + bool isSelected = SelectedFunc(null); + if (Widgets.RadioButtonLabeled(new Rect(0, cursor, ContentSize.x - 32, height), "EdB.None".Translate(), isSelected)) { + SelectAction(null); + } + cursor += height; + cursor += 2; + } + + Rect itemRect = new Rect(0, cursor, ContentSize.x - 32, 0); + foreach (T option in options) + { + string name = NameFunc(option); + bool selected = SelectedFunc(option); + bool enabled = EnabledFunc != null ? EnabledFunc(option) : true; + + float height = Text.CalcHeight(name, ContentSize.x - 32); + if (height < 30) { + height = 30; + } + itemRect.height = height; + Vector2 size = Text.CalcSize(name); + if (size.x > ContentSize.x - 32) { + size.x = ContentSize.x; + } + size.y = height; + + if (enabled) { + GUI.color = Color.white; + if (Widgets.RadioButtonLabeled(itemRect, name, selected)) { + SelectAction(option); + } + } + else { + GUI.color = new Color(0.65f, 0.65f, 0.65f); + Widgets.Label(new Rect(0, cursor + 2, ContentSize.x - 32, height), name); + Texture2D image = Textures.TextureRadioButtonOff; + Vector2 topLeft = new Vector2(itemRect.x + itemRect.width - 32, itemRect.y + itemRect.height / 2 - 16); + GUI.color = new Color(1, 1, 1, 0.28f); + GUI.DrawTexture(new Rect(topLeft.x, topLeft.y, 24, 24), image); + GUI.color = Color.white; + } + + if (DescriptionFunc != null) { + Rect tipRect = new Rect(itemRect.x, itemRect.y, size.x, size.y); + TooltipHandler.TipRegion(tipRect, DescriptionFunc(option)); + } + + cursor += height; + cursor += 2; + itemRect.y = cursor; + } + ScrollView.End(cursor); + GUI.EndGroup(); + GUI.color = Color.white; + + GUI.BeginGroup(FooterRect); + Rect buttonRect = SingleButtonRect; + if (CancelButtonLabel != null) { + if (Widgets.ButtonText(CancelButtonRect, CancelButtonLabel.Translate(), true, true, true)) { + this.Close(true); + } + buttonRect = ConfirmButtonRect; + } + if (Widgets.ButtonText(buttonRect, ConfirmButtonLabel.Translate(), true, true, true)) { + string validationMessage = ConfirmValidation(); + if (validationMessage != null) { + Messages.Message(validationMessage.Translate(), MessageSound.RejectInput); + } + else { + this.Confirm(); + } + } + GUI.EndGroup(); + } + + protected void Confirm() + { + confirmButtonClicked = true; + this.Close(true); + } + + public override void PostClose() + { + if (ConfirmButtonLabel != null) { + if (confirmButtonClicked && CloseAction != null) { + CloseAction(); + } + } + else { + if (CloseAction != null) { + CloseAction(); + } + } + } + } +} + diff --git a/Source/Dialog_Preset.cs b/Source/Dialog_Preset.cs new file mode 100644 index 0000000..834b3c2 --- /dev/null +++ b/Source/Dialog_Preset.cs @@ -0,0 +1,127 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public abstract class Dialog_Preset : Window + { + protected const float DeleteButtonSpace = 5; + protected const float MapDateExtraLeftMargin = 220; + + private static readonly Color ManualSaveTextColor = new Color(1, 1, 0.6f); + private static readonly Color AutosaveTextColor = new Color(0.75f, 0.75f, 0.75f); + + protected const float MapEntrySpacing = 8; + protected const float BoxMargin = 20; + protected const float MapNameExtraLeftMargin = 15; + protected const float MapEntryMargin = 6; + + private Vector2 scrollPosition = Vector2.zero; + + protected string interactButLabel = "Error"; + protected float bottomAreaHeight; + + public Dialog_Preset() + { + this.closeOnEscapeKey = true; + this.doCloseButton = true; + this.doCloseX = true; + this.absorbInputAroundWindow = true; + this.forcePause = true; + } + + public override Vector2 InitialSize { + get { + return new Vector2(600, 700); + } + } + + protected abstract void DoMapEntryInteraction(string mapName); + + protected virtual void DoSpecialSaveLoadGUI(Rect inRect) + { + } + + public override void PostClose() + { + Page_ConfigureStartingPawnsCarefully charPage = Find.WindowStack.WindowOfType(); + if (charPage != null) { + charPage.Show(); + } + else { + Page_Equipment equipmentPage = Find.WindowStack.WindowOfType(); + if (equipmentPage != null) { + equipmentPage.Show(); + } + else { + Find.WindowStack.Add(new Page_ConfigureStartingPawnsCarefully()); + } + } + GUI.FocusControl(null); + } + + public override void DoWindowContents(Rect inRect) + { + Vector2 vector = new Vector2(inRect.width - 16, 36); + Vector2 vector2 = new Vector2(100, vector.y - 6); + inRect.height -= 45; + List list = PresetFiles.AllFiles.ToList(); + float num = vector.y + 3; + float height = (float)list.Count * num; + Rect viewRect = new Rect(0, 0, inRect.width - 16, height); + Rect outRect = new Rect(inRect.AtZero()); + outRect.height -= this.bottomAreaHeight; + Widgets.BeginScrollView(outRect, ref this.scrollPosition, viewRect); + float num2 = 0; + int num3 = 0; + foreach (FileInfo current in list) { + Rect rect = new Rect(0, num2, vector.x, vector.y); + if (num3 % 2 == 0) { + GUI.DrawTexture(rect, Textures.TextureAlternateRow); + } + Rect innerRect = rect.ContractedBy(3); + GUI.BeginGroup(innerRect); + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(current.Name); + GUI.color = ManualSaveTextColor; + Rect rect2 = new Rect(15, 0, innerRect.width, innerRect.height); + Text.Anchor = TextAnchor.MiddleLeft; + Text.Font = GameFont.Small; + Widgets.Label(rect2, fileNameWithoutExtension); + GUI.color = Color.white; + Rect rect3 = new Rect(250, 0, innerRect.width, innerRect.height); + Text.Font = GameFont.Tiny; + GUI.color = new Color(1, 1, 1, 0.5f); + Widgets.Label(rect3, current.LastWriteTime.ToString("g")); + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + Text.Font = GameFont.Small; + float num4 = vector.x - 6 - vector2.x - vector2.y; + Rect butRect = new Rect(num4, 0, vector2.x, vector2.y); + if (Widgets.ButtonText(butRect, this.interactButLabel, true, false, true)) { + this.DoMapEntryInteraction(Path.GetFileNameWithoutExtension(current.Name)); + } + Rect rect4 = new Rect(num4 + vector2.x + 5, 0, vector2.y, vector2.y); + if (Widgets.ButtonImage(rect4, Textures.TextureDeleteX)) { + FileInfo localFile = current; + Find.UIRoot.windows.Add(new Dialog_Confirm("EdB.ConfirmPresetDelete".Translate(new object[] { + localFile.Name + }), delegate { + localFile.Delete(); + }, true, null, true)); + } + TooltipHandler.TipRegion(rect4, "EdB.DeleteThisPreset".Translate()); + GUI.EndGroup(); + num2 += vector.y + 3; + num3++; + } + Widgets.EndScrollView(); + this.DoSpecialSaveLoadGUI(inRect.AtZero()); + } + } +} + diff --git a/Source/Dialog_SaveColonist.cs b/Source/Dialog_SaveColonist.cs new file mode 100644 index 0000000..bc3eb22 --- /dev/null +++ b/Source/Dialog_SaveColonist.cs @@ -0,0 +1,69 @@ +using RimWorld; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Dialog_SaveColonist : Dialog_Colonist + { + protected const float NewColonistNameButtonSpace = 20; + protected const float NewColonistHeight = 35; + protected const float NewColonistNameWidth = 400; + protected static string Filename = ""; + + private bool focusedColonistNameArea; + + public Dialog_SaveColonist(CustomPawn pawn) + { + this.interactButLabel = "OverwriteButton".Translate(); + this.bottomAreaHeight = 85; + Filename = pawn.Pawn.LabelShort; + } + + protected override void DoMapEntryInteraction(string colonistName) + { + Filename = colonistName; + ColonistSaver.SaveToFile(PrepareCarefully.Instance, CharMakerPage, Filename); + Messages.Message("SavedAs".Translate(new object[] { + Filename + }), MessageSound.Standard); + Close(true); + } + + protected override void DoSpecialSaveLoadGUI(Rect inRect) + { + GUI.BeginGroup(inRect); + bool flag = Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return; + float top = inRect.height - 52; + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleLeft; + GUI.SetNextControlName("ColonistNameField"); + Rect rect = new Rect(5, top, 400, 35); + string text = Widgets.TextField(rect, Filename); + if (GenText.IsValidFilename(text)) { + Filename = text; + } + if (!this.focusedColonistNameArea) { + GUI.FocusControl("ColonistNameField"); + this.focusedColonistNameArea = true; + } + Rect butRect = new Rect(420, top, inRect.width - 400 - 20, 35); + if (Widgets.ButtonText(butRect, "EdB.DialogSaveColonistButton".Translate(), true, false, true) || flag) { + if (Filename.Length == 0) { + Messages.Message("NeedAName".Translate(), MessageSound.RejectInput); + } + else { + ColonistSaver.SaveToFile(PrepareCarefully.Instance, CharMakerPage, Filename); + Messages.Message("SavedAs".Translate(new object[] { + Filename + }), MessageSound.Standard); + Close(true); + } + } + Text.Anchor = TextAnchor.UpperLeft; + GUI.EndGroup(); + } + } +} + diff --git a/Source/Dialog_SavePreset.cs b/Source/Dialog_SavePreset.cs new file mode 100644 index 0000000..6e73f78 --- /dev/null +++ b/Source/Dialog_SavePreset.cs @@ -0,0 +1,70 @@ +using RimWorld; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Dialog_SavePreset : Dialog_Preset + { + protected const float NewPresetNameButtonSpace = 20; + protected const float NewPresetHeight = 35; + protected const float NewPresetNameWidth = 400; + + private bool focusedPresetNameArea; + + public Dialog_SavePreset() + { + this.interactButLabel = "OverwriteButton".Translate(); + this.bottomAreaHeight = 85; + if ("".Equals(PrepareCarefully.Instance.Filename)) { + PrepareCarefully.Instance.Filename = PresetFiles.UnusedDefaultName(); + } + } + + protected override void DoMapEntryInteraction(string MapName) + { + PrepareCarefully.Instance.Filename = MapName; + PresetSaver.SaveToFile(PrepareCarefully.Instance, PrepareCarefully.Instance.Filename); + Messages.Message("SavedAs".Translate(new object[] { + PrepareCarefully.Instance.Filename + }), MessageSound.Standard); + Close(true); + } + + protected override void DoSpecialSaveLoadGUI(Rect inRect) + { + GUI.BeginGroup(inRect); + bool flag = Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return; + float top = inRect.height - 52; + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleLeft; + GUI.SetNextControlName("PresetNameField"); + Rect rect = new Rect(5, top, 400, 35); + string text = Widgets.TextField(rect, PrepareCarefully.Instance.Filename); + if (GenText.IsValidFilename(text)) { + PrepareCarefully.Instance.Filename = text; + } + if (!this.focusedPresetNameArea) { + GUI.FocusControl("PresetNameField"); + this.focusedPresetNameArea = true; + } + Rect butRect = new Rect(420, top, inRect.width - 400 - 20, 35); + if (Widgets.ButtonText(butRect, "EdB.SavePresetButton".Translate(), true, false, true) || flag) { + if (PrepareCarefully.Instance.Filename.Length == 0) { + Messages.Message("NeedAName".Translate(), MessageSound.RejectInput); + } + else { + PresetSaver.SaveToFile(PrepareCarefully.Instance, PrepareCarefully.Instance.Filename); + Messages.Message("SavedAs".Translate(new object[] { + PrepareCarefully.Instance.Filename + }), MessageSound.Standard); + Close(true); + } + } + Text.Anchor = TextAnchor.UpperLeft; + GUI.EndGroup(); + } + } +} + diff --git a/Source/DragSlider.cs b/Source/DragSlider.cs new file mode 100644 index 0000000..3d429cf --- /dev/null +++ b/Source/DragSlider.cs @@ -0,0 +1,183 @@ +using RimWorld; +using System; +using System.Reflection; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace EdB.PrepareCarefully +{ + public delegate void DragSliderValueUpdateCallback(int value); + + public class DragSlider + { + private DragSliderCallback dragStartCallback; + private DragSliderCallback dragUpdateCallback; + private DragSliderCallback dragCompletedCallback; + protected static FieldInfo draggingField; + + public DragSliderValueUpdateCallback valueUpdateCallback = null; + + private float lastDragRealTime = -10000; + + private static readonly SoundDef DragStartSound = SoundDef.Named("TradeSlider_DragStart"); + + private static readonly SoundDef DragAmountChangedSound = SoundDef.Named("Drag_TradeSlider"); + + private static readonly SoundDef DragEndSound = SoundDef.Named("TradeSlider_DragEnd"); + + private const int DragTimeGrowMinimumOffset = 300; + + public bool dragLimitWarningGiven = false; + + public int dragBaseAmount; + + public float lastUpdateTime = 0; + + public float stretch = 0.4f; + public float scale = 15; + public int multiplier = 10; + public float maxDelta = 400; + public int maxValue = Int32.MaxValue; + public int minValue = 0; + + protected int value; + + public DragSlider() + { + dragStartCallback = new DragSliderCallback(this.DraggingStart); + dragUpdateCallback = new DragSliderCallback(this.DraggingUpdate); + dragCompletedCallback = new DragSliderCallback(this.DraggingCompleted); + } + + public DragSlider(float stretch, float scale, float maxDelta) : this() + { + this.stretch = stretch; + this.scale = scale; + this.maxDelta = maxDelta; + } + + public void Reset() + { + lastUpdateTime = 0; + } + + protected void Update() + { + lastUpdateTime = lastUpdateTime += Time.deltaTime; + } + + public void DraggingStart(float mouseOffX, float rateFactor) + { + SoundStarter.PlayOneShotOnCamera(DragStartSound); + } + + public void DraggingCompleted(float mouseOffX, float rateFactor) + { + SoundStarter.PlayOneShotOnCamera(DragEndSound); + valueUpdateCallback = null; + } + + public void DraggingUpdate(float mouseOffX, float rateFactor) + { + Update(); + + int amount = 0; + int direction = 1; + + if (mouseOffX != 0) { + float delta = Math.Abs(mouseOffX); + if (delta > maxDelta) { + delta = maxDelta; + } + delta = delta * stretch; + delta = scale / (delta * delta); + if (lastUpdateTime > delta) { + amount = 0; + while (lastUpdateTime > delta) { + lastUpdateTime -= delta; + amount++; + } + } + } + + if (mouseOffX < 0) { + direction = -1; + } + + AcceptanceReport acceptanceReport = null; + if (amount != 0) { + if (Event.current.shift) { + amount = amount * multiplier; + } + if (direction > 0) { + if (value < maxValue) { + if (maxValue - value < amount) { + value = maxValue; + } + else { + value += amount; + } + acceptanceReport = true; + } + else { + acceptanceReport = false; + } + } + else { + if (value > minValue) { + if (minValue + amount > value) { + value = minValue; + } + else { + value -= amount; + } + acceptanceReport = true; + } + else { + acceptanceReport = false; + } + } + if (acceptanceReport.Accepted) { + SoundStarter.PlayOneShotOnCamera(DragAmountChangedSound); + lastDragRealTime = Time.realtimeSinceStartup; + } + } + if (valueUpdateCallback != null) { + valueUpdateCallback(value); + } + } + + public void OnGUI(Rect rect, int value, DragSliderValueUpdateCallback valueUpdateCallback) + { + float centerX = rect.x + rect.width / 2; + float y = rect.y + rect.height / 2; + float height = rect.height; + Rect slRect = new Rect(rect); + + if (DragSliderManager.DragSlider(slRect, 1, dragStartCallback, dragUpdateCallback, dragCompletedCallback)) { + Reset(); + this.value = value; + dragBaseAmount = value; + dragLimitWarningGiven = false; + this.valueUpdateCallback = valueUpdateCallback; + } + + string label = value.ToString(); + GUI.color = Color.white; + Text.Anchor = TextAnchor.MiddleCenter; + rect.y += 2; + Widgets.Label(rect, label); + rect.y -= 2; + Text.Anchor = TextAnchor.UpperLeft; + GUI.color = Color.white; + } + + public static bool IsDragging() { + if (draggingField == null) { + draggingField = typeof(DragSliderManager).GetField("dragging", BindingFlags.Static | BindingFlags.NonPublic); + } + return (bool) draggingField.GetValue(null); + } + } +} diff --git a/Source/EquipmentDatabase.cs b/Source/EquipmentDatabase.cs new file mode 100644 index 0000000..cb8ff12 --- /dev/null +++ b/Source/EquipmentDatabase.cs @@ -0,0 +1,408 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class EquipmentDatabase + { + protected Dictionary entries = new Dictionary(); + + protected List resources = new List(); + protected List stuff = new List(); + protected CostCalculator costs = new CostCalculator(); + + public EquipmentDatabase() + { + foreach (ThingDef def in DefDatabase.AllDefs) { + if (def.IsStuff && def.stuffProps != null) { + stuff.Add(def); + } + } + BuildEquipmentLists(); + } + + public void BuildEquipmentLists() + { + foreach (var def in DefDatabase.AllDefs) { + + int type = ClassifyThingDef(def); + if (type != -1) { + AddThingDef(def, type); + } + /* + if (def.weaponTags != null && def.weaponTags.Count > 0) { + if (def.equipmentType != EquipmentType.None && !def.destroyOnDrop && def.canBeSpawningInventory) { + AddThingDef(def, EquipmentDatabaseEntry.TypeWeapon); + } + continue; + } + + if (def.apparel != null) { + if (!def.destroyOnDrop) { + AddThingDef(def, EquipmentDatabaseEntry.TypeApparel); + } + continue; + } + + if (def.CountAsResource) { + if (def.ingestible != null) { + AddThingDef(def, EquipmentDatabaseEntry.TypeFood); + continue; + } + + if ("AIPersonaCore".Equals(def.defName)) { + AddThingDef(def, EquipmentDatabaseEntry.TypeUncategorized); + continue; + } + if ("Neurotrainer".Equals(def.defName)) { + continue; + } + + AddThingDef(def, EquipmentDatabaseEntry.TypeResource); + continue; + } + + if (def.building != null) { + if (def.Minifiable) { + AddThingDef(def, EquipmentDatabaseEntry.TypeBuilding); + } + } + + if (def.isBodyPartOrImplant) { + AddThingDef(def, EquipmentDatabaseEntry.TypeImplant); + continue; + } + + if (def.race != null && def.race.Animal == true) { + AddThingDef(def, EquipmentDatabaseEntry.TypeAnimal); + } + */ + } + } + + public int ClassifyThingDef(ThingDef def) + { + if (def.weaponTags != null && def.weaponTags.Count > 0) { + if (def.equipmentType != EquipmentType.None && !def.destroyOnDrop && def.canBeSpawningInventory) { + return EquipmentDatabaseEntry.TypeWeapon; + } + } + + if (def.apparel != null) { + if (!def.destroyOnDrop) { + return EquipmentDatabaseEntry.TypeApparel; + } + } + + if (def.CountAsResource) { + if (def.ingestible != null) { + return EquipmentDatabaseEntry.TypeFood; + } + + if ("AIPersonaCore".Equals(def.defName)) { + return EquipmentDatabaseEntry.TypeUncategorized; + } + if ("Neurotrainer".Equals(def.defName)) { + return -1; + } + + return EquipmentDatabaseEntry.TypeResource; + } + + if (def.building != null) { + if (def.Minifiable) { + return EquipmentDatabaseEntry.TypeBuilding; + } + } + + if (def.isBodyPartOrImplant) { + return EquipmentDatabaseEntry.TypeImplant; + } + + if (def.race != null && def.race.Animal == true) { + return EquipmentDatabaseEntry.TypeAnimal; + } + + return -1; + } + + public List Resources { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeResource; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Food { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeFood; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Weapons { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeWeapon; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Apparel { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeApparel; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Animals { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeAnimal; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Implants { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeImplant; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Buildings { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeBuilding; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public List Other { + get { + List result = entries.Values.ToList().FindAll((EquipmentDatabaseEntry e) => { + return e.type == EquipmentDatabaseEntry.TypeUncategorized; + }); + result.Sort((EquipmentDatabaseEntry a, EquipmentDatabaseEntry b) => { + return a.Label.CompareTo(b.Label); + }); + return result; + } + } + + public EquipmentDatabaseEntry this[EquipmentKey key] { + get { + EquipmentDatabaseEntry result; + if (entries.TryGetValue(key, out result)) { + return result; + } + else { + return null; + } + } + } + + public EquipmentDatabaseEntry AddThingDefWithStuff(ThingDef def, ThingDef stuff, int type) + { + if (type == -1) { + Log.Warning("Prepare Carefully could not add unclassified equipment: " + def); + return null; + } + EquipmentKey key = new EquipmentKey(def, stuff); + EquipmentDatabaseEntry entry = CreateEquipmentEntry(def, stuff, type); + if (entry != null) { + entries[key] = entry; + } + return entry; + } + + protected void AddThingDef(ThingDef def, int type) { + + if (def.MadeFromStuff) { + foreach (var s in stuff) { + if (s.stuffProps.CanMake(def)) { + EquipmentKey key = new EquipmentKey(def, s); + EquipmentDatabaseEntry entry = CreateEquipmentEntry(def, s, type); + if (entry != null) { + entries[key] = entry; + } + } + } + } + else if (def.race != null && def.race.Animal) { + if (def.race.hasGenders) { + EquipmentDatabaseEntry femaleEntry = CreateEquipmentEntry(def, Gender.Female, type); + if (femaleEntry != null) { + entries[new EquipmentKey(def, Gender.Female)] = femaleEntry; + } + EquipmentDatabaseEntry maleEntry = CreateEquipmentEntry(def, Gender.Male, type); + if (maleEntry != null) { + entries[new EquipmentKey(def, Gender.Male)] = maleEntry; + } + + } + else { + EquipmentKey key = new EquipmentKey(def, Gender.None); + EquipmentDatabaseEntry entry = CreateEquipmentEntry(def, Gender.None, type); + if (entry != null) { + entries[key] = entry; + } + } + } + else { + EquipmentKey key = new EquipmentKey(def, null); + EquipmentDatabaseEntry entry = CreateEquipmentEntry(def, null, Gender.None, type); + if (entry != null) { + entries[key] = entry; + } + } + } + + protected EquipmentDatabaseEntry CreateEquipmentEntry(ThingDef def, ThingDef stuffDef, int type) + { + return CreateEquipmentEntry(def, stuffDef, Gender.None, type); + } + + protected EquipmentDatabaseEntry CreateEquipmentEntry(ThingDef def, Gender gender, int type) + { + return CreateEquipmentEntry(def, null, gender, type); + } + + protected EquipmentDatabaseEntry CreateEquipmentEntry(ThingDef def, ThingDef stuffDef, Gender gender, int type) + { + double baseCost = costs.GetBaseThingCost(def, stuffDef); + if (baseCost == 0) { + return null; + } + int stackSize = CalculateStackCount(def, baseCost); + + EquipmentDatabaseEntry result = new EquipmentDatabaseEntry(); + result.type = type; + result.def = def; + result.stuffDef = stuffDef; + result.stackSize = stackSize; + result.cost = costs.CalculateStackCost(def, stuffDef, baseCost); + result.stacks = true; + result.gear = false; + result.animal = false; + if (def.MadeFromStuff && stuffDef != null) { + if (stuffDef.stuffProps.allowColorGenerators && (def.colorGenerator != null || def.colorGeneratorInTraderStock != null)) { + if (def.colorGenerator != null) { + result.color = def.colorGenerator.NewRandomizedColor(); + } + else if (def.colorGeneratorInTraderStock != null) { + result.color = def.colorGeneratorInTraderStock.NewRandomizedColor(); + } + } + else { + result.color = stuffDef.stuffProps.color; + } + } + else { + if (def.graphicData != null) { + result.color = def.graphicData.color; + } + else { + result.color = Color.white; + } + } + if (def.apparel != null) { + result.stacks = false; + result.gear = true; + } + if (def.weaponTags != null && def.weaponTags.Count > 0) { + result.stacks = false; + result.gear = true; + } + + if (def.thingCategories != null) { + if (def.thingCategories.SingleOrDefault((ThingCategoryDef d) => { + return (d.defName == "FoodMeals"); + }) != null) { + result.gear = true; + } + if (def.thingCategories.SingleOrDefault((ThingCategoryDef d) => { + return (d.defName == "Medicine"); + }) != null) { + result.gear = true; + } + } + + if (def.defName == "Apparel_PersonalShield") { + result.hideFromPortrait = true; + } + + if (def.race != null && def.race.Animal) { + result.animal = true; + result.gender = gender; + Pawn pawn = CreatePawn(def, stuffDef, gender); + if (pawn == null) { + return null; + } + else { + result.thing = pawn; + } + } + + return result; + } + + + public int CalculateStackCount(ThingDef def, double basePrice) + { + return 1; + } + + public Pawn CreatePawn(ThingDef def, ThingDef stuffDef, Gender gender) + { + PawnKindDef kindDef = (from td in DefDatabase.AllDefs + where td.race == def + select td).FirstOrDefault(); + if (kindDef != null) { + Pawn pawn = PawnGenerator.GeneratePawn(kindDef, null); + pawn.gender = gender; + pawn.Drawer.renderer.graphics.ResolveAllGraphics(); + return pawn; + } + else { + return null; + } + } + } +} diff --git a/Source/EquipmentDatabaseEntry.cs b/Source/EquipmentDatabaseEntry.cs new file mode 100644 index 0000000..6cff8a0 --- /dev/null +++ b/Source/EquipmentDatabaseEntry.cs @@ -0,0 +1,96 @@ +using RimWorld; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class EquipmentDatabaseEntry + { + public static readonly int TypeUncategorized = 0; + public static readonly int TypeResource = 1; + public static readonly int TypeFood = 2; + public static readonly int TypeWeapon = 3; + public static readonly int TypeApparel = 4; + public static readonly int TypeAnimal = 5; + public static readonly int TypeImplant = 6; + public static readonly int TypeBuilding = 7; + + public ThingDef def; + public ThingDef stuffDef = null; + public Gender gender = Gender.None; + public Thing thing = null; + public int type; + public int stackSize; + public double cost = 0; + public Color color = Color.white; + public bool stacks = true; + public bool gear = false; + public bool animal = false; + protected string label = null; + public bool hideFromPortrait = false; + + public bool Minifiable { + get { + return def.Minifiable && def.building != null; + } + } + + public string Label { + get { + if (label == null) { + if (thing != null && animal == true) { + return LabelForAnimal; + } + else { + return GenLabel.ThingLabel(def, stuffDef, stackSize).CapitalizeFirst(); + } + } + else { + return label; + } + } + } + + public string LabelNoCount { + get { + if (label == null) { + if (thing != null && animal == true) { + return LabelForAnimal; + } + else { + return GenLabel.ThingLabel(def, stuffDef, 1).CapitalizeFirst(); + } + } + else { + return label; + } + } + } + + public string LabelForAnimal { + get { + Pawn pawn = thing as Pawn; + return "PawnMainDescGendered".Translate(new object[] { + pawn.gender.GetLabel(), + pawn.kindDef.label + }).CapitalizeFirst(); + } + } + + public EquipmentKey EquipmentKey { + get { + return new EquipmentKey(def, stuffDef, gender); + } + } + + public override string ToString() + { + return string.Format("[EquipmentDatabaseEntry: def = {0}, stuffDef = {1}]", (def != null ? def.defName : "null"), (stuffDef != null ? stuffDef.defName : "null")); + } + } + + + +} + diff --git a/Source/EquipmentKey.cs b/Source/EquipmentKey.cs new file mode 100644 index 0000000..dbe889e --- /dev/null +++ b/Source/EquipmentKey.cs @@ -0,0 +1,50 @@ +using RimWorld; +using System; +using Verse; + +namespace EdB.PrepareCarefully +{ + public struct EquipmentKey { + public ThingDef thingDef; + public ThingDef stuffDef; + public Gender gender; + public EquipmentKey(ThingDef thingDef, ThingDef stuffDef, Gender gender) { + this.thingDef = thingDef; + this.stuffDef = stuffDef; + this.gender = gender; + } + public EquipmentKey(ThingDef thingDef, ThingDef stuffDef) { + this.thingDef = thingDef; + this.stuffDef = stuffDef; + this.gender = Gender.None; + } + public EquipmentKey(ThingDef thingDef) { + this.thingDef = thingDef; + this.stuffDef = null; + this.gender = Gender.None; + } + public EquipmentKey(ThingDef thingDef, Gender gender) { + this.thingDef = thingDef; + this.stuffDef = null; + this.gender = gender; + } + public override bool Equals(System.Object o) { + if (o == null) { + return false; + } + if (!(o is EquipmentKey)) { + return false; + } + EquipmentKey pair = (EquipmentKey) o; + return (thingDef == pair.thingDef && stuffDef == pair.stuffDef); + } + public override int GetHashCode() { + unchecked { + int a = thingDef != null ? thingDef.GetHashCode() : 0; + int b = stuffDef != null ? stuffDef.GetHashCode() : 0; + return (31 * a + b) * 31 + gender.GetHashCode(); + } + } + } +} + diff --git a/Source/Genstep_Colonists.cs b/Source/Genstep_Colonists.cs new file mode 100644 index 0000000..c97e8d6 --- /dev/null +++ b/Source/Genstep_Colonists.cs @@ -0,0 +1,295 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + // TODO: Alpha 14 + // This is the Alpha 13 map generation step that spawns the colonists into the map, along with + // their equipment. It is no longer needed but is left for reference. + public class Genstep_Colonists : Genstep + { + public Genstep_Colonists() + { + } + + public override void Generate() + { + if (PrepareCarefully.Instance.Active && PrepareCarefully.Instance.Colonists != null + && PrepareCarefully.Instance.Colonists.Count > 0) + { + GeneratePrepareCarefullyColonistsAndEquipment(); + } + else { + RimWorld.GenStep_ScenParts genstep = new RimWorld.GenStep_ScenParts(); + genstep.Generate(); + } + } + + // A copy of RimWorld.Genstep_GenerateColonists.Generate() with modifications. + public void GeneratePrepareCarefullyColonistsAndEquipment() + { + // EdB: Copy our colonists into the Find.GameInitData. + Find.GameInitData.startingPawns = PrepareCarefully.Instance.Colonists; + + foreach (Pawn current in Find.GameInitData.startingPawns) { + current.SetFactionDirect(Faction.OfPlayer); + PawnComponentsUtility.AddAndRemoveDynamicComponents(current, false); + // TODO: Alpha 14 + /* + current.needs.mood.thoughts.TryGainThought(ThoughtDefOf.NewColonyOptimism); + foreach (Pawn current2 in Find.GameInitData.startingPawns) { + if (current2 != current) { + Thought_SocialMemory thought_SocialMemory = (Thought_SocialMemory)ThoughtMaker.MakeThought(ThoughtDefOf.CrashedTogether); + thought_SocialMemory.SetOtherPawn(current2); + current.needs.mood.thoughts.TryGainThought(thought_SocialMemory); + } + } + */ + } + + // EdB: Call our modified version of the work settings methods. + //Genstep_Colonists.CreateInitialWorkSettings(); + CreateInitialWorkSettings(); + + bool startedDirectInEditor = Find.GameInitData.QuickStarted; + List> list = new List>(); + foreach (Pawn current3 in Find.GameInitData.startingPawns) { + if (Find.GameInitData.startedFromEntry && Rand.Value < 0.5) { + current3.health.AddHediff(HediffDefOf.CryptosleepSickness, null, null); + } + // EdB: Don't give the default equipment to the colonists + //List list2 = new List(); + //list2.Add(current3); + //Thing thing = ThingMaker.MakeThing(ThingDefOf.MealSurvivalPack, null); + //thing.stackCount = 10; + //list2.Add(thing); + //Thing thing2 = ThingMaker.MakeThing(ThingDefOf.Medicine, null); + //thing2.stackCount = 8; + //list2.Add(thing2); + //list.Add(list2); + } + // EdB: Do not add the default weapons and pet. + //List list3 = new List { + // ThingMaker.MakeThing(ThingDefOf.Gun_SurvivalRifle, null), + // ThingMaker.MakeThing(ThingDefOf.Gun_Pistol, null), + // ThingMaker.MakeThing(ThingDefOf.MeleeWeapon_Knife, ThingDefOf.Plasteel), + // Genstep_Colonists.GenerateRandomPet() + //}; + // EdB: Don't try to assign the default equipment to the colony here. + //int num = 0; + //foreach (Thing current4 in list3) { + // current4.SetFactionDirect(Faction.OfPlayer); + // list[num].Add(current4); + // num++; + // if (num >= list.Count) { + // num = 0; + // } + //} + // EdB: Don't do the default drop pod setup. + //bool canInstaDropDuringInit = startedDirectInEditor; + //DropPodUtility.DropThingGroupsNear(MapGenerator.PlayerStartSpot, list, 110, canInstaDropDuringInit, true, true); + + // EdB: We add our custom steps at the end. + // EdB: Add injuries and custom body modifications. + int index = 0; + foreach (Pawn pawn in Find.GameInitData.startingPawns) { + pawn.health = new Pawn_HealthTracker(pawn); + CustomPawn customPawn = PrepareCarefully.Instance.Pawns[index++]; + if (customPawn.RandomInjuries) { + AgeInjuryUtility.GenerateRandomOldAgeInjuries(pawn, true); + } + } + for (int i = 0; i < PrepareCarefully.Instance.Pawns.Count; i++) { + CustomPawn customPawn = PrepareCarefully.Instance.Pawns[i]; + Pawn pawn = Find.GameInitData.startingPawns[i]; + foreach (Injury injury in customPawn.Injuries) { + injury.AddToPawn(customPawn, pawn); + } + foreach (Implant implant in customPawn.Implants) { + implant.AddToPawn(customPawn, pawn); + } + } + + // EdB: Prepare the custom inventory in the drop pods. + List> pods = new List>(); + for (int i = 0; i < Find.GameInitData.startingPawns.Count; i++) { + pods.Add(new List()); + pods[i].Add(Find.GameInitData.startingPawns[i]); + } + int pawnCount = pods.Count; + + List weapons = new List(); + List food = new List(); + List apparel = new List(); + List animals = new List(); + List other = new List(); + + int maxStack = 50; + foreach (var e in PrepareCarefully.Instance.Equipment) { + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[e.EquipmentKey]; + if (entry == null) { + string thing = e.def != null ? e.def.defName : "null"; + string stuff = e.stuffDef != null ? e.stuffDef.defName : "null"; + Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); + continue; + } + if (entry.gear) { + int count = e.Count; + int idealStackCount = count / pawnCount; + while (count > 0) { + int stackCount = idealStackCount; + if (stackCount < 1) { + stackCount = 1; + } + if (stackCount > entry.def.stackLimit) { + stackCount = entry.def.stackLimit; + } + if (stackCount > maxStack) { + stackCount = maxStack; + } + if (stackCount > count) { + stackCount = count; + } + + Thing thing = null; + if (entry.def.MadeFromStuff && entry.stuffDef == null) { + if (entry.def.apparel != null) { + thing = ThingMaker.MakeThing(entry.def, ThingDef.Named("Synthread")); + } + else { + Log.Warning("Could not add item. Item is \"made from stuff\" but no material was specified and there is no known default."); + } + } + else { + thing = ThingMaker.MakeThing(entry.def, entry.stuffDef); + } + + if (thing != null) { + thing.stackCount = stackCount; + count -= stackCount; + + if (entry.def.weaponTags != null && entry.def.weaponTags.Count > 0) { + thing.SetFactionDirect(Faction.OfPlayer); + weapons.Add(thing); + } + else if (entry.def.apparel != null) { + apparel.Add(thing); + } + else if (entry.def.ingestible != null) { + food.Add(thing); + } + else { + other.Add(thing); + } + } + } + } + else if (entry.animal) { + int count = e.Count; + for (int i = 0; i < count; i++) { + Thing animal = CreateAnimal(entry); + if (animal != null) { + animals.Add(animal); + } + } + } + } + + List combined = new List(); + combined.AddRange(weapons); + combined.AddRange(food); + combined.AddRange(apparel); + combined.AddRange(animals); + combined.AddRange(other); + + int pod = 0; + foreach (Thing thing in combined) { + pods[pod].Add(thing); + if (++pod >= pawnCount) { + pod = 0; + } + } + + // EdB: Deploy the drop pods. + bool canInstaDropDuringInit = startedDirectInEditor; + DropPodUtility.DropThingGroupsNear(MapGenerator.PlayerStartSpot, pods, 110, canInstaDropDuringInit, true, false); + } + + private static Thing CreateAnimal(EquipmentDatabaseEntry entry) + { + ThingDef def = entry.def; + PawnKindDef kindDef = (from td in DefDatabase.AllDefs + where td.race == def + select td).FirstOrDefault(); + if (kindDef != null) { + Pawn pawn = PawnGenerator.GeneratePawn(kindDef, Faction.OfPlayer); + pawn.gender = entry.gender; + if (kindDef.RaceProps.petness > 0) { + if (pawn.Name == null || pawn.Name.Numerical) { + pawn.Name = NameGenerator.GeneratePawnName(pawn, NameStyle.Full, null); + Pawn pawn2 = PrepareCarefully.Instance.Colonists.RandomElement(); + pawn2.relations.AddDirectRelation(PawnRelationDefOf.Bond, pawn); + } + } + else { + pawn.Name = null; + } + return pawn; + } + else { + return null; + } + } + + public void AddImplantToPawn(Pawn pawn, Implant implant) + { + pawn.health.AddHediff(implant.recipe.addsHediff, implant.BodyPartRecord, new DamageInfo?()); + } + + // EdB: Copy of RimWold.Genstep_GenerateColonists.CreateInitialWorkSettings(), but with error + // messages removed. + private static void CreateInitialWorkSettings() + { + foreach (Pawn current in Find.GameInitData.startingPawns) { + current.workSettings.DisableAll(); + } + foreach (WorkTypeDef w in DefDatabase.AllDefs) { + if (w.alwaysStartActive) { + foreach (Pawn current2 in from col in Find.GameInitData.startingPawns + where !col.story.WorkTypeIsDisabled(w) + select col) { + current2.workSettings.SetPriority(w, 3); + } + } + else { + bool flag = false; + foreach (Pawn current3 in Find.GameInitData.startingPawns) { + if (!current3.story.WorkTypeIsDisabled(w) && current3.skills.AverageOfRelevantSkillsFor(w) >= 6) { + current3.workSettings.SetPriority(w, 3); + flag = true; + } + } + if (!flag) { + IEnumerable source = from col in Find.GameInitData.startingPawns + where !col.story.WorkTypeIsDisabled(w) + select col; + if (source.Any()) { + Pawn pawn = source.InRandomOrder(null).MaxBy((Pawn c) => c.skills.AverageOfRelevantSkillsFor(w)); + pawn.workSettings.SetPriority(w, 3); + } + else if (w.requireCapableColonist) { + // EdB: Show warning instead of an error. + //Log.Error("No colonist could do requireCapableColonist work type " + w); + Log.Warning("No colonist can do what is thought to be a required work type " + w.gerundLabel); + } + } + } + } + } + } +} + diff --git a/Source/Genstep_ScenParts.cs b/Source/Genstep_ScenParts.cs new file mode 100644 index 0000000..bc94f88 --- /dev/null +++ b/Source/Genstep_ScenParts.cs @@ -0,0 +1,283 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + // Alternate map generation to include customized pawn, equipment and resources. + public class Genstep_ScenParts : Genstep + { + public Genstep_ScenParts() + { + } + + public override void Generate() + { + if (!PrepareCarefully.Instance.Active) { + Find.Scenario.GenerateIntoMap(); + return; + } + else { + ReplaceColonists(); + + // TODO: Alpha 14 + // Do all of the scenario steps except the ones that place equipment and resources. + // Skip those an figure out how to add customized equipment. Need to separate equipment + // placed with colonists ("equipment") and equipment scattered near colonists ("resources").\ + ScenPart_PlayerPawnsArriveMethod arriveMethodPart = null; + foreach (ScenPart current in Find.Scenario.AllParts) { + ScenPart_StartingThing_Defined startingThings = current as ScenPart_StartingThing_Defined; + ScenPart_ScatterThingsNearPlayerStart thingsNearStart = current as ScenPart_ScatterThingsNearPlayerStart; + ScenPart_StartingAnimal animal = current as ScenPart_StartingAnimal; + ScenPart_PlayerPawnsArriveMethod arriveMethod = current as ScenPart_PlayerPawnsArriveMethod; + if (arriveMethod != null) { + arriveMethodPart = arriveMethod; + } + if (startingThings == null && thingsNearStart == null && animal == null && arriveMethod == null) { + current.GenerateIntoMap(); + } + } + + SpawnColonistsWithEquipment(arriveMethodPart); + ApplyColonistHealthCustomizations(); + SpawnStartingResources(); + } + } + + // Copy of ScenPart_PlayerPawnsArriveMethod.GenerateIntoMap(), but with changes to spawn custom + // equipment. + public void SpawnColonistsWithEquipment(ScenPart_PlayerPawnsArriveMethod arriveMethodPart) + { + List> list = new List>(); + foreach (Pawn current in Find.GameInitData.startingPawns) { + list.Add(new List { + current + }); + } + List list2 = new List(); + foreach (ScenPart current2 in Find.Scenario.AllParts) { + ScenPart_StartingThing_Defined startingThings = current2 as ScenPart_StartingThing_Defined; + ScenPart_StartingAnimal animal = current2 as ScenPart_StartingAnimal; + if (startingThings == null && animal == null) { + list2.AddRange(current2.PlayerStartingThings()); + } + } + + int num = 0; + foreach (Thing current3 in list2) { + if (current3.def.CanHaveFaction) { + current3.SetFactionDirect(Faction.OfPlayer); + } + list[num].Add(current3); + num++; + if (num >= list.Count) { + num = 0; + } + } + + + // Spawn custom equipment + List weapons = new List(); + List food = new List(); + List apparel = new List(); + List animals = new List(); + List other = new List(); + + int maxStack = 50; + foreach (var e in PrepareCarefully.Instance.Equipment) { + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[e.EquipmentKey]; + if (entry == null) { + string thing = e.def != null ? e.def.defName : "null"; + string stuff = e.stuffDef != null ? e.stuffDef.defName : "null"; + Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); + continue; + } + if (entry.gear) { + int count = e.Count; + int idealStackCount = count / list.Count; + while (count > 0) { + int stackCount = idealStackCount; + if (stackCount < 1) { + stackCount = 1; + } + if (stackCount > entry.def.stackLimit) { + stackCount = entry.def.stackLimit; + } + if (stackCount > maxStack) { + stackCount = maxStack; + } + if (stackCount > count) { + stackCount = count; + } + + Thing thing = null; + if (entry.def.MadeFromStuff && entry.stuffDef == null) { + if (entry.def.apparel != null) { + thing = ThingMaker.MakeThing(entry.def, ThingDef.Named("Synthread")); + } + else { + Log.Warning("Could not add item. Item is \"made from stuff\" but no material was specified and there is no known default."); + } + } + else { + thing = ThingMaker.MakeThing(entry.def, entry.stuffDef); + } + + if (thing != null) { + thing.stackCount = stackCount; + count -= stackCount; + + if (entry.def.weaponTags != null && entry.def.weaponTags.Count > 0) { + weapons.Add(thing); + } + else if (entry.def.apparel != null) { + apparel.Add(thing); + } + else if (entry.def.ingestible != null) { + food.Add(thing); + } + else { + other.Add(thing); + } + } + } + } + else if (entry.animal) { + int count = e.Count; + for (int i = 0; i < count; i++) { + Thing animal = CreateAnimal(entry); + if (animal != null) { + animals.Add(animal); + } + } + } + } + + List combined = new List(); + combined.AddRange(weapons); + combined.AddRange(food); + combined.AddRange(apparel); + combined.AddRange(animals); + combined.AddRange(other); + + num = 0; + foreach (Thing thing in combined) { + if (thing.def.CanHaveFaction) { + thing.SetFactionDirect(Faction.OfPlayer); + } + list[num].Add(thing); + num++; + if (num >= list.Count) { + num = 0; + } + } + + // Get the arrive method from the scenario part. + PlayerPawnsArriveMethod arriveMethod = PlayerPawnsArriveMethod.DropPods; + if (arriveMethodPart != null) { + arriveMethod = (PlayerPawnsArriveMethod)typeof(ScenPart_PlayerPawnsArriveMethod).GetField("method", + BindingFlags.NonPublic | BindingFlags.Instance).GetValue(arriveMethodPart); + } + + bool instaDrop = Find.GameInitData.QuickStarted || arriveMethod != PlayerPawnsArriveMethod.DropPods; + DropPodUtility.DropThingGroupsNear(MapGenerator.PlayerStartSpot, list, 110, instaDrop, true, true); + } + + private Thing CreateAnimal(EquipmentDatabaseEntry entry) + { + ThingDef def = entry.def; + PawnKindDef kindDef = (from td in DefDatabase.AllDefs + where td.race == def + select td).FirstOrDefault(); + if (kindDef != null) { + Pawn pawn = PawnGenerator.GeneratePawn(kindDef, Faction.OfPlayer); + pawn.gender = entry.gender; + if (pawn.Name == null || pawn.Name.Numerical) { + pawn.Name = NameGenerator.GeneratePawnName(pawn, NameStyle.Full, null); + } + if (kindDef.RaceProps.petness > 0) { + Pawn bondedColonist = Find.GameInitData.startingPawns.RandomElement(); + bondedColonist.relations.AddDirectRelation(PawnRelationDefOf.Bond, pawn); + } + return pawn; + } + else { + return null; + } + } + + // Replace colonists in the "GameInitData" with the customized colonists and do any necessary + // pawn setup that we might have skipped when creating the custom colonists. + public void ReplaceColonists() + { + Find.GameInitData.startingPawns = PrepareCarefully.Instance.Colonists; + // TODO: Alpha 14 + // Used to do this in Alpha 13. Still necessary? + foreach (Pawn current in Find.GameInitData.startingPawns) { + PawnComponentsUtility.AddAndRemoveDynamicComponents(current, false); + } + } + + // Add health customizations. + public void ApplyColonistHealthCustomizations() + { + int index = 0; + foreach (Pawn pawn in Find.GameInitData.startingPawns) { + pawn.health = new Pawn_HealthTracker(pawn); + CustomPawn customPawn = PrepareCarefully.Instance.Pawns[index++]; + if (customPawn.RandomInjuries) { + AgeInjuryUtility.GenerateRandomOldAgeInjuries(pawn, true); + } + } + for (int i = 0; i < PrepareCarefully.Instance.Pawns.Count; i++) { + CustomPawn customPawn = PrepareCarefully.Instance.Pawns[i]; + Pawn pawn = Find.GameInitData.startingPawns[i]; + foreach (Injury injury in customPawn.Injuries) { + injury.AddToPawn(customPawn, pawn); + } + foreach (Implant implant in customPawn.Implants) { + implant.AddToPawn(customPawn, pawn); + } + } + } + + public void SpawnStartingResources() + { + foreach (var e in PrepareCarefully.Instance.Equipment) { + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[e.EquipmentKey]; + if (entry == null) { + string thing = e.def != null ? e.def.defName : "null"; + string stuff = e.stuffDef != null ? e.stuffDef.defName : "null"; + Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); + continue; + } + if (!entry.gear && !entry.animal) { + int stackSize = entry.def.stackLimit; + if (stackSize > 75) { + stackSize = 75; + } + if (entry.def == ThingDefOf.Component && e.Count <= 100) { + stackSize = 10; + } + int stacks = e.count / stackSize; + int remainder = e.count % stackSize; + //Log.Message("Scatter " + e.def.defName + ": " + stacks + " stacks of " + stackSize + " + " + remainder); + + new Genstep_ScatterThings { + nearPlayerStart = true, + thingDef = e.def, + stuff = e.stuffDef, + clusterSize = stackSize, + count = e.Count, + spotMustBeStandable = true, + minSpacing = 5f + }.Generate(); + } + } + } + } +} + diff --git a/Source/Genstep_SpawnStartingResources.cs b/Source/Genstep_SpawnStartingResources.cs new file mode 100644 index 0000000..00bed6e --- /dev/null +++ b/Source/Genstep_SpawnStartingResources.cs @@ -0,0 +1,144 @@ +using RimWorld; +using System; +using Verse; + +namespace EdB.PrepareCarefully +{ + // TODO: Alpha 14 + // This is the Alpha 13 map generation step that scatters custom resources on the map, near the + // starting location of the colonists. It is no longer needed but is left for reference. + public class Genstep_SpawnStartingResources : Genstep + { + public Genstep_SpawnStartingResources() + { + } + + public override void Generate() + { + if (PrepareCarefully.Instance.Active) { + GeneratePrepareCarefullyResources(); + } + else { + GenerateStandardResources(); + } + + } + + public void GeneratePrepareCarefullyResources() + { + foreach (var e in PrepareCarefully.Instance.Equipment) { + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[e.EquipmentKey]; + if (entry == null) { + string thing = e.def != null ? e.def.defName : "null"; + string stuff = e.stuffDef != null ? e.stuffDef.defName : "null"; + Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); + continue; + } + if (!entry.gear && !entry.animal && !entry.Minifiable) { + int stackSize = entry.def.stackLimit; + if (stackSize > 75) { + stackSize = 75; + } + if (entry.def == ThingDefOf.Component && e.Count <= 100) { + stackSize = 10; + } + int stacks = e.count / stackSize; + int remainder = e.count % stackSize; + //Log.Message("Scatter " + e.def.defName + ": " + stacks + " stacks of " + stackSize + " + " + remainder); + + // TODO: Alpha 14 + /* + new Genstep_ScatterThingGroups { + thingDefs = { + e.def + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(stacks, stacks), + stackCountRange = new IntRange(stackSize, stackSize), + countAtPlayerStart = 1 + }.Generate(); + if (remainder > 0) { + new Genstep_ScatterThingGroups { + thingDefs = { + e.def + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(1, 1), + stackCountRange = new IntRange(remainder, remainder), + countAtPlayerStart = 1 + }.Generate(); + } + */ + } + if (entry.Minifiable) { + for (int i = 0; i < e.Count; i++) { + /* + new Genstep_ScatterBuildings { + thingDefs = { + e.def + }, + stuffDef = e.stuffDef, + spotMustBeStandable = true, + // TODO: Alpha 14 + //countAtPlayerStart = 1 + }.Generate(); + */ + } + } + } + } + + public void GenerateStandardResources() + { + // TODO: Alpha 14 + /* + new Genstep_ScatterThingGroups { + thingDefs = { + ThingDefOf.Steel + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(6, 6), + stackCountRange = new IntRange(75, 75), + countAtPlayerStart = 1 + }.Generate(); + new Genstep_ScatterThingGroups { + thingDefs = { + ThingDefOf.WoodLog + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(6, 6), + stackCountRange = new IntRange(40, 60), + countAtPlayerStart = 1 + }.Generate(); + new Genstep_ScatterThingGroups { + thingDefs = { + ThingDefOf.Silver + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(5, 5), + stackCountRange = new IntRange(40, 60), + countAtPlayerStart = 2 + }.Generate(); + new Genstep_ScatterThingGroups { + thingDefs = { + ThingDefOf.Silver + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(3, 3), + stackCountRange = new IntRange(40, 60), + countAtPlayerStart = 2 + }.Generate(); + new Genstep_ScatterThingGroups { + thingDefs = { + ThingDefOf.Component + }, + spotMustBeStandable = true, + groupSizeRange = new IntRange(3, 3), + stackCountRange = new IntRange(10, 10), + countAtPlayerStart = 1 + }.Generate(); + */ + } + } +} + diff --git a/Source/GraphicsCache.cs b/Source/GraphicsCache.cs new file mode 100644 index 0000000..9bbc6ba --- /dev/null +++ b/Source/GraphicsCache.cs @@ -0,0 +1,130 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class GraphicsCache + { + static protected GraphicsCache instance; + + static public GraphicsCache Instance { + get { + if (instance == null) { + instance = new GraphicsCache(); + } + return instance; + } + } + + protected Dictionary> bodyTypes = new Dictionary>(); + Dictionary hairs = new Dictionary(); + protected List heads = new List(); + protected List headPaths = new List(); + protected Dictionary things = new Dictionary(); + + public GraphicsCache() + { + InitializeHeads(); + } + + protected void InitializeHeads() + { + MethodInfo headGraphicsMethod = typeof(GraphicDatabaseHeadRecords).GetMethod("BuildDatabaseIfNecessary", BindingFlags.Static | BindingFlags.NonPublic); + headGraphicsMethod.Invoke(null, null); + + string[] headsFolderPaths = new string[] { + "Things/Pawn/Humanlike/Heads/Male", + "Things/Pawn/Humanlike/Heads/Female" + }; + for (int i = 0; i < headsFolderPaths.Length; i++) { + string text = headsFolderPaths[i]; + foreach (string current in GraphicDatabaseUtility.GraphicNamesInFolder(text)) { + string headPath = text + "/" + current; + headPaths.Add(headPath); + } + } + } + + public Graphic_Multi GetHead(string path) + { + return GraphicDatabaseHeadRecords.GetHeadNamed(path, Color.white); + } + + public List MaleHeadPaths { + get { + return headPaths.FindAll((string path) => { return !path.Contains("Female"); }); + } + } + + public List FemaleHeadPaths { + get { + return headPaths.FindAll((string path) => { return !path.Contains("Male"); }); + } + } + + public Graphic GetHair(HairDef def) + { + if (hairs.ContainsKey(def)) { + return hairs[def]; + } + else { + Graphic graphic = CreateGraphic(def); + if (graphic != null) { + hairs[def] = graphic; + } + return graphic; + } + } + + public Graphic GetApparel(ThingDef def, BodyType bodyType) + { + Dictionary lookup; + if (!bodyTypes.TryGetValue(bodyType, out lookup)) { + lookup = new Dictionary(); + bodyTypes[bodyType] = lookup; + } + + if (lookup.ContainsKey(def)) { + return lookup[def]; + } + else { + Graphic graphic = CreateGraphic(def, bodyType); + if (graphic != null) { + lookup[def] = graphic; + } + return graphic; + } + } + + protected Graphic CreateGraphic(HairDef def) + { + if (def.texPath != null) { + return GraphicDatabase.Get(def.texPath, ShaderDatabase.Cutout, new Vector2(38, 38), Color.white, Color.white); + } + return null; + } + + protected Graphic CreateGraphic(ThingDef def, BodyType bodyType) + { + if (def.apparel != null) { + if (String.IsNullOrEmpty(def.apparel.wornGraphicPath)) { + return null; + } + string graphicPath; + if (def.apparel.LastLayer == ApparelLayer.Overhead) { + graphicPath = def.apparel.wornGraphicPath; + } + else { + graphicPath = def.apparel.wornGraphicPath + "_" + bodyType.ToString(); + } + return GraphicDatabase.Get(graphicPath, ShaderDatabase.Cutout, new Vector2(38, 38), Color.white, Color.white); + } + return null; + } + } +} + diff --git a/Source/HealthManager.cs b/Source/HealthManager.cs new file mode 100644 index 0000000..bced63d --- /dev/null +++ b/Source/HealthManager.cs @@ -0,0 +1,84 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class HealthManager + { + protected ImplantManager implantManager; + protected InjuryManager injuryManager; + protected List implantParts; + protected Dictionary implantsForRecipes = new Dictionary(); + protected List implantRecipes; + protected Dictionary> partsForRecipe = new Dictionary>(); + protected List allBodyParts = new List(); + protected List allOutsideBodyParts = new List(); + protected List allSkinCoveredBodyParts = new List(); + + public HealthManager() + { + implantManager = new ImplantManager(); + injuryManager = new InjuryManager(); + InitializeBodyParts(); + } + + public ImplantManager ImplantManager { + get { return implantManager; } + } + + public InjuryManager InjuryManager { + get { return injuryManager; } + } + + public IEnumerable ImplantRecipes { + get { return implantManager.Recipes; } + } + + public IEnumerable AllBodyParts { + get { return allBodyParts; } + } + + public IEnumerable AllOutsideBodyParts { + get { return allOutsideBodyParts; } + } + + public IEnumerable AllSkinCoveredBodyParts { + get { return allSkinCoveredBodyParts; } + } + + public BodyPartRecord FirstBodyPartRecord(string bodyPartDefName) { + foreach (BodyPartRecord record in BodyDefOf.Human.AllParts) { + if (record.def.defName == bodyPartDefName) { + return record; + } + } + return null; + } + + public BodyPartRecord FirstBodyPartRecord(BodyPartDef def) { + return FirstBodyPartRecord(def.defName); + } + + protected void InitializeBodyParts() + { + BodyDef bodyDef = BodyDefOf.Human; + foreach (BodyPartRecord record in bodyDef.AllParts) { + allBodyParts.Add(record); + if (record.depth == BodyPartDepth.Outside) { + allOutsideBodyParts.Add(record); + } + FieldInfo skinCoveredField = typeof(BodyPartDef).GetField("skinCovered", BindingFlags.Instance | BindingFlags.NonPublic); + Boolean value = (Boolean)skinCoveredField.GetValue(record.def); + if (value == true) { + allSkinCoveredBodyParts.Add(record); + } + } + } + + } +} + diff --git a/Source/IEquipment.cs b/Source/IEquipment.cs new file mode 100644 index 0000000..26c4c2c --- /dev/null +++ b/Source/IEquipment.cs @@ -0,0 +1,25 @@ +using System; +using Verse; + +namespace EdB.PrepareCarefully +{ + public interface IEquipment + { + int Count { + get; + } + + ThingDef ThingDef { + get; + } + + ThingDef StuffDef { + get; + } + + Gender Gender { + get; + } + } +} + diff --git a/Source/Implant.cs b/Source/Implant.cs new file mode 100644 index 0000000..a7d6371 --- /dev/null +++ b/Source/Implant.cs @@ -0,0 +1,164 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Implant : CustomBodyPart + { + protected BodyPartRecord bodyPartRecord; + + public string label = ""; + public RecipeDef recipe = null; + protected Hediff hediff = null; + + protected string tooltip; + + public Implant() + { + } + + public override BodyPartRecord BodyPartRecord { + get { + return bodyPartRecord; + } + set { + bodyPartRecord = value; + tooltip = null; + } + } + + override public string ChangeName { + get { + return Label; + } + } + + override public Color LabelColor { + get { + if (recipe.addsHediff != null) { + return recipe.addsHediff.defaultLabelColor; + } + else { + return Page_ConfigureStartingPawnsCarefully.TextColor; + } + } + } + + public Implant(BodyPartRecord bodyPartRecord, RecipeDef recipe) + { + this.BodyPartRecord = bodyPartRecord; + this.recipe = recipe; + } + + public RecipeDef Recipe { + get { + return recipe; + } + set { + recipe = value; + tooltip = null; + } + } + + public Hediff_AddedPart AddedBodyPart { + get { + if (recipe == null) { + return null; + } + Hediff_AddedPart addedPart = new Hediff_AddedPart(); + addedPart.Part = BodyPartRecord; + addedPart.def = recipe.addsHediff; + return addedPart; + } + } + + public string Label { + get { + if (recipe == null) { + return ""; + } + return recipe.addsHediff.LabelCap; + } + } + + public override bool Equals(System.Object obj) + { + if (obj == null) + { + return false; + } + + Implant option = obj as Implant; + if ((System.Object)option == null) + { + return false; + } + + return (BodyPartRecord == option.BodyPartRecord) && (recipe == option.recipe); + } + + public bool Equals(Implant option) + { + if ((object)option == null) + { + return false; + } + + return (BodyPartRecord == option.BodyPartRecord) && (recipe == option.recipe); + } + + public override int GetHashCode() { + unchecked { + int a = BodyPartRecord != null ? BodyPartRecord.GetHashCode() : 0; + int b = recipe != null ? recipe.GetHashCode() : 0; + return 31 * a + b; + } + } + + public override void AddToPawn(CustomPawn customPawn, Pawn pawn) { + if (recipe != null && BodyPartRecord != null) { + this.hediff = HediffMaker.MakeHediff(recipe.addsHediff, pawn, BodyPartRecord); + pawn.health.AddHediff(hediff, BodyPartRecord, new DamageInfo?()); + pawn.health.capacities.Clear(); + } + } + + public override bool HasTooltip { + get { + return hediff != null; + } + } + + public override string Tooltip { + get { + if (tooltip == null) { + InitializeTooltip(); + } + return tooltip; + } + } + + protected void InitializeTooltip() { + StringBuilder stringBuilder = new StringBuilder(); + Hediff_Injury hediff_Injury = hediff as Hediff_Injury; + string damageLabel = hediff.DamageLabel; + if (!hediff.Label.NullOrEmpty() || !damageLabel.NullOrEmpty() || !hediff.CapMods.NullOrEmpty()) { + stringBuilder.Append(hediff.LabelCap); + if (!damageLabel.NullOrEmpty()) { + stringBuilder.Append(": " + damageLabel); + } + stringBuilder.AppendLine(); + string tipStringExtra = hediff.TipStringExtra; + if (!tipStringExtra.NullOrEmpty()) { + stringBuilder.AppendLine(tipStringExtra.TrimEndNewlines().Indented()); + } + } + tooltip = stringBuilder.ToString(); + } + } +} + diff --git a/Source/ImplantManager.cs b/Source/ImplantManager.cs new file mode 100644 index 0000000..6d897b8 --- /dev/null +++ b/Source/ImplantManager.cs @@ -0,0 +1,130 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Verse; +using Verse.Sound; +namespace EdB.PrepareCarefully +{ + public class ImplantManager + { + protected Dictionary> bodyPartListLookup = new Dictionary>(); + protected List recipes = new List(); + protected Dictionary> recipeBodyParts = new Dictionary>(); + protected HashSet replaceableParts = new HashSet(); + protected Dictionary> bodyPartAncestors = new Dictionary>(); + + public List Recipes { + get { + return recipes; + } + } + + public ImplantManager() + { + bodyPartListLookup = new Dictionary>(); + + // Go through all of the body part records in a human. Each record is an individual body part, and you + // will see more than one instance of each record. For example, you'll see 12 "Rib" records, one for each + // of the 12 rib bones. For each body part type (i.e. "Rib"), we store a list of each body part record + // in a dictionary, so that we can get a list of all records that match a given type/definition. + BodyDef bodyDef = BodyDefOf.Human; + foreach (BodyPartRecord record in bodyDef.AllParts) { + List records; + if (!bodyPartListLookup.TryGetValue(record.def, out records)) { + records = new List(); + bodyPartListLookup[record.def] = records; + } + records.Add(record); + AddAncestors(record); + } + + // Find all recipes that replace a body part. + recipes.AddRange(DefDatabase.AllDefs.Where((RecipeDef def) => { + if (def.addsHediff != null && def.appliedOnFixedBodyParts != null && def.appliedOnFixedBodyParts.Count > 0 + && (def.recipeUsers.NullOrEmpty() || def.recipeUsers.Contains(ThingDefOf.Human)) ) { + return true; + } + else { + return false; + } + })); + + // Iterate the recipes. Populate a lookup with all of the body parts that apply to a given recipe. + foreach (var r in recipes) { + + // Get the list of recipe body parts from the dictionary or create it if it doesn't exist. + List bodyPartRecords; + if (!recipeBodyParts.TryGetValue(r, out bodyPartRecords)) { + bodyPartRecords = new List(); + recipeBodyParts.Add(r, bodyPartRecords); + } + // Add all of the body part records for that recipe to the list. + foreach (var bodyPartDef in r.appliedOnFixedBodyParts) { + if (bodyPartListLookup.ContainsKey(bodyPartDef)) { + List records = bodyPartListLookup[bodyPartDef]; + bodyPartRecords.AddRange(records); + foreach (var record in records) { + replaceableParts.Add(record); + } + } + } + } + + // Sort the recipes + recipes.Sort((RecipeDef a, RecipeDef b) => { + return a.LabelCap.CompareTo(b.LabelCap); + }); + } + + public List PartsForRecipe(RecipeDef recipe) + { + return this.recipeBodyParts[recipe]; + } + + public IEnumerable PartAncestors(BodyPartRecord part) + { + return bodyPartAncestors[part]; + } + + public bool AncestorIsImplant(BodyPartRecord record, CustomPawn pawn) + { + foreach (BodyPartRecord ancestor in bodyPartAncestors[record]) { + if (pawn.IsImplantedPart(ancestor)) { + return true; + } + } + return false; + } + + protected void AddAncestors(BodyPartRecord record) + { + if (bodyPartAncestors.ContainsKey(record)) { + return; + } + else { + bodyPartAncestors.Add(record, new HashSet()); + } + for (BodyPartRecord parentRecord = record.parent; parentRecord != null; parentRecord = parentRecord.parent) { + bodyPartAncestors[record].Add(parentRecord); + } + } + + // TODO: This is problematic for body parts that appear multiple times in a body (i.e. ribs). + // Calling this will return the first one that it finds. There's no distinguishing between multiple + // parts of the same type. + public BodyPartRecord FindReplaceableBodyPartByName(string name) + { + foreach (var record in replaceableParts) { + if (record.def.defName == name) { + return record; + } + } + return null; + } + + } +} + diff --git a/Source/Injury.cs b/Source/Injury.cs new file mode 100644 index 0000000..9a8b642 --- /dev/null +++ b/Source/Injury.cs @@ -0,0 +1,185 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Injury : CustomBodyPart + { + protected BodyPartRecord bodyPartRecord; + + protected InjuryOption option; + + protected Hediff hediff; + + protected string tooltip; + + public InjuryOption Option { + get { + return option; + } + set { + option = value; + tooltip = null; + stageLabel = ComputeStageLabel(); + } + } + + protected float severity = 2; + + protected string stageLabel = null; + + public float Severity { + get { + return severity; + } + set { + tooltip = null; + severity = value; + stageLabel = ComputeStageLabel(); + } + } + + public Injury() + { + } + + override public BodyPartRecord BodyPartRecord { + get { + return bodyPartRecord; + } + set { + bodyPartRecord = value; + } + } + + override public string ChangeName { + get { + if (stageLabel != null) { + return stageLabel; + } + else if (Option != null) { + return Option.Label; + } + else { + return "?"; + } + } + } + + override public Color LabelColor { + get { + if (Option != null && Option.HediffDef != null) { + return Option.HediffDef.defaultLabelColor; + } + else { + return Page_ConfigureStartingPawnsCarefully.TextColor; + } + } + } + + public override void AddToPawn(CustomPawn customPawn, Pawn pawn) { + if (Option.Giver != null) { + Hediff hediff = HediffMaker.MakeHediff(Option.HediffDef, pawn, BodyPartRecord); + hediff.Severity = this.Severity; + pawn.health.AddHediff(hediff, BodyPartRecord, null); + this.hediff = hediff; + } + else if (Option.IsOldInjury) { + Hediff_Injury hediff = (Hediff_Injury) HediffMaker.MakeHediff(Option.HediffDef, pawn, null); + hediff.Severity = this.Severity; + hediff.TryGetComp().IsOld = true; + // TODO: This should not be hard-coded. + hediff.TryGetComp().painFactor = 4; + pawn.health.AddHediff(hediff, BodyPartRecord, null); + this.hediff = hediff; + } + pawn.health.capacities.Clear(); + } + + protected string ComputeStageLabel() + { + if (Option.HasStageLabel) { + return "EdB.PrepareCarefully.Stage.FieldLabel".Translate(new object[] { + this.option.Label, CurStage.label + }); + } + else if (Option.IsOldInjury) { + return "EdB.PrepareCarefully.Severity.FieldLabel".Translate(new object[] { + this.option.Label, (int)severity + }); + } + else { + return null; + } + } + + protected HediffStage CurStage { + get { + return (!this.option.HediffDef.stages.NullOrEmpty()) ? this.option.HediffDef.stages[this.CurStageIndex] : null; + } + } + + protected int CurStageIndex { + get { + if (this.option.HediffDef.stages == null) { + return 0; + } + List stages = this.option.HediffDef.stages; + for (int i = stages.Count - 1; i >= 0; i--) { + if (this.Severity >= stages[i].minSeverity) { + return i; + } + } + return 0; + } + } + + public override bool HasTooltip { + get { + return hediff != null; + } + } + + public override string Tooltip { + get { + if (tooltip == null) { + InitializeTooltip(); + } + return tooltip; + } + } + + protected void InitializeTooltip() { + StringBuilder stringBuilder = new StringBuilder(); + if (hediff.Part != null) { + stringBuilder.Append(hediff.Part.def.LabelCap + ": "); + stringBuilder.Append(" " + hediff.pawn.health.hediffSet.GetPartHealth(hediff.Part).ToString() + " / " + hediff.Part.def.GetMaxHealth(hediff.pawn).ToString()); + } + else { + stringBuilder.Append("WholeBody".Translate()); + } + stringBuilder.AppendLine(); + stringBuilder.AppendLine("------------------"); + Hediff_Injury hediff_Injury = hediff as Hediff_Injury; + string damageLabel = hediff.DamageLabel; + if (!hediff.Label.NullOrEmpty() || !damageLabel.NullOrEmpty() || !hediff.CapMods.NullOrEmpty()) { + stringBuilder.Append(hediff.LabelCap); + if (!damageLabel.NullOrEmpty()) { + stringBuilder.Append(": " + damageLabel); + } + stringBuilder.AppendLine(); + string tipStringExtra = hediff.TipStringExtra; + if (!tipStringExtra.NullOrEmpty()) { + stringBuilder.AppendLine(tipStringExtra.TrimEndNewlines().Indented()); + } + } + tooltip = stringBuilder.ToString(); + } + } +} + diff --git a/Source/InjuryManager.cs b/Source/InjuryManager.cs new file mode 100644 index 0000000..83f265e --- /dev/null +++ b/Source/InjuryManager.cs @@ -0,0 +1,128 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class InjuryManager + { + public InjuryManager() + { + InitializeOptions(); + } + + protected List options = new List(); + + public IEnumerable Options { + get { return options; } + } + + public void InitializeOptions() + { + // Add long-term chronic. + HediffGiverSetDef giverSetDef = DefDatabase.GetNamedSilentFail("OrganicStandard"); + if (giverSetDef != null) { + foreach (var g in giverSetDef.hediffGivers) { + if (g.GetType() == typeof(HediffGiver_Birthday)) { + InjuryOption option = new InjuryOption(); + option.Chronic = true; + option.HediffDef = g.hediff; + option.Label = g.hediff.LabelCap; + option.Giver = g; + if (!g.canAffectAnyLivePart) { + option.ValidParts = new List(); + option.ValidParts.AddRange(g.partsToAffect); + } + options.Add(option); + } + } + } + + // Add old injury options. + List oldInjuries = new List(); + foreach (var hd in DefDatabase.AllDefs) { + HediffCompProperties props = hd.CompPropsFor(typeof(HediffComp_GetsOld)); + if (props != null) { + if (hd.defName == "MissingBodyPart") { + continue; + } + String label; + if (props.oldLabel != null) { + label = props.oldLabel.CapitalizeFirst(); + } + else { + Log.Warning("Could not find label for old injury: " + hd.defName); + continue; + } + InjuryOption option = new InjuryOption(); + option.HediffDef = hd; + option.IsOldInjury = true; + option.Label = label; + oldInjuries.Add(option); + } + } + + // Disambiguate duplicate injury labels. + HashSet labels = new HashSet(); + HashSet duplicateLabels = new HashSet(); + foreach (var option in oldInjuries) { + if (labels.Contains(option.Label)) { + duplicateLabels.Add(option.Label); + } + else { + labels.Add(option.Label); + } + } + foreach (var option in oldInjuries) { + HediffCompProperties props = option.HediffDef.CompPropsFor(typeof(HediffComp_GetsOld)); + if (props != null) { + if (duplicateLabels.Contains(option.Label)) { + string label = "EdB.PrepareCarefully.InjuryLabel".Translate(new string[] { + props.oldLabel.CapitalizeFirst(), option.HediffDef.LabelCap + }); + option.Label = label; + } + } + } + + // Sort injury options by their label before adding them to the full list of injury options. + oldInjuries.Sort((InjuryOption x, InjuryOption y) => { + return string.Compare(x.Label, y.Label); + }); + options.Sort((InjuryOption x, InjuryOption y) => { + return string.Compare(x.Label, y.Label); + }); + options.AddRange(oldInjuries); + } + + public void InitializePawnInjuries(Pawn pawn, CustomPawn customPawn) + { + foreach (var x in pawn.health.hediffSet.hediffs) { + InjuryOption option = FindOptionByHediffDef(x.def); + if (option != null) { + Injury injury = new Injury(); + injury.BodyPartRecord = x.Part; + injury.Option = option; + injury.Severity = x.Severity; + customPawn.AddInjury(injury); + } + else { + Log.Warning("Could not find injury option for hediff: " + x.def); + } + } + } + + public InjuryOption FindOptionByHediffDef(HediffDef def) + { + foreach (InjuryOption o in options) { + if (o.HediffDef == def) { + return o; + } + } + return null; + } + } +} + diff --git a/Source/InjuryOption.cs b/Source/InjuryOption.cs new file mode 100644 index 0000000..102371e --- /dev/null +++ b/Source/InjuryOption.cs @@ -0,0 +1,66 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class InjuryOption + { + protected HediffDef hediffDef; + protected HediffGiver hediffGiver; + protected bool oldInjury = false; + protected bool removesPart = false; + protected bool chronic = false; + protected string label = "?"; + protected List validParts = null; + + public InjuryOption() + { + } + + public HediffDef HediffDef { + get { return hediffDef; } + set { hediffDef = value; } + } + + public HediffGiver Giver { + get { return hediffGiver; } + set { hediffGiver = value; } + } + + public bool IsOldInjury { + get { return oldInjury; } + set { oldInjury = value; } + } + + public bool RemovesPart { + get { return removesPart; } + set { removesPart = value; } + } + + public bool Chronic { + get { return chronic; } + set { chronic = value; } + } + + public string Label { + get { return label; } + set { label = value; } + } + + public List ValidParts { + get { return validParts; } + set { validParts = value; } + } + + public bool HasStageLabel { + get { + return (hediffDef.stages != null && hediffDef.stages.Count > 1); + } + } + + } +} + diff --git a/Source/LoadableEquipment.cs b/Source/LoadableEquipment.cs new file mode 100644 index 0000000..2e62f93 --- /dev/null +++ b/Source/LoadableEquipment.cs @@ -0,0 +1,37 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class LoadableEquipment : IExposable + { + public int count; + public String def; + public String stuffDef; + public String gender; + + public LoadableEquipment() + { + } + + public LoadableEquipment(SelectedEquipment customPawn) + { + count = customPawn.count; + def = customPawn.def.defName; + stuffDef = customPawn.stuffDef.defName; + gender = customPawn.gender == Gender.None ? null : customPawn.gender.ToString(); + } + + public void ExposeData() + { + Scribe_Values.LookValue(ref this.def, "def", null, false); + Scribe_Values.LookValue(ref this.stuffDef, "stuffDef", null, false); + Scribe_Values.LookValue(ref this.gender, "gender", null, false); + Scribe_Values.LookValue(ref this.count, "count", 0, false); + } + } +} + diff --git a/Source/Logger.cs b/Source/Logger.cs new file mode 100644 index 0000000..db3fe93 --- /dev/null +++ b/Source/Logger.cs @@ -0,0 +1,47 @@ +using System; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Logger + { + private 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) { + Log.Message(message); + } + } + + public void Message(string message) { + Log.Message(message); + } + + public void Warning(string message) { + Log.Warning(message); + } + + public void Error(string message) { + Log.Error(message); + } + } +} + diff --git a/Source/ModController.cs b/Source/ModController.cs new file mode 100644 index 0000000..2b4ed44 --- /dev/null +++ b/Source/ModController.cs @@ -0,0 +1,148 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + class ModController : UnityEngine.MonoBehaviour + { + public static readonly string ModName = "EdB Prepare Carefully"; + public static readonly string Version = "0.13.0.3"; + + Window currentLayer = null; + bool gameplay = false; + + public ModController() + { + + } + + // Called when the MonoBehavior first starts up. + public virtual void Start() + { + this.enabled = true; + } + + // Called whenever the game switches from the main menu (level 0) into gameplay + // (level 1). + public void OnLevelWasLoaded(int level) + { + // Level 0 means we're in the game menus. Enable the behavior so that + // it starts checking every frame to see if it needs to inject the custom UI + if (level == 0) { + this.gameplay = false; + this.enabled = true; + PrepareCarefully.Instance.Active = false; + } + // Level 1 means we're in gameplay. Disable the behavior because we don't + // need to check anything every frame. + else if (level == 1) { + this.gameplay = true; + + // Disable this component + this.enabled = false; + } + } + + // Called every frame when the mod is enabled + public virtual void Update() + { + try { + if (!gameplay) { + MenusUpdate(); + } + else { + GameplayUpdate(); + } + } + catch (Exception e) { + this.enabled = false; + Log.Error(e.ToString()); + } + } + + // Check if the top user interface element is the vanilla character creation + // screen. If it is, and if the mod is enabled, swap in the custom version. + public virtual void MenusUpdate() + { + // Keep track of the user interface element that's currently on the + // top of the layer stack. + bool layerChanged = false; + Window layer = this.TopWindow; + if (layer != this.currentLayer) { + //Log.Message("Window changed: " + (layer == null ? "null" : layer.GetType().FullName)); + this.currentLayer = layer; + layerChanged = true; + } + + // Check the class name to see if it's the vanilla character creation screen + if (layer != null) { + if ("RimWorld.Page_ConfigureStartingPawns".Equals(layer.GetType().FullName)) { + if (ModEnabled) { + ResetTextures(); + Page page = layer as Page; + Page_ConfigureStartingPawns replacement = new Page_ConfigureStartingPawns(); + replacement.nextAct = page.nextAct; + replacement.next = page.next; + PrepareCarefully.Instance.OriginalPage = replacement; + Find.WindowStack.TryRemove(layer, true); + Find.WindowStack.Add(replacement); + Log.Message("Swapped in EdB Prepare Carefully Character Creation Page"); + } + else { + if (layerChanged) { + Log.Message("EdB Prepare Carefully not enabled. Did not replace Character Creation Page"); + } + } + } + } + } + + public virtual void GameplayUpdate() + { + } + + // Find the top window in the stack that isn't the Console. If we don't do this, all of our logic + // around swapping in a replacement window will fail when the console is up. + public Window TopWindow + { + get { + // The accessors reference properties that might be null with no null-checks, so we need to do some + // non-obvious null-checks ourselves here. + if (Find.UIRoot != null && Find.UIRoot.windows != null && Find.WindowStack != null && Find.WindowStack.Windows != null) { + // Iterate the layers. + foreach (Window window in Find.WindowStack.Windows.Reverse()) { + if (window != null && window.GetType().FullName != "Verse.ImmediateWindow" + && window.GetType().FullName != "Verse.EditWindow_Log") { + return window; + } + } + } + return null; + } + } + + public void ResetTextures() + { + + } + + public bool ModEnabled + { + get { + ModMetaData mod = ModLister.AllInstalledMods.First((ModMetaData m) => { + return m.Name.Equals(ModName); + }); + if (mod == null) { + return false; + } + return mod.Active; + } + } + } +} diff --git a/Source/ModInitializer.cs b/Source/ModInitializer.cs new file mode 100644 index 0000000..498c95c --- /dev/null +++ b/Source/ModInitializer.cs @@ -0,0 +1,31 @@ + +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + // This class gets instantiated + public class ModInitializer : ITab + { + protected GameObject gameObject = null; + + public ModInitializer() : base() + { + LongEventHandler.ExecuteWhenFinished(() => { + Log.Message("Initialized the EdB Prepare Carefully mod"); + gameObject = new GameObject("EdBPrepareCarefullyController"); + gameObject.AddComponent(); + MonoBehaviour.DontDestroyOnLoad(gameObject); + }); + } + + protected override void FillTab() { + return; + } + } +} diff --git a/Source/ModNotInstalledException.cs b/Source/ModNotInstalledException.cs new file mode 100644 index 0000000..bcd8f39 --- /dev/null +++ b/Source/ModNotInstalledException.cs @@ -0,0 +1,12 @@ +using System; + +namespace EdB.PrepareCarefully +{ + public class ModNotInstalledException : Exception + { + public ModNotInstalledException() { } + public ModNotInstalledException(String message) : base(message) { } + public ModNotInstalledException(String message, Exception cause) : base(message, cause) { } + } +} + diff --git a/Source/Page_ConfigureStartingPawns.cs b/Source/Page_ConfigureStartingPawns.cs new file mode 100644 index 0000000..c2af3ff --- /dev/null +++ b/Source/Page_ConfigureStartingPawns.cs @@ -0,0 +1,123 @@ +using RimWorld; +using System; +using System.Linq; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace EdB.PrepareCarefully +{ + public class Page_ConfigureStartingPawns : Page + { + // + // Static Fields + // + private const float TabAreaHeight = 30; + + private const float RectAreaWidth = 100; + + private const float RightRectLeftPadding = 5; + + private static readonly Vector2 PawnPortraitSize = new Vector2(100, 140); + + // + // Fields + // + private Pawn curPawn; + + // + // Properties + // + // EdB: Copy of the RimWorld.Page_ConfigureStartingPawns.PageTitle + // Updated for Alpha 14. + public override string PageTitle { + get { + return "CreateCharacters".Translate(); + } + } + + // + // Methods + // + public override void DoWindowContents(Rect rect) + { + base.DrawPageTitle(rect); + Rect mainRect = base.GetMainRect(rect, 30, false); + Widgets.DrawMenuSection(mainRect, true); + TabDrawer.DrawTabs(mainRect, from c in Find.GameInitData.startingPawns + select new TabRecord(c.LabelCap, delegate { + this.SelectPawn(c); + }, c == this.curPawn)); + Rect rect2 = mainRect.ContractedBy(17); + Rect rect3 = rect2; + rect3.width = 100; + GUI.DrawTexture(new Rect(rect3.xMin + (rect3.width - Page_ConfigureStartingPawns.PawnPortraitSize.x) / 2 - 10, rect3.yMin + 20, Page_ConfigureStartingPawns.PawnPortraitSize.x, Page_ConfigureStartingPawns.PawnPortraitSize.y), PortraitsCache.Get(this.curPawn, Page_ConfigureStartingPawns.PawnPortraitSize, default(Vector3), 1)); + Rect rect4 = rect2; + rect4.xMin = rect3.xMax; + Rect rect5 = rect4; + rect5.width = 475; + CharacterCardUtility.DrawCharacterCard(rect5, this.curPawn, new Action(this.RandomizeCurPawn)); + Rect rect6 = new Rect(rect5.xMax + 5, rect4.y + 100, rect4.width - rect5.width - 5, 200); + Text.Font = GameFont.Medium; + Widgets.Label(rect6, "Health".Translate()); + Text.Font = GameFont.Small; + rect6.yMin += 35; + HealthCardUtility.DrawHediffListing(rect6, this.curPawn, true); + Rect rect7 = new Rect(rect6.x, rect6.yMax, rect6.width, 200); + Text.Font = GameFont.Medium; + Widgets.Label(rect7, "Relations".Translate()); + Text.Font = GameFont.Small; + rect7.yMin += 35; + SocialCardUtility.DrawRelationsAndOpinions(rect7, this.curPawn); + // EdB: Add a middle "Prepare Carefully" button. + Action prepareCarefullyAction = () => { + PrepareCarefully.Instance.Initialize(); + Find.WindowStack.Add(new Page_ConfigureStartingPawnsCarefully()); + }; + base.DoBottomButtons(rect, "Start".Translate(), "EdB.PrepareCarefully".Translate(), prepareCarefullyAction, true); + } + + // EdB: Copy of the RimWorld.Page_ConfigureStartingPawns.PreOpen() + // Updated for Alpha 14. + public override void PreOpen() + { + base.PreOpen(); + if (Find.GameInitData.startingPawns.Count > 0) { + this.curPawn = Find.GameInitData.startingPawns[0]; + } + } + + // EdB: Copy of the RimWorld.Page_ConfigureStartingPawns.RandomizeCurPawn() + // Updated for Alpha 14. + private void RandomizeCurPawn() + { + do { + this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn); + } + while (!StartingPawnUtility.AnyoneCanDoRequiredWorkTypes()); + } + + // EdB: Copy of the RimWorld.Page_ConfigureStartingPawns.SelectPawn() + // Updated for Alpha 14. + public void SelectPawn(Pawn c) + { + if (c != this.curPawn) { + this.curPawn = c; + } + } + + // EdB: Copy of the RimWorld.Page_ConfigureStartingPawns.TryNext() + // Updated for Alpha 14. + protected override bool TryNext() + { + foreach (Pawn current in Find.GameInitData.startingPawns) { + if (!current.Name.IsValid) { + Messages.Message("EveryoneNeedsValidName".Translate(), MessageSound.RejectInput); + return false; + } + } + PortraitsCache.Clear(); + return true; + } + } +} diff --git a/Source/Page_ConfigureStartingPawnsCarefully.cs b/Source/Page_ConfigureStartingPawnsCarefully.cs new file mode 100644 index 0000000..8bdbfcb --- /dev/null +++ b/Source/Page_ConfigureStartingPawnsCarefully.cs @@ -0,0 +1,2509 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace EdB.PrepareCarefully +{ + public class Page_ConfigureStartingPawnsCarefully : Page_PrepareCarefully + { + private DragSlider chronologicalAgeDragSlider; + private DragSlider biologicalAgeDragSlider; + + public static readonly Vector2 WinSize = new Vector2(1020, 764); + public const float TopAreaHeight = 80; + + public static Color SectionBackgroundColor = new Color(0.145098f, 0.1490196f, 0.152941f); + public static Color TextColor = new Color(0.90196f, 0.90196f, 0.90196f); + public static Color PortraitBorderColor = new Color(0.3843f, 0.3843f, 0.3843f); + public static Color ButtonColor = new Color(0.623529f, 0.623529f, 0.623529f); + public static Color ButtonHighlightColor = new Color(0.97647f, 0.97647f, 0.97647f); + public static Color ButtonDisabledColor = new Color(0.27647f, 0.27647f, 0.27647f); + public static Color PortraitTabActiveColor = SectionBackgroundColor; + public static Color PortraitTabInactiveColor = new Color(21.0f / 255.0f, 25.0f / 255.0f, 29.0f / 255.0f); + public static float PortraitTabMargin = 1; + public static float PortraitTabPadding = 11; + + protected int selectedPawnLayer = 0; + protected List pawnLayers; + protected List pawnLayerActions; + + protected FieldInfo apparelGraphicsField = null; + + protected ScrollView skillScrollView = new ScrollView(); + + public int CurrentPawnIndex { + get { + return State.CurrentPawnIndex; + } + set { + State.CurrentPawnIndex = value; + } + } + public CustomPawn CurrentPawn { + get { + return PrepareCarefully.Instance.Pawns[CurrentPawnIndex]; + } + } + + protected int pawnLayerLabelIndex = -1; + protected CustomPawn pawnLayerLabelModel = null; + protected string pawnLayerLabel = null; + + protected List> apparelLists = new List>(PawnLayers.Count); + + protected List childhoodBackstories = new List(); + protected List adulthoodBackstories = new List(); + protected List sortedChildhoodBackstories; + protected List sortedAdulthoodBackstories; + protected List traits = new List(); + protected List sortedTraits; + + protected List maleHeads = new List(); + protected List femaleHeads = new List(); + protected Dictionary bodyTypeLabels = new Dictionary(); + protected List maleHairDefs = new List(); + protected List femaleHairDefs = new List(); + protected List skinColors = new List(); + protected List hairColors = new List(); + protected LeftPanelMode leftPanelMode = LeftPanelMode.Appearance; + + protected bool bionicsMode = false; + protected Vector2 bionicsScrollPosition = new Vector2(0, 0); + protected float bionicsScrollHeight = 0; + + protected Dictionary> apparelStuffLookup = new Dictionary>(); + + protected int selectedStuff = 0; + + protected HashSet problemBackstories = new HashSet(); + + protected Randomizer randomizer = new Randomizer(); + + protected int maxAge = 90; + protected int minAge = 15; + protected int maxChronologicalAge = 3200; + + protected ScrollView HealthScrollView = new ScrollView(); + protected ScrollView RelationsScrollView = new ScrollView(); + + protected Panel_Relations PanelRelations; + protected Panel_Health PanelHealth; + + static Page_ConfigureStartingPawnsCarefully() { + } + + public override Vector2 InitialSize { + get { + return Page_ConfigureStartingPawnsCarefully.WinSize; + } + } + + public enum LeftPanelMode + { + Appearance, + Relations, + Health + }; + + public Page_ConfigureStartingPawnsCarefully() + { + this.absorbInputAroundWindow = true; + this.forcePause = true; + + chronologicalAgeDragSlider = new DragSlider(0.4f, 20, 100); + chronologicalAgeDragSlider.minValue = minAge; + chronologicalAgeDragSlider.maxValue = maxChronologicalAge; + + biologicalAgeDragSlider = new DragSlider(0.4f, 15, 100); + biologicalAgeDragSlider.minValue = minAge; + biologicalAgeDragSlider.maxValue = maxAge; + + foreach (string path in GraphicsCache.Instance.MaleHeadPaths) { + maleHeads.Add(path); + } + foreach (string path in GraphicsCache.Instance.FemaleHeadPaths) { + femaleHeads.Add(path); + } + + bodyTypeLabels.Add(BodyType.Fat, "EdB.BodyType.Fat".Translate()); + bodyTypeLabels.Add(BodyType.Hulk, "EdB.BodyType.Hulk".Translate()); + bodyTypeLabels.Add(BodyType.Thin, "EdB.BodyType.Thin".Translate()); + bodyTypeLabels.Add(BodyType.Male, "EdB.BodyType.Average".Translate()); + bodyTypeLabels.Add(BodyType.Female, "EdB.BodyType.Average".Translate()); + + pawnLayers = new List(new int[] { + PawnLayers.Hair, + PawnLayers.HeadType, + PawnLayers.Pants, + PawnLayers.BottomClothingLayer, + PawnLayers.MiddleClothingLayer, + PawnLayers.TopClothingLayer, + PawnLayers.Hat + }); + pawnLayerActions = new List(new Action[] { + delegate { this.ChangePawnLayer(PawnLayers.Hair); }, + delegate { this.ChangePawnLayer(PawnLayers.HeadType); }, + delegate { this.ChangePawnLayer(PawnLayers.Pants); }, + delegate { this.ChangePawnLayer(PawnLayers.BottomClothingLayer); }, + delegate { this.ChangePawnLayer(PawnLayers.MiddleClothingLayer); }, + delegate { this.ChangePawnLayer(PawnLayers.TopClothingLayer); }, + delegate { this.ChangePawnLayer(PawnLayers.Hat); }, + }); + + foreach (HairDef hairDef in DefDatabase.AllDefs) { + if (hairDef.hairGender != HairGender.Male) { + femaleHairDefs.Add(hairDef); + } + if (hairDef.hairGender != HairGender.Female) { + maleHairDefs.Add(hairDef); + } + } + + for (int i = 0; i < PawnLayers.Count; i++) { + if (PawnLayers.IsApparelLayer(i)) { + this.apparelLists.Add(new List()); + } + else { + this.apparelLists.Add(null); + } + } + + // Get all apparel options + foreach (ThingDef apparelDef in DefDatabase.AllDefs) { + if (apparelDef.apparel == null || apparelDef.defName == "Apparel_PersonalShield") { + continue; + } + int layer = PawnLayers.ToPawnLayerIndex(apparelDef.apparel); + if (layer != -1) { + apparelLists[layer].Add(apparelDef); + } + } + + // Get the apparel graphics private method so that we can call it later + apparelGraphicsField = typeof(PawnGraphicSet).GetField("apparelGraphics", BindingFlags.Instance | BindingFlags.NonPublic); + + // Organize stuff by its category + Dictionary> stuffByCategory = new Dictionary>(); + foreach (ThingDef thingDef in DefDatabase.AllDefs) { + if (thingDef.IsStuff && thingDef.stuffProps != null) { + foreach (StuffCategoryDef cat in thingDef.stuffProps.categories) { + HashSet thingDefs = null; + if (!stuffByCategory.TryGetValue(cat, out thingDefs)) { + thingDefs = new HashSet(); + stuffByCategory.Add(cat, thingDefs); + } + thingDefs.Add(thingDef); + } + } + } + + // For each apparel def, get the list of all materials that can be used to make it. + foreach (ThingDef thingDef in DefDatabase.AllDefs) { + if (thingDef.apparel != null && thingDef.MadeFromStuff) { + if (thingDef.stuffCategories != null) { + List stuffList = new List(); + foreach (var cat in thingDef.stuffCategories) { + HashSet thingDefs; + if (stuffByCategory.TryGetValue(cat, out thingDefs)) { + foreach (ThingDef stuffDef in thingDefs) { + stuffList.Add(stuffDef); + } + } + } + apparelStuffLookup[thingDef] = stuffList; + } + } + } + + // Iterate through each solid bio. Find the corresponding backstories. If the backstory description contains the bio's name + // the mark the backstory as name-specific. If the bio does not has HE/HIS/etc, mark the backstory as gender-specific. + + List backstories = BackstoryDatabase.allBackstories.Values.ToList(); + foreach (Backstory backstory in backstories) { + + if (!backstory.shuffleable && (!backstory.baseDesc.Contains("NAME") || (!backstory.baseDesc.Contains("HECAP") && !backstory.baseDesc.Contains("HE ") + && !backstory.baseDesc.Contains("HIS") && !backstory.baseDesc.Contains("HISCAP") && !backstory.baseDesc.Contains("HIM")))) { + problemBackstories.Add(backstory); + } + if (backstory.slot == BackstorySlot.Childhood) { + childhoodBackstories.Add(backstory); + } + else { + adulthoodBackstories.Add(backstory); + } + } + + // Create sorted versions of the backstory lists + sortedChildhoodBackstories = new List(childhoodBackstories); + sortedChildhoodBackstories.Sort((b1, b2) => b1.title.CompareTo(b2.title)); + sortedAdulthoodBackstories = new List(adulthoodBackstories); + sortedAdulthoodBackstories.Sort((b1, b2) => b1.title.CompareTo(b2.title)); + + // Get all trait options + foreach (TraitDef def in DefDatabase.AllDefs) { + List degreeData = def.degreeDatas; + int count = degreeData.Count; + if (count > 0) { + for (int i = 0; i < count; i++) { + Trait trait = new Trait(def, degreeData[i].degree); + traits.Add(trait); + } + } + else { + traits.Add(new Trait(def, 0)); + } + } + + // Create a sorted version of the trait list + sortedTraits = new List(traits); + sortedTraits.Sort((t1, t2) => t1.LabelCap.CompareTo(t2.LabelCap)); + + // Set up default hair colors + hairColors.Add(new Color(0.2f, 0.2f, 0.2f)); + hairColors.Add(new Color(0.31f, 0.28f, 0.26f)); + hairColors.Add(new Color(0.25f, 0.2f, 0.15f)); + hairColors.Add(new Color(0.3f, 0.2f, 0.1f)); + hairColors.Add(new Color(0.3529412f, 0.227451f, 0.1254902f)); + hairColors.Add(new Color(0.5176471f, 0.3254902f, 0.1843137f)); + hairColors.Add(new Color(0.7568628f, 0.572549f, 0.3333333f)); + hairColors.Add(new Color(0.9294118f, 0.7921569f, 0.6117647f)); + + // Set up default skin colors + skinColors.Add(new Color(0.3882353f, 0.2745098f, 0.1411765f)); + skinColors.Add(new Color(0.509804f, 0.3568628f, 0.1882353f)); + skinColors.Add(new Color(0.8941177f, 0.6196079f, 0.3529412f)); + skinColors.Add(new Color(1f, 0.9372549f, 0.7411765f)); + skinColors.Add(new Color(1f, 0.9372549f, 0.8352941f)); + skinColors.Add(new Color(0.9490196f, 0.9294118f, 0.8784314f)); + + this.ChangePawnLayer(pawnLayers[0]); + + PanelRelations = new Panel_Relations(SectionRectPortraitContent); + PanelHealth = new Panel_Health(SectionRectPortraitContent); + } + + public static void ExportBackstoriesToFile(string path) + { + System.Xml.Linq.XDocument doc = new System.Xml.Linq.XDocument(); + System.Xml.Linq.XElement el = new System.Xml.Linq.XElement("Backstories"); + doc.Add(el); + foreach (Backstory backstory in BackstoryDatabase.allBackstories.Values) { + el.Add(XmlSaver.XElementFromObject(backstory, backstory.GetType())); + } + doc.Save(path); + } + + public void AddColonist() + { + AddColonist(randomizer.GenerateColonist()); + } + + public void AddColonist(Pawn pawn) + { + AddColonist(new CustomPawn(pawn)); + } + + public void AddColonist(CustomPawn pawn) + { + PrepareCarefully.Instance.AddPawn(pawn); + CurrentPawnIndex = PrepareCarefully.Instance.Pawns.Count - 1; + } + + public void DeleteColonist(int index) + { + CustomPawn customPawn = CurrentPawn; + PrepareCarefully.Instance.RemovePawn(customPawn); + + if (CurrentPawnIndex >= PrepareCarefully.Instance.Pawns.Count) { + CurrentPawnIndex = PrepareCarefully.Instance.Pawns.Count - 1; + } + PrepareCarefully.Instance.RelationshipManager.DeletePawnRelationships(customPawn); + } + + private AcceptanceReport CanStart() + { + foreach (CustomPawn current in PrepareCarefully.Instance.Pawns) { + if (!current.Name.IsValid) { + return new AcceptanceReport("EveryoneNeedsValidName".Translate()); + } + } + return AcceptanceReport.WasAccepted; + } + + public override void WindowUpdate() + { + base.WindowUpdate(); + DragSliderManager.DragSlidersUpdate(); + } + + public override void DoWindowContents(Rect inRect) + { + if (hidden) { + return; + } + + if (CurrentPawnIndex >= PrepareCarefully.Instance.Pawns.Count) { + CurrentPawnIndex = PrepareCarefully.Instance.Pawns.Count - 1; + } + + Text.Font = GameFont.Medium; + Widgets.Label(new Rect(0, 0, 300, 300), "CreateCharacters".Translate()); + Text.Font = GameFont.Small; + + if (this.IsBroken) { + return; + } + + Rect rect = new Rect(0, 80, inRect.width, inRect.height - 60 - 80); + Widgets.DrawMenuSection(rect, true); + int tabCount = PrepareCarefully.Instance.Pawns.Count; + CustomPawn selectedModel = CurrentPawn; + List tabs = (from m in PrepareCarefully.Instance.Pawns select new TabRecord(tabCount < 7 ? m.Label : m.NickName, delegate { this.SelectModel(m); }, m == selectedModel)).ToList(); + DrawTabs(rect, tabs); + + // Draw the add colonist button. + bool moreColonistsAllowed = PrepareCarefully.Instance.Pawns.Count < Config.maxColonists; + Rect addButtonRect = new Rect(rect.width - 20, rect.y - 23, 16, 16); + if (moreColonistsAllowed) { + GUI.color = addButtonRect.Contains(Event.current.mousePosition) ? Page_ConfigureStartingPawnsCarefully.ButtonHighlightColor : Page_ConfigureStartingPawnsCarefully.ButtonColor; + } + else { + GUI.color = Page_ConfigureStartingPawnsCarefully.ButtonDisabledColor; + } + GUI.DrawTexture(addButtonRect, Textures.TextureButtonAdd); + if (moreColonistsAllowed && Widgets.ButtonInvisible(addButtonRect, false)) { + SoundDefOf.SelectDesignator.PlayOneShotOnCamera(); + AddColonist(); + } + GUI.color = Color.white; + + Rect innerRect = rect.ContractedBy(22); + + try { + GUI.BeginGroup(innerRect); + DrawNameAndDescription(); + DrawRandomizeAll(); + DrawGenderAndAge(); + DrawColonistSaveButtons(); + DrawPortrait(); + DrawBackstory(); + DrawTraits(); + DrawIncapable(); + DrawSkills(); + } + catch (Exception e) { + FatalError("Could not draw character screen.", e); + } + finally { + GUI.EndGroup(); + } + + DrawCost(inRect); + DrawPresetButtons(); + + GUI.color = Color.white; + + // TODO: Alpha 14 + DoNextBackButtons(inRect, "Next".Translate(), + delegate { + AcceptanceReport acceptanceReport = this.CanStart(); + if (acceptanceReport.Accepted) { + Page_Equipment equipmentPage = new Page_Equipment(); + Find.WindowStack.Add(new Page_Equipment()); + this.Close(true); + } + else { + Messages.Message(acceptanceReport.Reason, MessageSound.RejectInput); + } + }, + delegate { + Find.WindowStack.Add(new Dialog_Confirm("EdB.ExitPrepareCarefullyConfirm".Translate(), delegate { + PrepareCarefully.Instance.Clear(); + this.Close(true); + }, true, null, true)); + } + ); + } + + public TabRecord DrawTabs(Rect baseRect, IEnumerable tabsEnum) + { + bool moreColonistsAllowed = PrepareCarefully.Instance.Pawns.Count < Config.maxColonists; + bool fewerColonistsAllowed = PrepareCarefully.Instance.Pawns.Count > 1; + + List tabList = tabsEnum.ToList(); + int tabCount = tabList.Count; + int colonistCount = tabList.Count; + TabRecord clickedTab = null; + TabRecord selectedTab = (from t in tabList + where t.selected + select t).FirstOrDefault(); + if (selectedTab == null) { + Debug.LogWarning("Drew tabs without any being selected."); + return tabList[0]; + } + float num = (baseRect.width - 24) + (float)(tabCount - 1) * 10; + float tabWidth = (float)(Math.Floor(num / ((float)tabCount))); + if (tabWidth > 200) { + tabWidth = 200; + } + Rect position = new Rect(baseRect); + position.y -= 32; + position.height = 9999; + GUI.BeginGroup(position); + try { + Text.Anchor = TextAnchor.MiddleCenter; + Text.Font = GameFont.Small; + Func func = delegate(TabRecord tab) { + int tabIndex = tabList.IndexOf(tab); + float left = (float)tabIndex * (tabWidth - 10); + return new Rect(left, 1, tabWidth, 32); + }; + + List list = tabList.ListFullCopy(); + list.Remove(selectedTab); + list.Add(selectedTab); + TabRecord tabRecord3 = null; + List list2 = list.ListFullCopy(); + list2.Reverse(); + + foreach (TabRecord current in list2) { + Rect rect = func(current); + if (tabRecord3 == null && rect.Contains(Event.current.mousePosition)) { + tabRecord3 = current; + } + + Rect closeButtonRect = new Rect(rect.x + rect.width - 30, 8, 16, 16); + if (colonistCount > 1 && current.selected && Widgets.ButtonInvisible(closeButtonRect, false)) { + int index = tabList.IndexOf(current); + Find.WindowStack.Add(new Dialog_Confirm("EdB.DeleteColonistConfirm".Translate(), delegate { + DeleteColonist(index); + }, true, null, true)); + } + + MouseoverSounds.DoRegion(rect); + if (Widgets.ButtonInvisible(rect, false)) { + clickedTab = current; + } + } + + foreach (TabRecord current2 in list) { + Rect rect2 = func(current2); + current2.DrawTab(rect2); + if (fewerColonistsAllowed && current2.selected) { + Rect closeButtonRect = new Rect(rect2.x + rect2.width - 30, 8, 16, 16); + if (closeButtonRect.Contains(Event.current.mousePosition)) { + GUI.color = Color.white; + GUI.DrawTexture(closeButtonRect, Textures.TextureButtonDeleteTabHighlight); + } + else { + GUI.color = new Color(0.7f, 0.7f, 0.7f); + GUI.DrawTexture(closeButtonRect, Textures.TextureButtonDeleteTab); + } + GUI.color = Color.white; + } + } + + Text.Anchor = TextAnchor.UpperLeft; + } + catch (Exception e) { + FatalError("Failed to draw colonist tabs", e); + } + finally { + GUI.EndGroup(); + } + if (clickedTab != null) { + SoundDefOf.SelectDesignator.PlayOneShotOnCamera(); + if (clickedTab.clickedAction != null) { + clickedTab.clickedAction(); + } + } + return clickedTab; + } + + private void SelectModel(CustomPawn m) + { + if (CurrentPawn != m) { + CurrentPawnIndex = PrepareCarefully.Instance.Pawns.IndexOf(m); + if (CurrentPawnIndex > -1) { + SoundDefOf.SelectDesignator.PlayOneShotOnCamera(); + } + } + } + + protected string PawnDescription + { + get { + CustomPawn customPawn = CurrentPawn; + string text = string.Concat(new string[] { + ((customPawn.Gender != Gender.Male) ? "Female".Translate() : "Male".Translate()), + " ", + customPawn.Pawn.def.label, + " ", + customPawn.Pawn.KindLabel, + ", ", + "AgeIndicator".Translate(new object[] { + customPawn.BiologicalAge + }) + }); + return text; + } + } + + public CustomPawn SelectedPawn { + get { + return CurrentPawn; + } + } + + protected static int LeftColumnWidth = 250; + protected static int MiddleColumnWidth = 320; + protected static int RightColumnWidth = 330; + protected static Vector2 SectionPadding = new Vector2(18, 12); + protected static Vector2 SectionMargin = new Vector2(18, 12); + + protected static Rect SectionRandomizeAll = new Rect(0, 0, 64, 64); + protected static Rect SectionRectNameAndDescripton = new Rect(SectionRandomizeAll.x + SectionRandomizeAll.width + SectionPadding.x, SectionRandomizeAll.y, 505, 64); + + protected static float PortraitTabsHeight = 28; + protected static Rect SectionRectPortrait = new Rect(0, SectionRectNameAndDescripton.y + SectionRectNameAndDescripton.height + SectionPadding.y, LeftColumnWidth, 441); + protected static Rect SectionRectPortraitBody = new Rect(0, SectionRectPortrait.y + PortraitTabsHeight, LeftColumnWidth, SectionRectPortrait.height - PortraitTabsHeight); + protected static Rect SectionRectPortraitContent = new Rect(0, PortraitTabsHeight, SectionRectPortrait.width, SectionRectPortrait.height - PortraitTabsHeight); + + protected static Rect SectionRectGenderAndAge = new Rect(SectionRectPortrait.x + SectionRectPortrait.width + SectionPadding.x, SectionRectPortrait.y, MiddleColumnWidth, 52); + protected static Rect SectionRectBackstory = new Rect(SectionRectGenderAndAge.x, SectionRectGenderAndAge.y + SectionRectGenderAndAge.height + SectionPadding.y, MiddleColumnWidth, 120); + protected static Rect SectionRectTraits = new Rect(SectionRectGenderAndAge.x, SectionRectBackstory.y + SectionRectBackstory.height + SectionPadding.y, MiddleColumnWidth, 157); + protected static Rect SectionColonistSave = new Rect(SectionRectGenderAndAge.x, SectionRectTraits.y + SectionRectTraits.height + SectionPadding.y, MiddleColumnWidth, SectionRectNameAndDescripton.height + 12); + + protected static Rect SectionRectSkills = new Rect(SectionRectGenderAndAge.x + SectionRectGenderAndAge.width + SectionPadding.x, 0, RightColumnWidth, 400); + protected static Rect SectionRectIncapable = new Rect(SectionRectSkills.x, SectionRectSkills.y + SectionRectSkills.height + SectionPadding.y, RightColumnWidth, 105); + + protected void DrawRandomizeAll() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRandomizeAll, BaseContent.WhiteTex); + + Rect randomRect = new Rect(SectionRandomizeAll.x + SectionRandomizeAll.width / 2 - Textures.TextureButtonRandomLarge.width / 2 - 1, + SectionRandomizeAll.y + SectionRandomizeAll.height / 2 - Textures.TextureButtonRandomLarge.height / 2, Textures.TextureButtonRandomLarge.width, + Textures.TextureButtonRandomLarge.height); + if (randomRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(randomRect, Textures.TextureButtonRandomLarge); + if (Widgets.ButtonInvisible(randomRect, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + randomizer.RandomizeAll(customPawn); + } + + GUI.color = Color.white; + } + + protected void DrawNameAndDescription() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectNameAndDescripton, BaseContent.WhiteTex); + + GUI.BeginGroup(new Rect(SectionRectNameAndDescripton.x + SectionPadding.x, SectionRectNameAndDescripton.y + 10, SectionRectNameAndDescripton.width - SectionPadding.x * 2, 44)); + + Text.Anchor = TextAnchor.UpperLeft; + + GUI.color = Color.white; + Rect nameFieldsRect = new Rect(0, 7, 442, 30); + Rect firstNameRect = new Rect(nameFieldsRect); + firstNameRect.width *= 0.333f; + Rect nickNameRect = new Rect(nameFieldsRect); + nickNameRect.width *= 0.333f; + nickNameRect.x += nickNameRect.width + 2; + Rect lastNameRect = new Rect(nameFieldsRect); + lastNameRect.width *= 0.333f; + lastNameRect.x += lastNameRect.width * 2 + 4; + string first = customPawn.FirstName; + string nick = customPawn.NickName; + string last = customPawn.LastName; + GUI.SetNextControlName("PrepareCarefullyFirst"); + CharacterCardUtility.DoNameInputRect(firstNameRect, ref first, 12); + if (nick == first || nick == last) { + GUI.color = new Color(1, 1, 1, 0.5f); + } + GUI.SetNextControlName("PrepareCarefullyNick"); + CharacterCardUtility.DoNameInputRect(nickNameRect, ref nick, 9); + GUI.color = Color.white; + GUI.SetNextControlName("PrepareCarefullyLast"); + CharacterCardUtility.DoNameInputRect(lastNameRect, ref last, 12); + TooltipHandler.TipRegion(firstNameRect, "FirstNameDesc".Translate()); + TooltipHandler.TipRegion(nickNameRect, "ShortIdentifierDesc".Translate()); + TooltipHandler.TipRegion(lastNameRect, "LastNameDesc".Translate()); + customPawn.FirstName = first; + customPawn.NickName = nick; + customPawn.LastName = last; + + GUI.EndGroup(); + + // Random button + Rect randomRect = new Rect(SectionRectNameAndDescripton.x + SectionRectNameAndDescripton.width - 31, SectionRectNameAndDescripton.y + 21, 22, 22); + if (randomRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(randomRect, Textures.TextureButtonRandom); + if (Widgets.ButtonInvisible(randomRect, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + randomizer.RandomizeName(customPawn); + } + } + + protected void DrawColonistSaveButtons() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionColonistSave, BaseContent.WhiteTex); + + float groupWidth = SectionColonistSave.width - SectionPadding.x * 2; + GUI.BeginGroup(new Rect(SectionColonistSave.x + SectionPadding.x, SectionColonistSave.y + 10, groupWidth, 54)); + + // Load/Save Colonist + GUI.color = Color.white; + float middle = groupWidth / 2; + float buttonWidth = 136; + float buttonHeight = 38; + float buttonSpacing = 16; + float buttonTop = 9; + Text.Font = GameFont.Small; + if (Widgets.ButtonText(new Rect(middle - buttonWidth - buttonSpacing / 2, buttonTop, buttonWidth, buttonHeight), "EdB.LoadColonistButton".Translate(), true, false, true)) { + if (PrepareCarefully.Instance.Pawns.Count < Config.maxColonists) { + Hide(); + Find.WindowStack.Add(new Dialog_LoadColonist()); + } + else { + Messages.Message("EdB.TooManyColonists".Translate(new object[] { + Config.maxColonists + }), MessageSound.RejectInput); + } + } + if (Widgets.ButtonText(new Rect(middle + buttonSpacing / 2, buttonTop, buttonWidth, buttonHeight), "EdB.SaveColonistButton".Translate(), true, false, true)) { + Hide(); + Find.WindowStack.Add(new Dialog_SaveColonist(SelectedPawn)); + } + + GUI.EndGroup(); + } + + private static Color DisabledColor = new Color(1, 1, 1, 0.3f); + private static Color DisabledNextPreviousButtonColor = new Color(ButtonColor.r, ButtonColor.g, ButtonColor.b, 0.4f); + protected void DrawDisabledFieldSelector(Rect fieldRect, string label, Color textColor) + { + GUI.color = DisabledColor; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + Text.Anchor = TextAnchor.MiddleCenter; + fieldRect.y += 2; + GUI.color = new Color(textColor.r, textColor.g, textColor.b, DisabledColor.a); + Widgets.Label(fieldRect, label); + GUI.color = Color.white; + + Rect prevButtonRect = new Rect(fieldRect.x - Textures.TextureButtonPrevious.width - 3, fieldRect.y + 4, Textures.TextureButtonPrevious.width, Textures.TextureButtonPrevious.height); + Rect nextButtonRect = new Rect(fieldRect.x + fieldRect.width + 1, fieldRect.y + 4, Textures.TextureButtonPrevious.width, Textures.TextureButtonPrevious.height); + + GUI.color = DisabledNextPreviousButtonColor; + GUI.DrawTexture(prevButtonRect, Textures.TextureButtonPrevious); + GUI.DrawTexture(nextButtonRect, Textures.TextureButtonNext); + Text.Anchor = TextAnchor.UpperLeft; + } + + protected void DrawFieldSelector(Rect fieldRect, string label, Action previousAction, Action nextAction) + { + DrawFieldSelector(fieldRect, label, previousAction, nextAction, TextColor); + } + + protected void DrawFieldSelector(Rect fieldRect, string label, Action previousAction, Action nextAction, Color labelColor) + { + GUI.color = Color.white; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + Text.Anchor = TextAnchor.MiddleCenter; + fieldRect.y += 2; + GUI.color = labelColor; + if (fieldRect.Contains(Event.current.mousePosition)) { + GUI.color = Color.white; + } + Widgets.Label(fieldRect, label); + GUI.color = Color.white; + + Rect prevButtonRect = new Rect(fieldRect.x - Textures.TextureButtonPrevious.width - 3, fieldRect.y + 4, Textures.TextureButtonPrevious.width, Textures.TextureButtonPrevious.height); + Rect nextButtonRect = new Rect(fieldRect.x + fieldRect.width + 1, fieldRect.y + 4, Textures.TextureButtonPrevious.width, Textures.TextureButtonPrevious.height); + + if (prevButtonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(prevButtonRect, Textures.TextureButtonPrevious); + if (previousAction != null && Widgets.ButtonInvisible(prevButtonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + previousAction(); + } + + if (nextButtonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(nextButtonRect, Textures.TextureButtonNext); + if (nextAction != null && Widgets.ButtonInvisible(nextButtonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + nextAction(); + } + Text.Anchor = TextAnchor.UpperLeft; + } + + protected void DrawAppearance(CustomPawn customPawn) + { + string label = PawnLayers.Label(this.selectedPawnLayer); + if (Widgets.ButtonText(new Rect(28, 23, 192, 28), label, true, false, true)) { + List list = new List(); + int layerCount = this.pawnLayerActions.Count; + for (int i = 0; i < layerCount; i++) { + label = PawnLayers.Label(pawnLayers[i]); + list.Add(new FloatMenuOption(label, this.pawnLayerActions[i], MenuOptionPriority.Medium, null, null, 0, null)); + } + Find.WindowStack.Add(new FloatMenu(list, null, false)); + } + + Rect portraitRect = new Rect(28, 60, 192, 192); + GUI.DrawTexture(portraitRect, Textures.TexturePortraitBackground); + + DrawPawn(portraitRect); + + GUI.color = PortraitBorderColor; + Widgets.DrawBox(portraitRect, 1); + GUI.color = Color.white; + + // Conflict alert + if (customPawn.ApparelConflict != null) { + GUI.color = Color.white; + Rect alertRect = new Rect(portraitRect.x + 77, portraitRect.y + 150, 36, 32); + GUI.DrawTexture(alertRect, Textures.TextureAlert); + TooltipHandler.TipRegion(alertRect, customPawn.ApparelConflict); + } + + // Draw apparel selector + Rect fieldRect = new Rect(portraitRect.x, portraitRect.y + portraitRect.height + 5, portraitRect.width, 28); + DrawFieldSelector(fieldRect, PawnLayerLabel.CapitalizeFirst(), + () => { + if (this.selectedPawnLayer == PawnLayers.HeadType) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + SelectNextHead(-1); + } + else if (this.selectedPawnLayer == PawnLayers.Hair) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + SelectNextHair(-1); + } + else { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + SelectNextApparel(-1); + } + }, + () => { + if (this.selectedPawnLayer == PawnLayers.HeadType) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + SelectNextHead(1); + } + else if (this.selectedPawnLayer == PawnLayers.Hair) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + SelectNextHair(1); + } + else { + SelectNextApparel(1); + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + } + } + ); + + if (Widgets.ButtonInvisible(fieldRect, false)) { + if (this.selectedPawnLayer == PawnLayers.HeadType) { + ShowHeadDialog(); + } + else if (this.selectedPawnLayer == PawnLayers.Hair) { + ShowHairDialog(); + } + else { + ShowApparelDialog(this.selectedPawnLayer); + } + } + + float cursorY = fieldRect.y + 34; + + // Draw stuff selector for apparel + if (PawnLayers.IsApparelLayer(this.selectedPawnLayer)) { + ThingDef apparelDef = customPawn.GetSelectedApparel(selectedPawnLayer); + if (apparelDef != null && apparelDef.MadeFromStuff) { + if (customPawn.GetSelectedStuff(selectedPawnLayer) == null) { + Log.Error("Selected stuff for " + PawnLayers.ToApparelLayer(selectedPawnLayer) + " is null"); + } + Rect stuffFieldRect = new Rect(portraitRect.x, cursorY, portraitRect.width, 28); + DrawFieldSelector(stuffFieldRect, customPawn.GetSelectedStuff(selectedPawnLayer).LabelCap, + () => { + ThingDef selected = customPawn.GetSelectedStuff(selectedPawnLayer); + int index = this.apparelStuffLookup[apparelDef].FindIndex((ThingDef d) => { return selected == d; }); + index--; + if (index < 0) { + index = this.apparelStuffLookup[apparelDef].Count - 1; + } + customPawn.SetSelectedStuff(selectedPawnLayer, apparelStuffLookup[apparelDef][index]); + }, + () => { + ThingDef selected = customPawn.GetSelectedStuff(selectedPawnLayer); + int index = this.apparelStuffLookup[apparelDef].FindIndex((ThingDef d) => { return selected == d; }); + index++; + if (index >= this.apparelStuffLookup[apparelDef].Count) { + index = 0; + } + customPawn.SetSelectedStuff(selectedPawnLayer, this.apparelStuffLookup[apparelDef][index]); + } + ); + + if (Widgets.ButtonInvisible(stuffFieldRect, false)) { + ShowApparelStuffDialog(this.selectedPawnLayer); + } + + cursorY += stuffFieldRect.height; + } + } + cursorY += 8; + + // Draw Color Selector + if (PawnLayers.IsApparelLayer(selectedPawnLayer)) { + ThingDef def = customPawn.GetSelectedApparel(selectedPawnLayer); + if (def != null) { + if (def.MadeFromStuff) { + DrawColorSelector(cursorY, null); + } + else { + DrawColorSelector(cursorY, def.colorGenerator); + } + } + } + else if (selectedPawnLayer == PawnLayers.BodyType || selectedPawnLayer == PawnLayers.HeadType) { + DrawSkinSelector(cursorY); + } + else if (selectedPawnLayer == PawnLayers.Hair) { + DrawColorSelector(cursorY, hairColors, true); + } + + // Random button + Rect randomRect = new Rect(SectionRectPortrait.x + 186, portraitRect.y + 10, 22, 22); + if (randomRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(randomRect, Textures.TextureButtonRandom); + if (Widgets.ButtonInvisible(randomRect, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + randomizer.RandomizePawn(customPawn); + } + } + + protected void DrawRelationships() + { + + } + + protected void DrawPortraitTabs() + { + float cursor = 0; + cursor = DrawPortraitTab(cursor, LeftPanelMode.Appearance, "EdB.Appearance"); + cursor = DrawPortraitTab(cursor, LeftPanelMode.Relations, "Relations"); + cursor = DrawPortraitTab(cursor, LeftPanelMode.Health, "Health"); + } + + protected float DrawPortraitTab(float cursor, LeftPanelMode mode, string textKey) + { + String text = textKey.Translate(); + float textWidth = Text.CalcSize(text).x; + float tabWidth = textWidth + (PortraitTabPadding * 2); + if (leftPanelMode == mode) { + GUI.color = PortraitTabActiveColor; + } + else { + GUI.color = PortraitTabInactiveColor; + } + Rect tabRect = new Rect(cursor, 0, tabWidth, PortraitTabsHeight); + GUI.DrawTexture(tabRect, BaseContent.WhiteTex); + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleCenter; + GUI.color = TextColor; + Widgets.Label(tabRect, text); + GUI.color = Color.white; + + if (Widgets.ButtonInvisible(tabRect, false) && leftPanelMode != mode) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + leftPanelMode = mode; + } + + return cursor + tabWidth + PortraitTabMargin; + } + + protected void DrawPortrait() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectPortraitBody, BaseContent.WhiteTex); + + GUI.BeginGroup(SectionRectPortrait); + DrawPortraitTabs(); + + GUI.BeginGroup(SectionRectPortraitContent); + + GUI.color = Color.white; + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleCenter; + + if (leftPanelMode == LeftPanelMode.Appearance) { + DrawAppearance(customPawn); + } + else if (leftPanelMode == LeftPanelMode.Relations) { + PanelRelations.Draw(); + } + else if (leftPanelMode == LeftPanelMode.Health) { + PanelHealth.Draw(); + } + + + GUI.EndGroup(); + GUI.EndGroup(); + + GUI.color = Color.white; + } + + protected List colorSelectorColors = new List(); + protected void DrawColorSelector(float cursorY, ColorGenerator generator) + { + colorSelectorColors.Clear(); + if (generator != null) { + Type generatorType = generator.GetType(); + if (typeof(ColorGenerator_Options).Equals(generatorType)) { + ColorGenerator_Options gen = generator as ColorGenerator_Options; + foreach (ColorOption option in gen.options) { + if (option.only != ColorValidator.ColorEmpty) { + colorSelectorColors.Add(option.only); + } + } + } + else if (typeof(ColorGenerator_White).Equals(generatorType)) { + colorSelectorColors.Add(Color.white); + } + else if (typeof(ColorGenerator_Single).Equals(generatorType)) { + ColorGenerator_Single gen = generator as ColorGenerator_Single; + colorSelectorColors.Add(gen.color); + } + } + DrawColorSelector(cursorY, colorSelectorColors, true); + } + + protected static Vector2 SwatchSize = new Vector2(16, 16); + protected static Vector2 SwatchPosition = new Vector2(29, 320); + protected static Vector2 SwatchSpacing = new Vector2(22, 22); + protected static Color ColorSwatchBorder = new Color(0.77255f, 0.77255f, 0.77255f); + protected static Color ColorSwatchSelection = new Color(0.9098f, 0.9098f, 0.9098f); + protected void DrawColorSelector(float cursorY, List colors, bool allowAnyColor) + { + CustomPawn customPawn = CurrentPawn; + Color currentColor = customPawn.GetColor(selectedPawnLayer); + Rect rect = new Rect(SwatchPosition.x, cursorY, SwatchSize.x, SwatchSize.y); + foreach (Color color in colors) { + bool selected = (color == currentColor); + if (selected) { + Rect selectionRect = new Rect(rect.x - 2, rect.y - 2, SwatchSize.x + 4, SwatchSize.y + 4); + GUI.color = ColorSwatchSelection; + GUI.DrawTexture(selectionRect, BaseContent.WhiteTex); + } + + Rect borderRect = new Rect(rect.x - 1, rect.y - 1, SwatchSize.x + 2, SwatchSize.y + 2); + GUI.color = ColorSwatchBorder; + GUI.DrawTexture(borderRect, BaseContent.WhiteTex); + + GUI.color = color; + GUI.DrawTexture(rect, BaseContent.WhiteTex); + + if (!selected) { + if (Widgets.ButtonInvisible(rect, false)) { + SetColor(color); + currentColor = color; + } + } + + rect.x += SwatchSpacing.x; + if (rect.x >= 227) { + rect.y += SwatchSpacing.y; + rect.x = SwatchPosition.x; + } + } + + GUI.color = Color.white; + if (!allowAnyColor) { + return; + } + + if (rect.x != SwatchPosition.x) { + rect.x = SwatchPosition.x; + rect.y += SwatchSpacing.y; + } + rect.y += 4; + rect.width = 49; + rect.height = 49; + GUI.color = ColorSwatchBorder; + GUI.DrawTexture(rect, BaseContent.WhiteTex); + GUI.color = currentColor; + GUI.DrawTexture(rect.ContractedBy(1), BaseContent.WhiteTex); + + GUI.color = Color.red; + float r = GUI.HorizontalSlider(new Rect(rect.x + 56, rect.y - 1, 136, 16), currentColor.r, 0, 1); + GUI.color = Color.green; + float g = GUI.HorizontalSlider(new Rect(rect.x + 56, rect.y + 19, 136, 16), currentColor.g, 0, 1); + GUI.color = Color.blue; + float b = GUI.HorizontalSlider(new Rect(rect.x + 56, rect.y + 39, 136, 16), currentColor.b, 0, 1); + SetColor(new Color(r, g, b)); + GUI.color = Color.white; + } + + protected void SetColor(Color color) + { + CurrentPawn.SetColor(selectedPawnLayer, color); + } + + protected void DrawSkinSelector(float cursorY) + { + CustomPawn customPawn = CurrentPawn; + + int currentIndex = PawnColorUtils.GetColorLeftIndex(customPawn.SkinColor); + + Color currentColor = PawnColorUtils.Colors[currentIndex]; + Rect rect = new Rect(SwatchPosition.x, cursorY, SwatchSize.x, SwatchSize.y); + + int colorCount = PawnColorUtils.Colors.Length - 1; + int clickedIndex = -1; + for (int i=0; i= 227) { + rect.y += SwatchSpacing.y; + rect.x = SwatchPosition.x; + } + } + + GUI.color = Color.white; + + if (rect.x != SwatchPosition.x) { + rect.x = SwatchPosition.x; + rect.y += SwatchSpacing.y; + } + rect.y += 4; + rect.width = 49; + rect.height = 49; + GUI.color = ColorSwatchBorder; + GUI.DrawTexture(rect, BaseContent.WhiteTex); + GUI.color = customPawn.SkinColor; + GUI.DrawTexture(rect.ContractedBy(1), BaseContent.WhiteTex); + GUI.color = Color.white; + + float minValue = 0.000001f; + float t = PawnColorUtils.GetSkinLerpValue(customPawn.SkinColor); + if (t < minValue) { + t = minValue; + } + + if (clickedIndex != -1) { + t = minValue; + } + + float newValue = GUI.HorizontalSlider(new Rect(rect.x + 56, rect.y + 18, 136, 16), t, minValue, 1); + GUI.color = Color.white; + + if (t != newValue || clickedIndex != -1) { + if (clickedIndex != -1) { + currentIndex = clickedIndex; + } + customPawn.SkinColor = PawnColorUtils.FindColor(currentIndex, newValue); + } + } + + protected void DrawGraphics(Rect rect, int start, int end) { + CustomPawn customPawn = CurrentPawn; + for (int i=start; i<=end; i++) { + if (PawnLayers.IsApparelLayer(i) && customPawn.GetAcceptedApparel(i) == null) { + continue; + } + Graphic g = customPawn.graphics[i]; + if (g == null) { + continue; + } + Material material = g.MatFront; + if (material == null) { + continue; + } + GUI.color = customPawn.GetBlendedColor(i); + GUI.DrawTexture(rect, material.mainTexture); + } + } + + protected void DrawGraphic(Rect rect, int index) { + CustomPawn customPawn = CurrentPawn; + if (PawnLayers.IsApparelLayer(index) && customPawn.GetAcceptedApparel(index) == null) { + return; + } + Graphic g = customPawn.graphics[index]; + if (g == null) { + return; + } + Material material = g.MatFront; + if (material == null) { + return; + } + GUI.color = customPawn.GetBlendedColor(index); + GUI.DrawTexture(rect, material.mainTexture); + } + + protected void DrawOneGraphic(Rect rect, int index, int alternate) { + CustomPawn customPawn = CurrentPawn; + Graphic g = customPawn.graphics[index]; + Color color = customPawn.GetBlendedColor(index); + if (g == null || (PawnLayers.IsApparelLayer(index) && customPawn.GetAcceptedApparel(index) == null)) { + g = customPawn.graphics[alternate]; + color = customPawn.GetBlendedColor(alternate); + } + if (g == null) { + return; + } + Material material = g.MatFront; + if (material == null || (PawnLayers.IsApparelLayer(alternate) && customPawn.GetAcceptedApparel(alternate) == null)) { + return; + } + GUI.color = color; + GUI.DrawTexture(rect, material.mainTexture); + } + + protected void DrawPawn(Rect rect) + { + CustomPawn customPawn = CurrentPawn; + GUI.BeginGroup(rect); + + Rect bodyRect = new Rect(rect.width / 2 - 64, rect.height / 2 - 64, 128, 128); + Rect headRect = new Rect(bodyRect.x, bodyRect.y - 30, 128, 128); + List graphics = customPawn.graphics; + DrawGraphics(bodyRect, PawnLayers.BodyType, PawnLayers.TopClothingLayer); + DrawGraphic(headRect, PawnLayers.HeadType); + DrawOneGraphic(headRect, PawnLayers.Hat, PawnLayers.Hair); + + GUI.EndGroup(); + GUI.color = Color.white; + } + + protected static Color BodyPartReplacedFieldColor = new Color(0.451f, 0.451f, 0.8118f); + protected static Color BodyPartBoxBorderColor = new Color(0.2471f, 0.2471f, 0.2471f); + protected static Rect BodyPartBoxRect = new Rect(16, 40, 218, 358); + protected static Rect BodyPartBoxScrollRect = new Rect(0, 0, BodyPartBoxRect.width - 2, BodyPartBoxRect.height - 2); + protected static Rect OldInjuriesFieldRect = new Rect(18, BodyPartBoxRect.y + BodyPartBoxRect.height + 12, 216, 32); + /* + protected void DrawHealth() + { + PawnFacade pawn = CurrentPawn; + GUI.color = BodyPartBoxBorderColor; + Rect rect = BodyPartBoxRect; + Widgets.DrawBox(rect, 1); + GUI.color = Color.white; + Rect innerRect = rect.ContractedBy(1); + GUI.BeginGroup(innerRect); + + Rect scrollRect = BodyPartBoxScrollRect; + Rect viewRect = new Rect(scrollRect.x, scrollRect.y, scrollRect.width - 16, bionicsScrollHeight); + Widgets.BeginScrollView(scrollRect, ref bionicsScrollPosition, viewRect); + + Rect partRect = new Rect(0, 0, 216, 56); + Rect labelRect = new Rect(20, 4, 180, 24); + Rect fieldRect = new Rect(20, 21, 163, 28); + bool odd = true; + foreach (BodyPart bodyPart in this.bodyParts) { + + if (!odd) { + GUI.color = new Color(0.1882f, 0.1882f, 0.1882f); + GUI.DrawTexture(partRect, BaseContent.WhiteTex); + } + odd = !odd; + + bool invalid = false; + foreach (var p in bodyPart.ancestors) { + if (pawn.IsBodyPartReplaced(p)) { + invalid = true; + break; + } + } + + if (!invalid) { + GUI.color = TextColor; + } + else { + GUI.color = new Color(TextColor.r, TextColor.g, TextColor.b, 0.3f); + } + Text.Font = GameFont.Tiny; + Widgets.Label(labelRect, bodyPart.bodyPartRecord.def.LabelCap); + Text.Font = GameFont.Small; + GUI.color = Color.white; + + Implant selectedOption = FindSelectedBodyPartOption(bodyPart); + if (!invalid) { + DrawFieldSelector(fieldRect, selectedOption == null ? "EdB.NormalBodyPart".Translate() : (selectedOption.label), + delegate { + SetBodyPartOption(bodyPart, FindNextBodyPartOption(bodyPart, 1)); + }, + delegate { + SetBodyPartOption(bodyPart, FindNextBodyPartOption(bodyPart, -1)); + }, + selectedOption == null ? TextColor : BodyPartReplacedFieldColor + ); + } + else { + DrawDisabledFieldSelector(fieldRect, selectedOption == null ? "EdB.NormalBodyPart".Translate() : (selectedOption.label), + selectedOption == null ? TextColor : BodyPartReplacedFieldColor); + } + + partRect.y += partRect.height; + labelRect.y += partRect.height; + fieldRect.y += partRect.height; + } + + if (Event.current.type == EventType.Layout) { + bionicsScrollHeight = partRect.y; + } + + GUI.EndScrollView(); + GUI.EndGroup(); + + GUI.color = TextColor; + bool oldInjuriesValue = pawn.OldInjuries; + Vector2 size = Text.CalcSize("EdB.OldInjuries".Translate()); + labelRect = OldInjuriesFieldRect; + labelRect.x += labelRect.width / 2 - (size.x + 20 + 16) / 2; + Widgets.Label(labelRect, "EdB.OldInjuries".Translate()); + size.x += 16; + size.y = 0; + Widgets.Checkbox(labelRect.min + size, ref oldInjuriesValue, 24, false); + + pawn.OldInjuries = oldInjuriesValue; + } + */ + + /* + public void SetBodyPartOption(BodyPart part, Implant option) { + PawnFacade pawn = CurrentPawn; + if (option != null) { + pawn.implants[part.bodyPartRecord] = option; + } + else { + if (pawn.implants.ContainsKey(part.bodyPartRecord)) { + pawn.implants.Remove(part.bodyPartRecord); + } + } + } + + public Implant FindSelectedBodyPartOption(BodyPart part) + { + PawnFacade pawn = CurrentPawn; + Implant selection; + if (pawn.implants.TryGetValue(part.bodyPartRecord, out selection)) { + return selection; + } + return null; + } + + public int FindBodyPartOptionIndex(BodyPart part) + { + PawnFacade pawn = CurrentPawn; + Implant selection; + if (!pawn.implants.TryGetValue(part.bodyPartRecord, out selection)) { + return -1; + } + int count = part.options.Count; + for (int i = 0; i < count; i++) { + if (part.options[i].Equals(selection)) { + return i; + } + } + return -1; + } + + public Implant FindNextBodyPartOption(BodyPart part, int direction) + { + int index = FindBodyPartOptionIndex(part); + index += direction; + if (index >= part.options.Count) { + return null; + } + else if (index < -1) { + index = part.options.Count - 1; + } + if (index == -1) { + return null; + } + return part.options[index]; + } + + */ + + protected static Rect RectGenderButton = new Rect(0, 0, 100, 28); + protected static Rect RectAgeLabel = new Rect(108, 2, 45, 28); + protected static Rect RectBiologicalAgeField = new Rect(RectAgeLabel.x + RectAgeLabel.width + 18, 0, 32, 28); + protected static Rect RectChronologicalAgeField = new Rect(RectBiologicalAgeField.x + RectBiologicalAgeField.width + 33, 0, 48, 28); + protected void DrawGenderAndAge() + { + CustomPawn customPawn = CurrentPawn; + chronologicalAgeDragSlider.minValue = customPawn.BiologicalAge; + biologicalAgeDragSlider.maxValue = customPawn.ChronologicalAge < maxAge ? customPawn.ChronologicalAge : maxAge; + + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectGenderAndAge, BaseContent.WhiteTex); + GUI.BeginGroup(new Rect(SectionRectGenderAndAge.x + SectionMargin.x - 6, SectionRectGenderAndAge.y + SectionMargin.y, + SectionRectGenderAndAge.width - SectionMargin.x * 2 + 12, SectionRectGenderAndAge.height - SectionMargin.y * 2)); + + GUI.color = Color.white; + string label = customPawn.Gender == Gender.Male ? "Male".Translate() : "Female".Translate(); + if (Widgets.ButtonText(RectGenderButton, label.CapitalizeFirst(), true, false, true)) { + List list = new List(); + list.Add(new FloatMenuOption("Male".Translate().CapitalizeFirst(), delegate { this.SetGender(Gender.Male); }, MenuOptionPriority.Medium, null, null, 0, null)); + list.Add(new FloatMenuOption("Female".Translate().CapitalizeFirst(), delegate { this.SetGender(Gender.Female); }, MenuOptionPriority.Medium, null, null, 0, null)); + Find.WindowStack.Add(new FloatMenu(list, null, false)); + } + + GUI.color = ColorText; + Text.Anchor = TextAnchor.MiddleRight; + Widgets.Label(RectAgeLabel, "EdB.Age".Translate()); + + // Biological Age + GUI.color = Color.white; + Rect fieldRect = RectBiologicalAgeField; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + biologicalAgeDragSlider.OnGUI(fieldRect, customPawn.BiologicalAge, (int value) => { + customPawn.BiologicalAge = value; + }); + bool dragging = DragSlider.IsDragging(); + + Rect buttonRect = new Rect(fieldRect.x - 17, fieldRect.y + 6, 16, 16); + if (!dragging && buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + int age = customPawn.BiologicalAge - amount; + if (age < minAge) { + age = minAge; + } + else if (age > maxAge || age > customPawn.ChronologicalAge) { + if (age > maxAge) { + age = maxAge; + } + else { + age = customPawn.ChronologicalAge; + } + } + customPawn.BiologicalAge = age; + } + + buttonRect = new Rect(fieldRect.x + fieldRect.width + 1, fieldRect.y + 6, 16, 16); + if (!dragging &&buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + int age = customPawn.BiologicalAge + amount; + if (age < minAge) { + age = minAge; + } + else if (age > maxAge || age > customPawn.ChronologicalAge) { + if (age > maxAge) { + age = maxAge; + } + else { + age = customPawn.ChronologicalAge; + } + } + customPawn.BiologicalAge = age; + } + + // Chronological Age + GUI.color = Color.white; + fieldRect = RectChronologicalAgeField; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + chronologicalAgeDragSlider.OnGUI(fieldRect, customPawn.ChronologicalAge, (int value) => { + customPawn.ChronologicalAge = value; + }); + dragging = DragSlider.IsDragging(); + + buttonRect = new Rect(fieldRect.x - 17, fieldRect.y + 6, 16, 16); + if (!dragging && buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + int age = customPawn.ChronologicalAge - amount; + if (age < customPawn.BiologicalAge) { + age = customPawn.BiologicalAge; + } + if (age > maxChronologicalAge) { + age = maxChronologicalAge; + } + customPawn.ChronologicalAge = age; + } + + buttonRect = new Rect(fieldRect.x + fieldRect.width + 1, fieldRect.y + 6, 16, 16); + if (!dragging &&buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + int age = customPawn.ChronologicalAge + amount; + if (age < customPawn.BiologicalAge) { + age = customPawn.BiologicalAge; + } + if (age > maxChronologicalAge) { + age = maxChronologicalAge; + } + customPawn.ChronologicalAge = age; + } + + GUI.EndGroup(); + } + + protected void SetGender(Gender gender) + { + CustomPawn customPawn = CurrentPawn; + customPawn.Gender = gender; + } + + protected void DrawBackstory() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectBackstory, BaseContent.WhiteTex); + + GUI.BeginGroup(new Rect(SectionRectBackstory.x + SectionMargin.x, SectionRectBackstory.y + SectionMargin.y, + SectionRectBackstory.width - SectionMargin.x * 2 + 6, SectionRectBackstory.height - SectionMargin.y * 2)); + + GUI.color = TextColor; + Text.Font = GameFont.Medium; + Text.Anchor = TextAnchor.UpperLeft; + Widgets.Label(new Rect(0, 0, 300, 40), "Backstory".Translate()); + + Text.Font = GameFont.Small; + Widgets.Label(new Rect(0, 36, 300, 32), "Childhood".Translate()); + + GUI.color = Color.white; + Rect fieldRect = new Rect(90, 32, 188, 28); + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + Text.Anchor = TextAnchor.MiddleCenter; + fieldRect.y += 2; + fieldRect.x -= 2; + if (!fieldRect.Contains(Event.current.mousePosition)) { + GUI.color = TextColor; + } + Widgets.Label(fieldRect, customPawn.Childhood.title); + GUI.color = Color.white; + fieldRect.y -= 2; + fieldRect.x += 2; + + if (problemBackstories.Contains(customPawn.Childhood)) { + fieldRect.width -= 30; + TooltipHandler.TipRegion(fieldRect, customPawn.Childhood.FullDescriptionFor(customPawn.Pawn)); + if (Widgets.ButtonInvisible(fieldRect, false)) { + ShowBackstoryDialog(customPawn, BackstorySlot.Childhood); + } + fieldRect.width += 30; + Rect problemRect = new Rect(fieldRect.x + fieldRect.width - 26, fieldRect.y + 4, 20, 20); + GUI.DrawTexture(problemRect, Textures.TextureAlertSmall); + TooltipHandler.TipRegion(problemRect, "EdB.BackstoryWarning".Translate()); + } + else { + TooltipHandler.TipRegion(fieldRect, customPawn.Childhood.FullDescriptionFor(customPawn.Pawn)); + if (Widgets.ButtonInvisible(fieldRect, false)) { + ShowBackstoryDialog(customPawn, BackstorySlot.Childhood); + } + } + + Rect buttonRect = new Rect(fieldRect.x - 17, 38, 16, 16); + if (buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + this.SelectPreviousBackstory(0); + } + + buttonRect = new Rect(fieldRect.x + fieldRect.width + 1, 38, 16, 16); + if (buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + this.SelectNextBackstory(0); + } + + + GUI.color = Color.white; + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.UpperLeft; + Widgets.Label(new Rect(0, 70, 300, 32), "Adulthood".Translate()); + fieldRect.y += 35; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + Text.Anchor = TextAnchor.MiddleCenter; + fieldRect.y += 2; + fieldRect.x -= 2; + if (!fieldRect.Contains(Event.current.mousePosition)) { + GUI.color = TextColor; + } + Widgets.Label(fieldRect, customPawn.Adulthood.title); + GUI.color = Color.white; + fieldRect.y -= 2; + fieldRect.x += 2; + + if (problemBackstories.Contains(customPawn.Adulthood)) { + fieldRect.width -= 30; + TooltipHandler.TipRegion(new Rect(fieldRect.x, fieldRect.y, fieldRect.width - 30, fieldRect.height), customPawn.Adulthood.FullDescriptionFor(customPawn.Pawn)); + if (Widgets.ButtonInvisible(fieldRect, false)) { + ShowBackstoryDialog(customPawn, BackstorySlot.Adulthood); + } + fieldRect.width += 30; + + Rect problemRect = new Rect(fieldRect.x + fieldRect.width - 26, fieldRect.y + 4, 20, 20); + GUI.DrawTexture(problemRect, Textures.TextureAlertSmall); + TooltipHandler.TipRegion(problemRect, "EdB.BackstoryWarning".Translate()); + } + else { + TooltipHandler.TipRegion(fieldRect, customPawn.Adulthood.FullDescriptionFor(customPawn.Pawn)); + if (Widgets.ButtonInvisible(fieldRect, false)) { + ShowBackstoryDialog(customPawn, BackstorySlot.Adulthood); + } + } + + buttonRect = new Rect(fieldRect.x - 17, 72, 16, 16); + if (buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + this.SelectPreviousBackstory(1); + } + + buttonRect = new Rect(fieldRect.x + fieldRect.width + 1, 72, 16, 16); + if (buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + this.SelectNextBackstory(1); + } + + GUI.EndGroup(); + + // Random button + Rect randomRect = new Rect(SectionRectBackstory.x + SectionRectBackstory.width - 32, SectionRectBackstory.y + 9, 22, 22); + if (randomRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(randomRect, Textures.TextureButtonRandom); + if (Widgets.ButtonInvisible(randomRect, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + randomizer.RandomizeBackstory(customPawn); + } + } + + protected void ShowHeadDialog() + { + CustomPawn customPawn = CurrentPawn; + List heads = customPawn.Gender == Gender.Male ? maleHeads : femaleHeads; + Dialog_Options dialog = new Dialog_Options(heads) { + NameFunc = (string head) => { + return GetHeadLabel(head); + }, + SelectedFunc = (string head) => { + return customPawn.HeadGraphicPath == head; + }, + SelectAction = (string head) => { + customPawn.HeadGraphicPath = head; + this.pawnLayerLabel = GetHeadLabel(customPawn.HeadGraphicPath); + }, + CloseAction = () => { } + }; + Find.WindowStack.Add(dialog); + } + + protected void ShowHairDialog() + { + CustomPawn customPawn = CurrentPawn; + List hairDefs = customPawn.Gender == Gender.Male ? maleHairDefs : femaleHairDefs; + Dialog_Options dialog = new Dialog_Options(hairDefs) { + NameFunc = (HairDef hairDef) => { + return hairDef.LabelCap; + }, + SelectedFunc = (HairDef hairDef) => { + return customPawn.HairDef == hairDef; + }, + SelectAction = (HairDef hairDef) => { + customPawn.HairDef = hairDef; + this.pawnLayerLabel = hairDef.LabelCap; + }, + CloseAction = () => { } + }; + Find.WindowStack.Add(dialog); + } + + protected void ShowApparelDialog(int layer) + { + CustomPawn customPawn = CurrentPawn; + List apparelList = this.apparelLists[layer]; + + Dialog_Options dialog = new Dialog_Options(apparelList) { + IncludeNone = true, + NameFunc = (ThingDef apparel) => { + return apparel.LabelCap; + }, + SelectedFunc = (ThingDef apparel) => { + return customPawn.GetSelectedApparel(layer) == apparel; + }, + SelectAction = (ThingDef apparel) => { + if (apparel != null) { + this.pawnLayerLabel = apparel.LabelCap; + if (apparel.MadeFromStuff) { + if (customPawn.GetSelectedStuff(layer) == null) { + customPawn.SetSelectedStuff(layer, apparelStuffLookup[apparel][0]); + } + } + else { + customPawn.SetSelectedStuff(layer, null); + } + customPawn.SetSelectedApparel(layer, apparel); + } + else { + customPawn.SetSelectedApparel(layer, null); + customPawn.SetSelectedStuff(layer, null); + this.pawnLayerLabel = "EdB.None".Translate(); + } + } + }; + Find.WindowStack.Add(dialog); + } + + protected void ShowApparelStuffDialog(int layer) + { + CustomPawn customPawn = CurrentPawn; + ThingDef apparel = customPawn.GetSelectedApparel(layer); + if (apparel == null) { + return; + } + List stuffList = this.apparelStuffLookup[apparel]; + Dialog_Options dialog = new Dialog_Options(stuffList) { + NameFunc = (ThingDef stuff) => { + return stuff.LabelCap; + }, + SelectedFunc = (ThingDef stuff) => { + return customPawn.GetSelectedStuff(layer) == stuff; + }, + SelectAction = (ThingDef stuff) => { + customPawn.SetSelectedStuff(layer, stuff); + } + }; + Find.WindowStack.Add(dialog); + } + + protected void ShowBackstoryDialog(CustomPawn customPawn, BackstorySlot slot) + { + Backstory originalBackstory = customPawn.Childhood; + Backstory selectedBackstory = originalBackstory; + Dialog_Options dialog = new Dialog_Options(slot == BackstorySlot.Childhood ? this.sortedChildhoodBackstories : this.sortedAdulthoodBackstories) { + NameFunc = (Backstory backstory) => { + return backstory.title; + }, + DescriptionFunc = (Backstory backstory) => { + return backstory.FullDescriptionFor(customPawn.Pawn); + }, + SelectedFunc = (Backstory backstory) => { + return selectedBackstory == backstory; + }, + SelectAction = (Backstory backstory) => { + selectedBackstory = backstory; + }, + CloseAction = () => { + if (slot == BackstorySlot.Childhood) { + customPawn.Childhood = selectedBackstory; + } + else { + customPawn.Adulthood = selectedBackstory; + } + } + }; + Find.WindowStack.Add(dialog); + } + + protected void DrawTraits() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectTraits, BaseContent.WhiteTex); + + GUI.BeginGroup(new Rect(SectionRectTraits.x + SectionMargin.x, SectionRectTraits.y + SectionMargin.y, + SectionRectTraits.width - SectionMargin.x * 2, SectionRectTraits.height - SectionMargin.y * 2 + 100)); + + GUI.color = TextColor; + Text.Font = GameFont.Medium; + Text.Anchor = TextAnchor.UpperLeft; + Widgets.Label(new Rect(0, 0, 300, 40), "Traits".Translate()); + Text.Font = GameFont.Small; + + Rect fieldRect = new Rect(13, 35, 258, 28); + int traitIndex = 0; + foreach (Trait trait in customPawn.Traits) { + int localIndex = traitIndex; + GUI.color = Color.white; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + if (trait != null) { + Text.Anchor = TextAnchor.MiddleCenter; + fieldRect.y += 2; + fieldRect.x -= 2; + if (!fieldRect.Contains(Event.current.mousePosition)) { + GUI.color = TextColor; + } + Widgets.Label(fieldRect, trait.LabelCap); + GUI.color = Color.white; + fieldRect.y -= 2; + fieldRect.x += 2; + TooltipHandler.TipRegion(fieldRect, trait.TipString(customPawn.Pawn)); + } + if (Widgets.ButtonInvisible(fieldRect, false)) { + + Trait originalTrait = customPawn.GetTrait(traitIndex); + Trait selectedTrait = originalTrait; + Dialog_Options dialog = new Dialog_Options(this.sortedTraits) { + IncludeNone = true, + NameFunc = (Trait t) => { + return t.LabelCap; + }, + DescriptionFunc = (Trait t) => { + return t.TipString(customPawn.Pawn); + }, + SelectedFunc = (Trait t) => { + return selectedTrait == t; + }, + SelectAction = (Trait t) => { + selectedTrait = t; + }, + EnabledFunc = (Trait t) => { + if (t == null) { + return originalTrait != null; + } + else if ((originalTrait == null || !originalTrait.Label.Equals(t.Label)) && customPawn.HasTrait(t)) { + return false; + } + else { + return true; + } + }, + CloseAction = () => { + customPawn.SetTrait(localIndex, selectedTrait); + } + }; + Find.WindowStack.Add(dialog); + + + //Find.WindowStack.Add(new Dialog_Traits(customPawn, localIndex, this.sortedTraits)); + } + + Rect buttonRect = new Rect(fieldRect.x - 17, fieldRect.y + 6, 16, 16); + if (buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + this.SelectPreviousTrait(traitIndex); + } + + buttonRect = new Rect(fieldRect.x + fieldRect.width + 1, fieldRect.y + 6, 16, 16); + if (buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + this.SelectNextTrait(traitIndex); + } + + traitIndex++; + fieldRect.y += 34; + } + + GUI.EndGroup(); + + // Random button + Rect randomRect = new Rect(SectionRectTraits.x + SectionRectTraits.width - 32, SectionRectTraits.y + 9, 22, 22); + if (randomRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(randomRect, Textures.TextureButtonRandom); + if (Widgets.ButtonInvisible(randomRect, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + randomizer.RandomizeTraits(customPawn); + } + } + + protected void DrawIncapable() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectIncapable, BaseContent.WhiteTex); + + GUI.BeginGroup(new Rect(SectionRectIncapable.x + SectionMargin.x, SectionRectIncapable.y + SectionMargin.y, + SectionRectIncapable.width - SectionMargin.x * 2, SectionRectIncapable.height - SectionMargin.y * 2)); + + GUI.color = TextColor; + Text.Font = GameFont.Medium; + Text.Anchor = TextAnchor.UpperLeft; + Widgets.Label(new Rect(0, 0, 300, 40), "IncapableOf".Translate()); + + string incapable = customPawn.IncapableOf; + if (incapable == null) { + incapable = "EdB.None".Translate(); + } + Text.WordWrap = true; + Text.Font = GameFont.Small; + Widgets.Label(new Rect(0.0f, 32, 300, 999f), incapable); + + GUI.EndGroup(); + } + + protected static Rect RectButtonClearSkills = new Rect(242, 3, 20, 20); + protected static Rect RectButtonResetSkills = new Rect(276, 2, 23, 21); + protected void DrawSkills() + { + CustomPawn customPawn = CurrentPawn; + GUI.color = SectionBackgroundColor; + GUI.DrawTexture(SectionRectSkills, BaseContent.WhiteTex); + + Vector2 sectionMargin = new Vector2(SectionMargin.x - 8, SectionMargin.y); + GUI.BeginGroup(new Rect(SectionRectSkills.x + sectionMargin.x, SectionRectSkills.y + sectionMargin.y, + SectionRectSkills.width - sectionMargin.x * 2, SectionRectSkills.height - sectionMargin.y * 2)); + + GUI.color = TextColor; + Text.Font = GameFont.Medium; + Text.Anchor = TextAnchor.UpperLeft; + Widgets.Label(new Rect(5, 0, 300, 40), "Skills".Translate()); + + // Clear button + if (RectButtonClearSkills.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(RectButtonClearSkills, Textures.TextureButtonClearSkills); + if (Widgets.ButtonInvisible(RectButtonClearSkills, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + CurrentPawn.ClearSkills(); + } + + // Reset button + if (RectButtonResetSkills.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(RectButtonResetSkills, Textures.TextureButtonReset); + if (Widgets.ButtonInvisible(RectButtonResetSkills, false)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + CurrentPawn.ResetSkills(); + } + + int skillCount = customPawn.Pawn.skills.skills.Count; + float spacing = 28; + float height = spacing * skillCount; + GUI.BeginGroup(new Rect(0f, 38f, 308f, 338)); + skillScrollView.Begin(new Rect(0,0,308f, 338)); + + Text.Font = GameFont.Small; + foreach (SkillDef current in DefDatabase.AllDefs) { + float x = Text.CalcSize(current.skillLabel).x; + if (x > skillLevelLabelWidth) { + skillLevelLabelWidth = x; + } + } + Rect skillsRect = new Rect(0f, 0f, SkillWidth, height); + if (skillScrollView.ScrollbarsVisible) { + skillsRect.width -= 16; + } + GUI.BeginGroup(skillsRect); + Vector2 offset = new Vector2(0, 0); + int skillIndex = 0; + foreach (SkillRecord current2 in customPawn.Pawn.skills.skills) { + float y = (float)skillIndex * spacing + offset.y; + DrawSkill(current2, new Vector2(offset.x, y), skillsRect.width); + skillIndex++; + } + GUI.EndGroup(); + + // Increase/Decrease buttons. + Rect rect = new Rect(skillsRect.width, 4, 16, 16); + for (int i = 0; i < skillCount; i++) { + + if (customPawn.IsDisabled(customPawn.Pawn.skills.skills[i].def)) { + rect.y += spacing; + continue; + } + + if (rect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(rect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(rect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + DecreaseSkill(i); + } + + rect.x += 16; + if (rect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(rect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(rect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + IncreaseSkill(i); + } + + rect.y += spacing; + rect.x = skillsRect.width; + } + + // Draw passions. + Rect position = new Rect(skillLevelLabelWidth + 10, 2, 24, 24); + for (int i = 0; i < skillCount; i++) { + SkillRecord skill = customPawn.Pawn.skills.skills[i]; + if (!customPawn.IsDisabled(skill.def)) { + Passion passion = customPawn.passions[skill.def]; + if (passion > Passion.None) { + Texture2D image = (passion != Passion.Major) ? Textures.TexturePassionMinor : Textures.TexturePassionMajor; + GUI.color = Color.white; + GUI.DrawTexture(position, image); + } + else { + GUI.color = Color.white; + GUI.DrawTexture(position, Textures.TexturePassionNone); + } + if (Widgets.ButtonInvisible(position, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + if (Event.current.button != 1) { + IncreasePassion(i); + } + else { + DecreasePassion(i); + } + } + } + position.y += spacing; + } + + float cursor = position.y; + this.skillScrollView.End(cursor); + GUI.EndGroup(); + + GUI.EndGroup(); + GUI.color = Color.white; + } + + private static Color ColorSkillDisabled = new Color(1f, 1f, 1f, 0.5f); + + public const float SkillWidth = 275; + public const float SkillYSpacing = 3; + public const float SkillHeight = 24; + public const float SkillLevelNumberX = 140; + public const float SkillLeftEdgeMargin = 6; + + private static float skillLevelLabelWidth = -1; + + public static void FillableBar(Rect screenRect, float fillPercent, Texture2D fillTex) + { + screenRect.width *= fillPercent; + GUI.DrawTexture(screenRect, fillTex); + } + + private void DrawSkill(SkillRecord skill, Vector2 topLeft, float skillWidth) + { + CustomPawn customPawn = CurrentPawn; + Rect rect = new Rect(topLeft.x, topLeft.y, skillWidth, 24); + GUI.BeginGroup(rect); + Text.Anchor = TextAnchor.UpperLeft; + Rect rect2 = new Rect(6, -1, skillLevelLabelWidth + 6, rect.height + 4); + rect2.yMin += 3; + GUI.color = ColorText; + Widgets.Label(rect2, skill.def.skillLabel); + Rect position = new Rect(rect2.xMax, 0, 24, 24); + int level = customPawn.GetSkillLevel(skill.def); + bool disabled = customPawn.IsDisabled(skill.def); + if (!disabled) { + float barSize = (level > 0 ? (float)level : 0) / 20f; + Rect screenRect = new Rect(position.xMax, 0, rect.width - position.xMax, rect.height); + FillableBar(screenRect, barSize, Textures.TextureSkillBarFill); + + int baseLevel = customPawn.GetBaseSkillLevel(skill.def); + float baseBarSize = (baseLevel > 0 ? (float)baseLevel : 0) / 20f; + screenRect = new Rect(position.xMax, 0, rect.width - position.xMax, rect.height); + FillableBar(screenRect, baseBarSize, Textures.TextureSkillBarFill); + + GUI.color = new Color(0.25f, 0.25f, 0.25f); + Widgets.DrawBox(screenRect, 1); + GUI.color = ColorText; + + if (Widgets.ButtonInvisible(screenRect, false)) { + Vector2 pos = Event.current.mousePosition; + float x = pos.x - screenRect.x; + int value = (int) Math.Round((x / screenRect.width) * 20); + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + SetSkillLevel(skill, value); + } + } + Rect rect3 = new Rect(position.xMax + 4, 0, 999, rect.height); + rect3.yMin += 3; + string label; + if (disabled) { + GUI.color = ColorSkillDisabled; + label = "-"; + } + else { + label = GenString.ToStringCached(level); + } + Text.Anchor = TextAnchor.MiddleLeft; + Widgets.Label(rect3, label); + GUI.color = Color.white; + GUI.EndGroup(); + TooltipHandler.TipRegion(rect, new TipSignal(GetSkillDescription(skill), skill.def.GetHashCode() * 397945)); + } + + // EdB: Copy of private static SkillUI.GetSkillDescription(). + private static string GetSkillDescription(SkillRecord sk) + { + StringBuilder stringBuilder = new StringBuilder(); + if (sk.TotallyDisabled) { + stringBuilder.Append("DisabledLower".Translate().CapitalizeFirst()); + } + else { + stringBuilder.AppendLine(string.Concat(new object[] { + "Level".Translate(), + " ", + sk.level, + ": ", + sk.LevelDescriptor + })); + stringBuilder.Append("Passion".Translate() + ": "); + switch (sk.passion) { + case Passion.None: + stringBuilder.Append("PassionNone".Translate(new object[] { + "0.3" + })); + break; + case Passion.Minor: + stringBuilder.Append("PassionMinor".Translate(new object[] { + "1.0" + })); + break; + case Passion.Major: + stringBuilder.Append("PassionMajor".Translate(new object[] { + "1.5" + })); + break; + } + } + stringBuilder.AppendLine(); + stringBuilder.AppendLine(); + stringBuilder.Append(sk.def.description); + return stringBuilder.ToString(); + } + + protected void EnableBionicsMode() { + bionicsMode = true; + } + + protected void ChangePawnLayer(int layer) + { + bionicsMode = false; + this.selectedPawnLayer = layer; + } + + protected string PawnLayerLabel + { + get { + CustomPawn customPawn = CurrentPawn; + //if (pawnLayerLabelIndex == selectedPawnLayer && pawnLayerLabelModel == customPawn && pawnLayerLabel != null) { + // return pawnLayerLabel; + //} + + string label = "EdB.None".Translate(); + if (selectedPawnLayer == PawnLayers.BodyType) { + label = bodyTypeLabels[customPawn.BodyType]; + } + else if (selectedPawnLayer == PawnLayers.HeadType) { + label = GetHeadLabel(customPawn.HeadGraphicPath); + } + else if (selectedPawnLayer == PawnLayers.Hair) { + if (customPawn.HairDef != null) { + label = customPawn.HairDef.LabelCap; + } + } + else { + label = null; + ThingDef def = customPawn.GetSelectedApparel(selectedPawnLayer); + if (def != null) { + int index = this.apparelLists[selectedPawnLayer].IndexOf(def); + if (index > -1) { + label = this.apparelLists[selectedPawnLayer][index].label; + } + } + else { + label = "EdB.None".Translate(); + } + } + pawnLayerLabelIndex = selectedPawnLayer; + pawnLayerLabelModel = customPawn; + pawnLayerLabel = label; + return label; + } + } + + protected void SelectNextHead(int direction) + { + CustomPawn customPawn = CurrentPawn; + List heads = customPawn.Gender == Gender.Male ? maleHeads : femaleHeads; + int index = heads.IndexOf(customPawn.HeadGraphicPath); + if (index == -1) { + Log.Warning("Could not find the current pawn's head in the cache of head graphics: " + customPawn.HeadGraphicPath); + return; + } + index += direction; + if (index < 0) { + index = heads.Count - 1; + } + else if (index >= heads.Count) { + index = 0; + } + customPawn.HeadGraphicPath = heads[index]; + this.pawnLayerLabel = GetHeadLabel(customPawn.HeadGraphicPath); + } + + protected void SelectNextHair(int direction) + { + CustomPawn customPawn = CurrentPawn; + List hairDefs = customPawn.Gender == Gender.Male ? maleHairDefs : femaleHairDefs; + int index = hairDefs.IndexOf(customPawn.HairDef); + index += direction; + if (index < 0) { + index = hairDefs.Count - 1; + } + else if (index >= hairDefs.Count) { + index = 0; + } + customPawn.HairDef = hairDefs[index]; + this.pawnLayerLabel = customPawn.HairDef.label; + } + + protected void SelectNextApparel(int direction) + { + CustomPawn customPawn = CurrentPawn; + int layer = this.selectedPawnLayer; + List apparelList = this.apparelLists[layer]; + int index = apparelList.IndexOf(customPawn.GetSelectedApparel(layer)); + index += direction; + if (index < -1) { + index = apparelList.Count - 1; + } + else if (index >= apparelList.Count) { + index = -1; + } + if (index > -1) { + this.pawnLayerLabel = apparelList[index].label; + if (apparelList[index].MadeFromStuff) { + if (customPawn.GetSelectedStuff(layer) == null) { + customPawn.SetSelectedStuff(layer, apparelStuffLookup[apparelList[index]][0]); + } + } + else { + customPawn.SetSelectedStuff(layer, null); + } + customPawn.SetSelectedApparel(layer, apparelList[index]); + } + else { + customPawn.SetSelectedApparel(layer, null); + customPawn.SetSelectedStuff(layer, null); + this.pawnLayerLabel = "EdB.None".Translate(); + } + } + + protected string GetHeadLabel(string path) + { + string[] values = path.Split(new string[] { "_" }, StringSplitOptions.None); + return values[values.Count() - 2] + ", " + values[values.Count() - 1]; + } + + protected void SelectNextTrait(int traitIndex) + { + CustomPawn customPawn = CurrentPawn; + Trait currentTrait = customPawn.GetTrait(traitIndex); + int index = -1; + if (currentTrait != null) { + index = traits.FindIndex((Trait t) => { + return t.Label.Equals(currentTrait.Label); + }); + } + int count = 0; + do { + index++; + if (index >= traits.Count) { + index = -1; + } + if (++count > traits.Count + 1) { + index = -1; + break; + } + } + while (index != -1 && customPawn.HasTrait(traits[index])); + + Trait newTrait = null; + if (index > -1) { + newTrait = traits[index]; + } + customPawn.SetTrait(traitIndex, newTrait); + } + + protected void SelectPreviousTrait(int traitIndex) + { + CustomPawn customPawn = CurrentPawn; + Trait currentTrait = customPawn.GetTrait(traitIndex); + int index = -1; + if (currentTrait != null) { + index = traits.FindIndex((Trait t) => { + return t.Label.Equals(currentTrait.Label); + }); + } + int count = 0; + do { + index--; + if (index < -1) { + index = traits.Count - 1; + } + if (++count > traits.Count + 1) { + index = -1; + break; + } + } + while (index != -1 && customPawn.HasTrait(traits[index])); + + Trait newTrait = null; + if (index > -1) { + newTrait = traits[index]; + } + customPawn.SetTrait(traitIndex, newTrait); + } + + protected void ClearTrait(int traitIndex) + { + CustomPawn customPawn = CurrentPawn; + customPawn.SetTrait(traitIndex, null); + } + + protected void SelectNextBackstory(int backstoryIndex) + { + CustomPawn customPawn = CurrentPawn; + int index; + if (backstoryIndex == 0) { + index = childhoodBackstories.FindIndex((Backstory b) => { return b.uniqueSaveKey.Equals(customPawn.Childhood.uniqueSaveKey); }); + if (index > -1) { + int newIndex = index + 1; + if (newIndex >= childhoodBackstories.Count) { + newIndex = 0; + } + customPawn.Childhood = childhoodBackstories[newIndex]; + } + else { + customPawn.Childhood = childhoodBackstories[0]; + } + } + else { + index = adulthoodBackstories.FindIndex((Backstory b) => { return b.uniqueSaveKey.Equals(customPawn.Adulthood.uniqueSaveKey); }); + if (index > -1) { + int newIndex = index + 1; + if (newIndex >= adulthoodBackstories.Count) { + newIndex = 0; + } + customPawn.Adulthood = adulthoodBackstories[newIndex]; + } + else { + customPawn.Adulthood = adulthoodBackstories[0]; + } + } + } + + protected void SelectPreviousBackstory(int backstoryIndex) + { + CustomPawn customPawn = CurrentPawn; + int index; + if (backstoryIndex == 0) { + index = childhoodBackstories.FindIndex((Backstory b) => { return b.uniqueSaveKey.Equals(customPawn.Childhood.uniqueSaveKey); }); + if (index > -1) { + int newIndex = index - 1; + if (newIndex < 0) { + newIndex = childhoodBackstories.Count - 1; + } + customPawn.Childhood = childhoodBackstories[newIndex]; + } + else { + customPawn.Childhood = childhoodBackstories[0]; + } + } + else { + index = adulthoodBackstories.FindIndex((Backstory b) => { return b.uniqueSaveKey.Equals(customPawn.Adulthood.uniqueSaveKey); }); + if (index > -1) { + int newIndex = index - 1; + if (newIndex < 0) { + newIndex = adulthoodBackstories.Count - 1; + } + customPawn.Adulthood = adulthoodBackstories[newIndex]; + } + else { + customPawn.Adulthood = adulthoodBackstories[0]; + } + } + } + + protected void SetSkillLevel(SkillRecord record, int value) + { + CustomPawn pawn = CurrentPawn; + pawn.SetSkillLevel(record.def, value); + } + + protected void IncreaseSkill(int skillIndex) + { + CustomPawn pawn = CurrentPawn; + SkillRecord record = pawn.Pawn.skills.skills[skillIndex]; + pawn.IncreaseSkill(record.def); + } + + protected void DecreaseSkill(int skillIndex) + { + CustomPawn pawn = CurrentPawn; + SkillRecord record = pawn.Pawn.skills.skills[skillIndex]; + pawn.DecreaseSkill(record.def); + } + + protected void IncreasePassion(int skillIndex) + { + CustomPawn customPawn = CurrentPawn; + SkillRecord record = customPawn.Pawn.skills.skills[skillIndex]; + customPawn.IncreasePassion(record.def); + } + + protected void DecreasePassion(int skillIndex) + { + CustomPawn customPawn = CurrentPawn; + SkillRecord record = customPawn.Pawn.skills.skills[skillIndex]; + customPawn.DecreasePassion(record.def); + } + } +} diff --git a/Source/Page_Equipment.cs b/Source/Page_Equipment.cs new file mode 100644 index 0000000..eade88d --- /dev/null +++ b/Source/Page_Equipment.cs @@ -0,0 +1,713 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace EdB.PrepareCarefully +{ + + public class Page_Equipment : Page_PrepareCarefully + { + private DragSlider equipmentDragSlider = new DragSlider(0.3f, 12, 400); + + private static Color ButtonColor = new Color(0.623529f, 0.623529f, 0.623529f); + private static Color ButtonHighlightColor = new Color(0.97647f, 0.97647f, 0.97647f); + + private static readonly Vector2 WinSize = new Vector2(1020, 764); + + private const float TopAreaHeight = 80; + + protected List tabs = new List(); + protected int selectedTab = 0; + + protected GraphicsCache cache = new GraphicsCache(); + + protected List> equipmentLists = new List>(); + + protected List sourceScrollPositions; + protected List sourceScrollViewHeights; + protected Vector2 destScrollPosition = Vector2.zero; + protected float destScrollViewHeight = 0; + protected float newDestScrollPosition = -1f; + + protected List sourceSelections; + protected int destSelection = -1; + + static Page_Equipment() + { + ResetTextures(); + } + + public Page_Equipment() + { + //base.SetCentered(Page_Equipment.WinSize); + //this.drawPriority = 2000; + this.absorbInputAroundWindow = true; + this.forcePause = true; + + tabs.Add(new TabRecord("EdB.EquipmentTab.Resources".Translate(), delegate { this.ChangeTab(0); }, true)); + tabs.Add(new TabRecord("EdB.EquipmentTab.Food".Translate(), delegate { this.ChangeTab(1); }, false)); + tabs.Add(new TabRecord("EdB.EquipmentTab.Weapons".Translate(), delegate { this.ChangeTab(2); }, false)); + tabs.Add(new TabRecord("EdB.EquipmentTab.Apparel".Translate(), delegate { this.ChangeTab(3); }, false)); + tabs.Add(new TabRecord("EdB.PrepareCarefully.EquipmentTab.Implants".Translate(), delegate { this.ChangeTab(4); }, false)); + tabs.Add(new TabRecord("EdB.PrepareCarefully.EquipmentTab.Buildings".Translate(), delegate { this.ChangeTab(5); }, false)); + tabs.Add(new TabRecord("EdB.PrepareCarefully.EquipmentTab.Animals".Translate(), delegate { this.ChangeTab(6); }, false)); + + sourceScrollPositions = new List(); + sourceScrollViewHeights = new List(); + sourceSelections = new List(); + for (int i = 0; i < tabs.Count; i++) { + sourceScrollPositions.Add(Vector2.zero); + sourceScrollViewHeights.Add(0); + sourceSelections.Add(-1); + } + + BuildEquipmentLists(); + SortEquipmentLists(); + } + + public override Vector2 InitialSize { + get { + return Page_Equipment.WinSize; + } + } + + public static void ResetTextures() + { + } + + public override void WindowUpdate() + { + base.WindowUpdate(); + DragSliderManager.DragSlidersUpdate(); + } + + public void SortEquipmentLists() + { + SortField field = PrepareCarefully.Instance.SortField; + SortOrder nameOrder = PrepareCarefully.Instance.NameSortOrder; + SortOrder costOrder = PrepareCarefully.Instance.CostSortOrder; + foreach (var list in equipmentLists) { + if (PrepareCarefully.Instance.SortField == SortField.Cost) { + list.Sort((EquipmentDatabaseEntry x, EquipmentDatabaseEntry y) => { + if (costOrder == SortOrder.Ascending) { + int result = x.cost.CompareTo(y.cost); + if (result != 0) { + return result; + } + } + else { + int result = y.cost.CompareTo(x.cost); + if (result != 0) { + return result; + } + } + if (nameOrder == SortOrder.Ascending) { + return x.Label.CompareTo(y.Label); + } + else { + return y.Label.CompareTo(x.Label); + } + }); + } + else { + list.Sort((EquipmentDatabaseEntry x, EquipmentDatabaseEntry y) => { + if (nameOrder == SortOrder.Ascending) { + int result = x.Label.CompareTo(y.Label); + if (result != 0) { + return result; + } + } + else { + int result = y.Label.CompareTo(x.Label); + if (result != 0) { + return result; + } + } + if (costOrder == SortOrder.Ascending) { + return x.cost.CompareTo(y.cost); + } + else { + return y.cost.CompareTo(x.cost); + } + }); + } + } + } + + public void BuildEquipmentLists() + { + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Resources); + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Food); + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Weapons); + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Apparel); + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Implants); + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Buildings); + equipmentLists.Add(PrepareCarefully.Instance.EquipmentEntries.Animals); + int count = equipmentLists.Count; + for (int i = 0; i 0) { + sourceSelections[i] = 0; + } + } + if (PrepareCarefully.Instance.Equipment.Count > 0) { + destSelection = 0; + } + } + + public void ChangeTab(int index) + { + tabs[selectedTab].selected = false; + tabs[index].selected = true; + selectedTab = index; + } + + private AcceptanceReport CanStart() + { + // Removing points. + //if (Config.pointsEnabled) { + // if (PrepareCarefully.Instance.PointsRemaining < 0) { + // return new AcceptanceReport("EdB.NotEnoughPoints".Translate()); + // } + //} + int pawnCount = PrepareCarefully.Instance.Pawns.Count; + if (Config.hardMaxColonists != null && pawnCount > Config.hardMaxColonists) { + if (Config.hardMaxColonists == 1) { + return new AcceptanceReport("EdB.PrepareCarefully.TooManyColonists1".Translate(new object[] { Config.maxColonists })); + } + else { + return new AcceptanceReport("EdB.PrepareCarefully.TooManyColonists".Translate(new object[] { Config.maxColonists })); + } + } + if (pawnCount < Config.minColonists) { + if (Config.minColonists == 1) { + return new AcceptanceReport("EdB.PrepareCarefully.NotEnoughColonists1".Translate(new object[] { Config.minColonists })); + } + else { + return new AcceptanceReport("EdB.PrepareCarefully.NotEnoughColonists".Translate(new object[] { Config.minColonists })); + } + } + + return AcceptanceReport.WasAccepted; + } + + public override void DoWindowContents(Rect inRect) + { + Text.Font = GameFont.Medium; + Widgets.Label(new Rect(0, 0, 500, 300), "EdB.SelectResourcesAndEquipment".Translate()); + Text.Font = GameFont.Small; + + string label = "Start".Translate(); + + // TODO: Alpha 14 + DoNextBackButtons(inRect, label, + delegate { + if (TryStartGame()) { + this.Close(false); + PrepareCarefully.Instance.NextPage(); + } + }, + delegate { + Find.WindowStack.Add(new Page_ConfigureStartingPawnsCarefully()); + this.Close(true); + } + ); + + if (IsBroken) { + return; + } + + Rect rect = new Rect(0, 80, inRect.width, inRect.height - 60 - 80); + Widgets.DrawMenuSection(rect, true); + TabDrawer.DrawTabs(new Rect(0, 80, rect.width - 1, rect.height), tabs); + Rect innerRect = rect.ContractedBy(22); + + GUI.BeginGroup(innerRect); + DrawAvailableEquipmentList(equipmentLists[this.selectedTab]); + DrawSelectedEquipmentList(); + GUI.EndGroup(); + + DrawCost(inRect); + DrawPresetButtons(); + } + + protected static Rect RectSourceBox = new Rect(0, 16, 450, 440); + protected static Rect RectSourceContent = RectSourceBox.ContractedBy(1); + protected static Rect RectSourceEntry = new Rect(0, 0, RectSourceContent.width, 42); + protected static Rect RectSourceItem = new Rect(10, 2, 38, 38); + protected static Rect RectSourceButton = new Rect(RectSourceBox.x + (RectSourceBox.width / 2) - 80, RectSourceBox.y + RectSourceBox.height + 8, 160, 34); + protected static Rect RectSourceCostLabel = new Rect(RectSourceBox.x + 320, RectSourceBox.y - 18, 100, 22); + protected static Rect RectSourceNameLabel = new Rect(RectSourceBox.x + 66, RectSourceBox.y - 18, 100, 22); + protected static Color ColorBoxOutline = new Color(0.3608f, 0.3608f, 0.3608f); + protected static Color ColorBoxBackground = new Color(0.145098f, 0.149f, 0.15294f); + protected static Color ColorEntryBackground = new Color(0.1882f, 0.19216f, 0.19608f); + protected static Color ColorSelectedEntry = new Color(0.05f, 0.05f, 0.05f); + protected static Vector2 SizeTextureSortIndicator = new Vector2(8, 4); + protected void DrawAvailableEquipmentList(List entries) + { + SortField sortField = PrepareCarefully.Instance.SortField; + Text.Font = GameFont.Tiny; + bool resort = false; + + Text.Anchor = TextAnchor.LowerLeft; + string nameLabelText = "EdB.PrepareCarefully.EquipmentName".Translate(); + Vector2 nameLabelSize = Text.CalcSize(nameLabelText); + Rect nameLabelButtonRect = new Rect(RectSourceNameLabel.x - 12, RectSourceNameLabel.y, nameLabelSize.x + 12, RectSourceNameLabel.height); + if (nameLabelButtonRect.Contains(Event.current.mousePosition)) { + GUI.color = Color.white; + } + else { + GUI.color = ColorText; + } + Widgets.Label(RectSourceNameLabel, nameLabelText); + if (sortField == SortField.Name) { + SortOrder sortOrder = PrepareCarefully.Instance.NameSortOrder; + if (sortOrder == SortOrder.Ascending) { + Rect rect = new Rect(RectSourceNameLabel.x - 13, RectSourceNameLabel.y + 8, SizeTextureSortIndicator.x, SizeTextureSortIndicator.y); + GUI.DrawTexture(rect, Textures.TextureSortAscending); + if (Widgets.ButtonInvisible(nameLabelButtonRect, false)) { + PrepareCarefully.Instance.NameSortOrder = SortOrder.Descending; + resort = true; + } + } + else { + Rect rect = new Rect(RectSourceNameLabel.x - 13, RectSourceNameLabel.y + 7, SizeTextureSortIndicator.x, SizeTextureSortIndicator.y); + GUI.DrawTexture(rect, Textures.TextureSortDescending); + if (Widgets.ButtonInvisible(nameLabelButtonRect, false)) { + PrepareCarefully.Instance.NameSortOrder = SortOrder.Ascending; + resort = true; + } + } + } + else if (Widgets.ButtonInvisible(nameLabelButtonRect, false)) { + PrepareCarefully.Instance.SortField = SortField.Name; + PrepareCarefully.Instance.NameSortOrder = PrepareCarefully.Instance.CostSortOrder; + resort = true; + } + + Text.Anchor = TextAnchor.LowerRight; + string costLabelText = "EdB.Cost".Translate(); + Vector2 costLabelSize = Text.CalcSize(costLabelText); + Rect costLabelButtonRect = new Rect(RectSourceCostLabel.x + RectSourceCostLabel.width - costLabelSize.x - 12, RectSourceNameLabel.y, costLabelSize.x + 12, RectSourceNameLabel.height); + if (costLabelButtonRect.Contains(Event.current.mousePosition)) { + GUI.color = Color.white; + } + else { + GUI.color = ColorText; + } + Widgets.Label(RectSourceCostLabel, costLabelText); + if (sortField == SortField.Cost) { + SortOrder sortOrder = PrepareCarefully.Instance.CostSortOrder; + if (sortOrder == SortOrder.Ascending) { + Rect rect = new Rect(RectSourceCostLabel.x + RectSourceCostLabel.width - costLabelSize.x - 12, RectSourceNameLabel.y + 8, SizeTextureSortIndicator.x, SizeTextureSortIndicator.y); + GUI.DrawTexture(rect, Textures.TextureSortAscending); + if (Widgets.ButtonInvisible(costLabelButtonRect, false)) { + PrepareCarefully.Instance.CostSortOrder = SortOrder.Descending; + resort = true; + } + } + else { + Rect rect = new Rect(RectSourceCostLabel.x + RectSourceCostLabel.width - costLabelSize.x - 12, RectSourceNameLabel.y + 7, SizeTextureSortIndicator.x, SizeTextureSortIndicator.y); + GUI.DrawTexture(rect, Textures.TextureSortDescending); + if (Widgets.ButtonInvisible(costLabelButtonRect, false)) { + PrepareCarefully.Instance.CostSortOrder = SortOrder.Ascending; + resort = true; + } + } + } + else if (Widgets.ButtonInvisible(costLabelButtonRect, false)) { + PrepareCarefully.Instance.SortField = SortField.Cost; + PrepareCarefully.Instance.CostSortOrder = PrepareCarefully.Instance.NameSortOrder; + resort = true; + } + + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.UpperLeft; + + GUI.color = ColorBoxBackground; + GUI.DrawTexture(RectSourceBox, BaseContent.WhiteTex); + GUI.color = ColorBoxOutline; + Widgets.DrawBox(RectSourceBox, 1); + + GUI.color = Color.white; + + GUI.BeginGroup(RectSourceContent); + Rect scrollRect = new Rect(0, 0, RectSourceContent.width, RectSourceContent.height); + Rect viewRect = new Rect(scrollRect.x, scrollRect.y, scrollRect.width - 16, sourceScrollViewHeights[selectedTab]); + + Vector2 scrollPositions = sourceScrollPositions[selectedTab]; + Widgets.BeginScrollView(scrollRect, ref scrollPositions, viewRect); + sourceScrollPositions[selectedTab] = scrollPositions; + + try { + Rect rectEntry = RectSourceEntry; + Rect rectText = RectSourceEntry; + rectText.x += 65; + rectText.width = 290; + Rect rectCost = RectSourceEntry; + rectCost.x += 320; + rectCost.width = 100; + Rect rectItem = RectSourceItem; + bool alternateBackground = false; + float top = sourceScrollPositions[selectedTab].y - rectEntry.height; + float bottom = sourceScrollPositions[selectedTab].y + RectSourceBox.height; + int index = -1; + foreach (EquipmentDatabaseEntry entry in entries) { + index++; + ThingDef def = entry.def; + + if (alternateBackground) { + GUI.color = ColorEntryBackground; + alternateBackground = false; + } + else { + GUI.color = ColorBoxBackground; + alternateBackground = true; + } + if (sourceSelections[selectedTab] == index) { + GUI.color = ColorSelectedEntry; + } + + GUI.DrawTexture(rectEntry, BaseContent.WhiteTex); + + GUI.color = ColorText; + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleLeft; + Widgets.Label(rectText, entry.Label); + + Text.Anchor = TextAnchor.MiddleRight; + Widgets.Label(rectCost, "" + entry.cost); + + DrawEquipmentIcon(rectItem, entry); + + if (rectEntry.y > top && rectEntry.y < bottom) { + if (Event.current.type == EventType.MouseDown && rectEntry.Contains(Event.current.mousePosition)) { + if (Event.current.clickCount == 1) { + if (Event.current.button == 1) { + if (sourceSelections[selectedTab] != index) { + sourceSelections[selectedTab] = index; + } + List list = new List(); + ThingDef thingDef = def; + ThingDef stuffDef = entry.stuffDef; + list.Add(new FloatMenuOption("ThingInfo".Translate(), delegate { + Find.WindowStack.Add(new Dialog_InfoCard(thingDef, stuffDef)); + }, MenuOptionPriority.Medium, null, null, 0, null)); + Find.WindowStack.Add(new FloatMenu(list, null, false)); + } + else { + sourceSelections[selectedTab] = index; + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + } + } + else if (Event.current.clickCount == 2) { + if (PrepareCarefully.Instance.AddEquipment(entry)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + } + else { + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + } + ScrollTo(entry); + } + } + } + rectEntry.y += rectEntry.height; + rectText.y += rectEntry.height; + rectCost.y += rectEntry.height; + rectItem.y += rectEntry.height; + } + + if (Event.current.type == EventType.Layout) { + sourceScrollViewHeights[selectedTab] = rectEntry.y; + } + } + catch (Exception e) { + FatalError("Could not draw available equipment and resources", e); + } + finally { + Widgets.EndScrollView(); + GUI.EndGroup(); + } + + if (sourceSelections[selectedTab] != -1) { + if (Widgets.ButtonText(RectSourceButton, "EdB.AddButton".Translate(), true, false, true)) { + var entry = equipmentLists[selectedTab][sourceSelections[selectedTab]]; + if (PrepareCarefully.Instance.AddEquipment(entry)) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + } + else { + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + } + ScrollTo(entry); + } + } + + if (resort) { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + SortEquipmentLists(); + } + } + + protected void DrawEquipmentIcon(Rect rect, EquipmentDatabaseEntry entry) + { + GUI.color = entry.color; + if (entry.thing == null) { + GUI.DrawTexture(rect, entry.def.uiIcon); + } + else { + // EdB: Inline copy of static Widgets.ThingIcon() with graphics switched to show a side view instead of front view. + Thing thing = entry.thing; + GUI.color = thing.DrawColor; + Texture resolvedIcon; + if (!thing.def.uiIconPath.NullOrEmpty()) { + resolvedIcon = thing.def.uiIcon; + } + else { + if (thing is Pawn) { + Pawn pawn = (Pawn)thing; + if (!pawn.Drawer.renderer.graphics.AllResolved) { + pawn.Drawer.renderer.graphics.ResolveAllGraphics(); + } + resolvedIcon = pawn.Drawer.renderer.graphics.nakedGraphic.MatSide.mainTexture; + } + else { + resolvedIcon = thing.Graphic.ExtractInnerGraphicFor(thing).MatSide.mainTexture; + } + } + + // EdB: Inline copy of static private method. + //Widgets.ThingIconWorker(rect, thing.def, resolvedIcon); + float num = GenUI.IconDrawScale(thing.def); + if (num != 1) { + Vector2 center = rect.center; + rect.width *= num; + rect.height *= num; + rect.center = center; + } + GUI.DrawTexture(rect, resolvedIcon); + } + GUI.color = Color.white; + } + + protected void ScrollTo(EquipmentDatabaseEntry entry) + { + int index = PrepareCarefully.Instance.Equipment.FindIndex((SelectedEquipment e) => { + return e.def == entry.def && e.stuffDef == entry.stuffDef; + }); + if (index != -1) { + destSelection = index; + float pos = (float)index * RectDestEntry.height; + if (pos < destScrollPosition.y) { + newDestScrollPosition = pos; + } + else if (pos > destScrollPosition.y + RectDestContent.height - RectDestEntry.height) { + newDestScrollPosition = pos + RectDestEntry.height - RectDestContent.height; + } + } + } + + protected static Rect RectDestBox = new Rect(490, RectSourceBox.y, RectSourceBox.width, RectSourceBox.height); + protected static Rect RectDestContent = RectDestBox.ContractedBy(1); + protected static Rect RectDestEntry = new Rect(0, 0, RectDestContent.width, 42); + protected static Rect RectDestItem = new Rect(10, 2, 38, 38); + protected static Rect RectDestButton = new Rect(RectDestBox.x + (RectDestBox.width / 2) - (RectSourceButton.width / 2), + RectDestBox.y + RectDestBox.height + 8, RectSourceButton.width, RectSourceButton.height); + + protected void DrawSelectedEquipmentList() + { + if (destSelection >= PrepareCarefully.Instance.Equipment.Count) { + destSelection = PrepareCarefully.Instance.Equipment.Count - 1; + } + + GUI.color = ColorBoxBackground; + GUI.DrawTexture(RectDestBox, BaseContent.WhiteTex); + GUI.color = ColorBoxOutline; + Widgets.DrawBox(RectDestBox, 1); + + try { + GUI.color = Color.white; + GUI.BeginGroup(RectDestContent); + Rect scrollRect = new Rect(0, 0, RectDestContent.width, RectDestContent.height); + Rect viewRect = new Rect(scrollRect.x, scrollRect.y, scrollRect.width - 16, destScrollViewHeight); + + Widgets.BeginScrollView(scrollRect, ref destScrollPosition, viewRect); + Rect rectEntry = RectDestEntry; + Rect rectText = RectDestEntry; + rectText.x += 65; + rectText.width = 320; + Rect rectCost = RectDestEntry; + rectCost.x += 352; + rectCost.y += 7; + rectCost.height -= 14; + rectCost.width = 60; + Rect rectItem = RectDestItem; + Rect rectEntryButton = RectDestEntry; + rectEntryButton.width = 320; + bool alternateBackground = false; + float top = destScrollPosition.y - rectEntry.height; + float bottom = destScrollPosition.y + RectDestBox.height; + int index = -1; + foreach (SelectedEquipment customPawn in PrepareCarefully.Instance.Equipment) { + index++; + ThingDef def = customPawn.def; + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[customPawn.EquipmentKey]; + if (entry == null) { + string thing = def != null ? def.defName : "null"; + string stuff = customPawn.stuffDef != null ? customPawn.stuffDef.defName : "null"; + Log.Warning(string.Format("Could not draw unrecognized resource/equipment. Invalid item was removed. This may have been caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); + PrepareCarefully.Instance.RemoveEquipment(customPawn); + continue; + } + SelectedEquipment loadoutRecord = PrepareCarefully.Instance.Find(entry); + + if (alternateBackground) { + GUI.color = ColorEntryBackground; + alternateBackground = false; + } + else { + GUI.color = ColorBoxBackground; + alternateBackground = true; + } + if (destSelection == index) { + GUI.color = ColorSelectedEntry; + } + + GUI.DrawTexture(rectEntry, BaseContent.WhiteTex); + + GUI.color = ColorText; + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleLeft; + Widgets.Label(rectText, entry.LabelNoCount); + + DrawEquipmentIcon(rectItem, entry); + + Rect fieldRect = rectCost; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + + equipmentDragSlider.OnGUI(fieldRect, loadoutRecord.count, (int value) => { + var record = loadoutRecord; + record.count = value; + }); + bool dragging = DragSlider.IsDragging(); + + Rect buttonRect = new Rect(fieldRect.x - 17, fieldRect.y + 6, 16, 16); + if (!dragging && buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonPrevious); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + loadoutRecord.count -= amount; + if (loadoutRecord.count < 0) { + loadoutRecord.count = 0; + } + } + + buttonRect = new Rect(fieldRect.x + fieldRect.width + 1, fieldRect.y + 6, 16, 16); + if (!dragging && buttonRect.Contains(Event.current.mousePosition)) { + GUI.color = ButtonHighlightColor; + } + else { + GUI.color = ButtonColor; + } + GUI.DrawTexture(buttonRect, Textures.TextureButtonNext); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + loadoutRecord.count += amount; + } + + if (rectEntry.y > top && rectEntry.y < bottom) { + if (Event.current.type == EventType.MouseDown && rectEntryButton.Contains(Event.current.mousePosition)) { + if (Event.current.clickCount == 1) { + if (Event.current.button == 1) { + if (destSelection != index) { + destSelection = index; + } + List list = new List(); + ThingDef thingDef = customPawn.def; + ThingDef stuffDef = customPawn.stuffDef; + list.Add(new FloatMenuOption("ThingInfo".Translate(), delegate { + Find.WindowStack.Add(new Dialog_InfoCard(thingDef, stuffDef)); + }, MenuOptionPriority.Medium, null, null, 0, null)); + Find.WindowStack.Add(new FloatMenu(list, null, false)); + } + else { + destSelection = index; + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + } + } + else if (Event.current.clickCount == 2) { + if (customPawn.count > 0) { + SoundDefOf.TickHigh.PlayOneShotOnCamera(); + int amount = Event.current.shift ? 10 : 1; + loadoutRecord.count -= amount; + if (loadoutRecord.count < 0) { + loadoutRecord.count = 0; + } + } + else { + SoundDefOf.TickLow.PlayOneShotOnCamera(); + PrepareCarefully.Instance.RemoveEquipment(PrepareCarefully.Instance.EquipmentEntries[customPawn.EquipmentKey]); + } + } + } + } + rectEntry.y += rectEntry.height; + rectText.y += rectEntry.height; + rectCost.y += rectEntry.height; + rectItem.y += rectEntry.height; + rectEntryButton.y += rectEntry.height; + } + + if (Event.current.type == EventType.Layout) { + destScrollViewHeight = rectEntry.y; + } + } + catch (Exception e) { + FatalError("Could not draw selected resources", e); + } + finally { + Widgets.EndScrollView(); + GUI.EndGroup(); + } + + if (newDestScrollPosition >= 0) { + destScrollPosition.y = newDestScrollPosition; + newDestScrollPosition = -1; + } + + GUI.color = Color.white; + + if (destSelection != -1) { + if (Widgets.ButtonText(RectDestButton, "EdB.RemoveButton".Translate(), true, false, true)) { + var customPawn = PrepareCarefully.Instance.Equipment[destSelection]; + SoundDefOf.TickLow.PlayOneShotOnCamera(); + PrepareCarefully.Instance.RemoveEquipment(PrepareCarefully.Instance.EquipmentEntries[customPawn.EquipmentKey]); + } + } + + } + + private bool TryStartGame() + { + AcceptanceReport acceptanceReport = this.CanStart(); + if (!acceptanceReport.Accepted) { + Messages.Message(acceptanceReport.Reason, MessageSound.RejectInput); + return false; + } + + PrepareCarefully.Instance.Active = true; + PrepareCarefully.Instance.CreateColonists(); + return true; + } + } +} diff --git a/Source/Page_PrepareCarefully.cs b/Source/Page_PrepareCarefully.cs new file mode 100644 index 0000000..64b95de --- /dev/null +++ b/Source/Page_PrepareCarefully.cs @@ -0,0 +1,179 @@ +using RimWorld; +using System; +using System.Reflection; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public abstract class Page_PrepareCarefully : Window + { + private bool broken = false; + protected bool hidden = false; + + protected static Color ColorText = new Color(0.80f, 0.80f, 0.80f); + + public Page_PrepareCarefully() + { + + } + + protected bool IsBroken { + get { + return broken; + } + } + + public Configuration Config { + get { + return PrepareCarefully.Instance.Config; + } + } + + public State State { + get { + return PrepareCarefully.Instance.State; + } + } + + protected void FatalError(string message, Exception e) { + Log.Error("An unrecoverable error has occurred in the Prepare Carefully mod"); + Log.Error(message); + Log.Error(e.ToString()); + Log.Error(e.StackTrace); + this.broken = true; + } + + protected void DrawCost(Rect parentRect) + { + Rect rect = new Rect(parentRect.width - 446, parentRect.height - 104, 418, 32); + Text.Anchor = TextAnchor.LowerRight; + GUI.color = Color.white; + Text.Font = GameFont.Small; + CostDetails cost = PrepareCarefully.Instance.Cost; + string label; + if (Config.pointsEnabled) { + int points = PrepareCarefully.Instance.PointsRemaining; + if (points < 0) { + GUI.color = Color.yellow; + } + else { + GUI.color = ColorText; + } + label = "EdB.PointsRemaining".Translate(new string[] { "" + points }); + } + else { + double points = cost.total; + GUI.color = ColorText; + label = "EdB.PointsSpent".Translate(new string[] { "" + points }); + } + Widgets.Label(rect, label); + + string tooltipText = ""; + foreach (var c in cost.colonistDetails) { + tooltipText += "EdB.CostSummaryColonist".Translate(new object[] { c.name, (c.total - c.apparel - c.bionics)}) + "\n"; + } + tooltipText += "\n" + "EdB.CostSummaryApparel".Translate(new object[] { cost.colonistApparel }) + "\n" + + "EdB.PrepareCarefully.CostSummary.Implants".Translate(new object[] { cost.colonistBionics }) + "\n" + + "EdB.CostSummaryEquipment".Translate(new object[] { cost.equipment }) + "\n\n" + + "EdB.CostSummaryTotal".Translate(new object[] { cost.total }); + TipSignal tip = new TipSignal(() => tooltipText, tooltipText.GetHashCode()); + TooltipHandler.TipRegion(rect, tip); + + // Removed points. + /* + GUI.color = ColorText; + Text.Anchor = TextAnchor.UpperLeft; + Text.Font = GameFont.Small; + string optionLabel; + float optionTop = parentRect.height - 97; + optionLabel = "EdB.PrepareCarefully.UsePoints".Translate(); + Vector2 size = Text.CalcSize(optionLabel); + Rect optionRect = new Rect(24, optionTop, size.x + 10, 32); + Widgets.Label(optionRect, optionLabel); + GUI.color = Color.white; + TooltipHandler.TipRegion(optionRect, "EdB.PrepareCarefully.UsePoints.Tip".Translate()); + Widgets.Checkbox(new Vector2(optionRect.x + optionRect.width, optionRect.y - 3), ref Config.pointsEnabled, 24, false); + + GUI.color = ColorText; + optionLabel = "EdB.PrepareCarefully.FixedPoints".Translate(); + Vector2 fixedPointsSize = Text.CalcSize(optionLabel); + Rect fixedPointsRect = new Rect(optionRect.x + optionRect.width + 40, optionTop, fixedPointsSize.x + 10, 32); + Widgets.Label(fixedPointsRect, optionLabel); + GUI.color = Color.white; + TooltipHandler.TipRegion(fixedPointsRect, "EdB.PrepareCarefully.FixedPoints.Tip".Translate(new object[] { Config.points })); + Widgets.Checkbox(new Vector2(fixedPointsRect.x + fixedPointsRect.width, fixedPointsRect.y - 3), ref Config.fixedPointsEnabled, 24, !Config.pointsEnabled); + */ + } + + protected void DrawPresetButtons() + { + float middle = 982f / 2f; + float buttonWidth = 150; + float buttonSpacing = 24; + if (Widgets.ButtonText(new Rect(middle - buttonWidth - buttonSpacing / 2, 692, buttonWidth, 38), "EdB.LoadPresetButton".Translate(), true, false, true)) { + Hide(); + Find.WindowStack.Add(new Dialog_LoadPreset()); + } + if (Widgets.ButtonText(new Rect(middle + buttonSpacing / 2, 692, buttonWidth, 38), "EdB.SavePresetButton".Translate(), true, false, true)) { + Hide(); + Find.WindowStack.Add(new Dialog_SavePreset()); + } + GUI.color = Color.white; + } + + public void Hide() { + hidden = true; + } + + public void Show() { + hidden = false; + } + + protected void OnBackButton(Action defaultAction, Action scenariosAction) + { + defaultAction(); + } + + protected void OnNextButton(Action defaultAction, Action scenariosAction) + { + defaultAction(); + } + + // + // Static Fields + // + public const float BottomAreaHeight = 38; + private readonly Vector2 BottomButSize = new Vector2(150, 38); + + // + // Static Methods + // + public bool DoMiddleButton(Rect innerRect, string label) + { + float top = innerRect.height - 38; + Rect rect = new Rect(innerRect.width / 2 - BottomButSize.x / 2, top, BottomButSize.x, BottomButSize.y); + return Widgets.ButtonText(rect, label, true, false, true); + } + + public void DoNextBackButtons(Rect innerRect, string nextLabel, Action nextAct, Action backAct) + { + float top = innerRect.height - 38; + Text.Font = GameFont.Small; + if (backAct != null) { + Rect rect = new Rect(0, top, BottomButSize.x, BottomButSize.y); + if (Widgets.ButtonText(rect, "Back".Translate(), true, false, true)) { + backAct(); + } + } + if (nextAct != null) { + Rect rect2 = new Rect(innerRect.width - BottomButSize.x, top, BottomButSize.x, BottomButSize.y); + if (Widgets.ButtonText(rect2, nextLabel, true, false, true)) { + nextAct(); + } + } + } + + } +} + diff --git a/Source/Panel_Health.cs b/Source/Panel_Health.cs new file mode 100644 index 0000000..c1af494 --- /dev/null +++ b/Source/Panel_Health.cs @@ -0,0 +1,502 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using RimWorld; +using Verse; +using Verse.Sound; +using UnityEngine; + +namespace EdB.PrepareCarefully +{ + public class Panel_Health + { + public static Color BackgroundBoxColor = new Color(43f / 255f, 44f / 255f, 45f / 255f); + + protected Rect RectHeader; + protected Rect RectBorder; + protected Rect RectContent; + protected Rect RectScroll; + protected Vector2 SizeElement; + protected Vector2 ContentPadding; + protected ScrollView scrollView = new ScrollView(); + + public IEnumerable implantRecipes; + public List partRemovalList = new List(); + protected HashSet disabledBodyParts = new HashSet(); + + protected List severityOptions = new List(); + protected List oldInjurySeverities = new List(); + + protected class InjurySeverity + { + public InjurySeverity(float value) { + this.value = value; + } + public InjurySeverity(float value, HediffStage stage) { + this.value = value; + this.stage = stage; + } + protected float value = 0; + protected HediffStage stage = null; + protected int? variant = null; + public float Value { + get { + return this.value; + } + } + public HediffStage Stage { + get { + return this.stage; + } + } + public int? Variant { + get { + return this.variant; + } + set { + this.variant = value; + } + } + public string Label { + get { + if (stage != null) { + if (variant == null) { + return stage.label.CapitalizeFirst(); + } + else { + return "EdB.PrepareCarefully.Stage.OptionLabel".Translate(new object[] { + stage.label.CapitalizeFirst(), variant + }); + } + } + else { + return ("EdB.PrepareCarefully.Severity.OptionLabel." + value).Translate(); + } + } + } + } + + public Panel_Health(Rect rect) + { + RectHeader = new Rect(5, 5, rect.width - 10, 30); + RectBorder = new Rect(0, 33, rect.width, rect.height - 33).ContractedBy(5); + RectBorder.height -= 50; + RectContent = RectBorder.ContractedBy(1); + RectScroll = new Rect(0, 0, RectContent.width, RectContent.height); + ContentPadding = new Vector2(4, 4); + SizeElement = new Vector2(RectScroll.width - (ContentPadding.x * 2), 70); + + implantRecipes = PrepareCarefully.Instance.HealthManager.ImplantManager.Recipes; + + oldInjurySeverities.Add(new InjurySeverity(2)); + oldInjurySeverities.Add(new InjurySeverity(3)); + oldInjurySeverities.Add(new InjurySeverity(4)); + oldInjurySeverities.Add(new InjurySeverity(5)); + oldInjurySeverities.Add(new InjurySeverity(6)); + } + + public void Draw() + { + float cursor = 0; + DrawHeader(); + GUI.color = Page_ConfigureStartingPawnsCarefully.PortraitTabInactiveColor; + Widgets.DrawBox(RectBorder, 1); + GUI.color = Color.white; + GUI.BeginGroup(RectContent); + scrollView.Begin(RectScroll); + + cursor = DrawCustomBodyParts(cursor); + + scrollView.End(cursor); + GUI.EndGroup(); + + DrawRandomOption(); + + CustomPawn customPawn = PrepareCarefully.Instance.State.CurrentPawn; + if (partRemovalList.Count > 0) { + foreach (var x in partRemovalList) { + customPawn.RemoveCustomBodyParts(x); + } + partRemovalList.Clear(); + } + } + + public void DrawHeader() + { + GUI.BeginGroup(RectHeader); + GUI.color = Color.white; + + // Injury button. + if (Widgets.ButtonText(new Rect(0, 0, 120, 28), "EdB.PrepareCarefully.AddInjury".Translate(), true, true, true)) { + CustomPawn customPawn = PrepareCarefully.Instance.State.CurrentPawn; + InjuryOption selectedInjury = null; + BodyPartRecord selectedBodyPart = null; + bool bodyPartSelectionRequired = true; + InjurySeverity selectedSeverity = null; + + Dialog_Options severityDialog; + Dialog_Options bodyPartDialog; + + Action addInjuryAction = () => { + if (bodyPartSelectionRequired) { + Injury injury = new Injury(); + injury.BodyPartRecord = selectedBodyPart; + injury.Option = selectedInjury; + if (selectedSeverity != null) { + injury.Severity = selectedSeverity.Value; + } + customPawn.AddInjury(injury); + } + else { + foreach (var p in selectedInjury.ValidParts) { + BodyPartRecord record = PrepareCarefully.Instance.HealthManager.FirstBodyPartRecord(p); + if (record != null) { + Injury injury= new Injury(); + injury.BodyPartRecord = record; + injury.Option = selectedInjury; + if (selectedSeverity != null) { + injury.Severity = selectedSeverity.Value; + } + customPawn.AddInjury(injury); + } + else { + Log.Warning("Could not find body part record for definition: " + p.defName); + } + } + } + }; + + severityDialog = new Dialog_Options(severityOptions) { + ConfirmButtonLabel = "EdB.PrepareCarefully.Add", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectSeverity", + NameFunc = (InjurySeverity option) => { + return option.Label; + }, + SelectedFunc = (InjurySeverity option) => { + return option == selectedSeverity; + }, + SelectAction = (InjurySeverity option) => { + selectedSeverity = option; + }, + ConfirmValidation = () => { + if (selectedSeverity == null) { + return "EdB.PrepareCarefully.ErrorMustSelectSeverity"; + } + else { + return null; + } + }, + CloseAction = () => { + addInjuryAction(); + } + }; + + bodyPartDialog = new Dialog_Options(null) + { + ConfirmButtonLabel = "EdB.PrepareCarefully.Add", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectBodyPart", + NameFunc = (BodyPartRecord option) => { + return option.def.LabelCap; + }, + SelectedFunc = (BodyPartRecord option) => { + return option == selectedBodyPart; + }, + SelectAction = (BodyPartRecord option) => { + selectedBodyPart = option; + }, + EnabledFunc = (BodyPartRecord option) => { + return !disabledBodyParts.Contains(option); + }, + ConfirmValidation = () => { + if (selectedBodyPart == null) { + return "EdB.PrepareCarefully.ErrorMustSelectBodyPart"; + } + else { + return null; + } + }, + CloseAction = () => { + if (this.severityOptions.Count > 1) { + Find.WindowStack.Add(severityDialog); + } + else { + addInjuryAction(); + } + } + }; + + + Dialog_Options injuryOptionDialog + = new Dialog_Options(PrepareCarefully.Instance.HealthManager.InjuryManager.Options) + { + ConfirmButtonLabel = "EdB.PrepareCarefully.Next", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectInjury", + NameFunc = (InjuryOption option) => { + return option.Label; + }, + SelectedFunc = (InjuryOption option) => { + return selectedInjury == option; + }, + SelectAction = (InjuryOption option) => { + selectedInjury = option; + if (option.ValidParts == null || option.ValidParts.Count == 0) { + bodyPartSelectionRequired = true; + } + else { + bodyPartSelectionRequired = false; + } + }, + ConfirmValidation = () => { + if (selectedInjury == null) { + return "EdB.PrepareCarefully.ErrorMustSelectInjury"; + } + else { + return null; + } + }, + CloseAction = () => { + ResetSeverityOptions(selectedInjury); + selectedSeverity = this.severityOptions[0]; + if (bodyPartSelectionRequired) { + bodyPartDialog.Options = PrepareCarefully.Instance.HealthManager.AllSkinCoveredBodyParts; + ResetBodyPartEnabledState(bodyPartDialog.Options, customPawn); + Find.WindowStack.Add(bodyPartDialog); + } + else if (severityOptions.Count > 1) { + Find.WindowStack.Add(severityDialog); + } + else { + addInjuryAction(); + } + } + }; + Find.WindowStack.Add(injuryOptionDialog); + } + + // Implant button. + if (Widgets.ButtonText(new Rect(RectHeader.width - 120, 0, 120, 28), + "EdB.PrepareCarefully.AddImplant".Translate(), true, true, true)) + { + CustomPawn customPawn = PrepareCarefully.Instance.State.CurrentPawn; + RecipeDef selectedRecipe = null; + BodyPartRecord selectedBodyPart = null; + bool bodyPartSelectionRequired = true; + + Dialog_Options bodyPartDialog = + new Dialog_Options(null) + { + ConfirmButtonLabel = "EdB.PrepareCarefully.Add", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectBodyPart", + NameFunc = (BodyPartRecord record) => { + return record.def.LabelCap; + }, + SelectedFunc = (BodyPartRecord record) => { + return record == selectedBodyPart; + }, + SelectAction = (BodyPartRecord record) => { + selectedBodyPart = record; + }, + EnabledFunc = (BodyPartRecord record) => { + return !disabledBodyParts.Contains(record); + }, + ConfirmValidation = () => { + if (selectedBodyPart == null) { + return "EdB.PrepareCarefully.ErrorMustSelectBodyPart"; + } + else { + return null; + } + }, + CloseAction = () => { + customPawn.AddImplant(new Implant(selectedBodyPart, selectedRecipe)); + } + }; + + + Dialog_Options implantRecipeDialog = new Dialog_Options(implantRecipes) { + ConfirmButtonLabel = "EdB.PrepareCarefully.Next", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectImplant", + NameFunc = (RecipeDef recipe) => { + return recipe.LabelCap; + }, + SelectedFunc = (RecipeDef recipe) => { + return selectedRecipe == recipe; + }, + SelectAction = (RecipeDef recipe) => { + selectedRecipe = recipe; + IEnumerable bodyParts = PrepareCarefully.Instance.HealthManager.ImplantManager.PartsForRecipe(recipe); + int bodyPartCount = bodyParts.Count(); + if (bodyParts != null && bodyPartCount > 0) { + if (bodyPartCount > 1) { + selectedBodyPart = null; + bodyPartDialog.Options = bodyParts; + bodyPartSelectionRequired = true; + ResetBodyPartEnabledState(bodyParts, customPawn); + } + else { + selectedBodyPart = bodyParts.First(); + bodyPartSelectionRequired = false; + } + } + else { + selectedBodyPart = null; + bodyPartSelectionRequired = false; + } + }, + ConfirmValidation = () => { + if (selectedRecipe == null) { + return "EdB.PrepareCarefully.ErrorMustSelectImplant"; + } + else { + return null; + } + }, + CloseAction = () => { + if (bodyPartSelectionRequired) { + Find.WindowStack.Add(bodyPartDialog); + } + else { + customPawn.AddImplant(new Implant(selectedBodyPart, selectedRecipe)); + } + } + }; + Find.WindowStack.Add(implantRecipeDialog); + } + + GUI.EndGroup(); + } + + protected void ResetBodyPartEnabledState(IEnumerable parts, CustomPawn pawn) + { + disabledBodyParts.Clear(); + ImplantManager implantManager = PrepareCarefully.Instance.HealthManager.ImplantManager; + foreach (var part in parts) { + if (pawn.IsImplantedPart(part) || implantManager.AncestorIsImplant(part, pawn)) { + disabledBodyParts.Add(part); + } + } + } + + protected void ResetSeverityOptions(InjuryOption injuryOption) + { + severityOptions.Clear(); + if (injuryOption.HediffDef.stages == null || injuryOption.HediffDef.stages.Count == 0) { + severityOptions.AddRange(oldInjurySeverities); + return; + } + + int variant = 1; + InjurySeverity previous = null; + foreach (var stage in injuryOption.HediffDef.stages) { + InjurySeverity value = null; + if (stage.minSeverity == 0) { + value = new InjurySeverity(0.001f, stage); + } + else { + value = new InjurySeverity(stage.minSeverity, stage); + } + if (previous == null) { + previous = value; + variant = 1; + } + else { + if (previous.Stage.label == stage.label) { + previous.Variant = variant; + variant++; + value.Variant = variant; + } + else { + previous = value; + variant = 1; + } + } + severityOptions.Add(value); + } + } + + public float DrawCustomBodyParts(float cursor) + { + CustomPawn customPawn = PrepareCarefully.Instance.State.CurrentPawn; + foreach (var i in customPawn.BodyParts) { + cursor = DrawCustomBodyPart(cursor, i); + } + return cursor; + } + + public float DrawCustomBodyPart(float cursor, CustomBodyPart customPart) + { + cursor += ContentPadding.y; + Vector2 boxSize = SizeElement; + if (scrollView.ScrollbarsVisible) { + boxSize.x -= ScrollView.ScrollbarSize; + } + Rect elementBox = new Rect(ContentPadding.x, cursor, boxSize.x, boxSize.y); + + // Draw background box. + GUI.color = BackgroundBoxColor; + GUI.DrawTexture(elementBox, BaseContent.WhiteTex); + + GUI.BeginGroup(elementBox); + + // Draw body part name. + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + Widgets.Label(new Rect(8, 6, boxSize.x - 16, 24), customPart.PartName); + + // Draw field. + Rect fieldRect = new Rect(16, 30, boxSize.x - 32, 28); + GUI.color = Color.white; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + + // Draw the hediff label. + GUI.color = customPart.LabelColor; + Rect labelRect = new Rect(fieldRect.x, fieldRect.y + 1, fieldRect.width, fieldRect.height); + Widgets.Label(labelRect, customPart.ChangeName); + if (customPart.HasTooltip) { + TooltipHandler.TipRegion(labelRect, customPart.Tooltip); + } + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + + // Delete the option. + Rect buttonRect = new Rect(boxSize.x - 21, 3, 18, 18); + GUI.color = buttonRect.Contains(Event.current.mousePosition) ? Page_ConfigureStartingPawnsCarefully.ButtonHighlightColor : Page_ConfigureStartingPawnsCarefully.ButtonColor; + GUI.DrawTexture(buttonRect, Textures.TextureButtonDelete); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + partRemovalList.Add(customPart); + } + GUI.color = Color.white; + + GUI.EndGroup(); + + return cursor + boxSize.y; + } + + public void DrawRandomOption() + { + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + string optionLabel = "EdB.PrepareCarefully.RandomizeInjuries".Translate(); + Vector2 size = Text.CalcSize(optionLabel); + float widthWithCheckbox = size.x + 24 + 10; + Rect rect = new Rect(RectBorder.x + RectBorder.width * 0.5f - widthWithCheckbox * 0.5f, RectBorder.y + RectBorder.height + 10, size.x + 10, 32); + Widgets.Label(rect, optionLabel); + GUI.color = Color.white; + TooltipHandler.TipRegion(rect, "EdB.PrepareCarefully.RandomizeInjuries.Tooltip".Translate()); + bool selected = PrepareCarefully.Instance.State.CurrentPawn.RandomInjuries; + bool enabled = !PrepareCarefully.Instance.State.CurrentPawn.HasCustomBodyParts; + Widgets.Checkbox(new Vector2(rect.x + rect.width, rect.y + 3), ref selected, 24, !enabled); + PrepareCarefully.Instance.State.CurrentPawn.RandomInjuries = selected; + if (enabled == false && PrepareCarefully.Instance.State.CurrentPawn.RandomInjuries != false) { + PrepareCarefully.Instance.State.CurrentPawn.RandomInjuries = false; + } + } + + } +} + diff --git a/Source/Panel_Relations.cs b/Source/Panel_Relations.cs new file mode 100644 index 0000000..d9b520c --- /dev/null +++ b/Source/Panel_Relations.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using RimWorld; +using Verse; +using Verse.Sound; +using UnityEngine; + +namespace EdB.PrepareCarefully +{ + public class Panel_Relations + { + public static Color BackgroundBoxColor = new Color(43f / 255f, 44f / 255f, 45f / 255f); + + protected Rect RectHeader; + protected Rect RectBorder; + protected Rect RectContent; + protected Rect RectScroll; + protected Rect RectAddButton; + protected Vector2 SizeRelation; + protected Vector2 ContentPadding; + protected Vector2 SizeAddButton = new Vector2(180, 28); + protected ScrollView scrollView = new ScrollView(); + + public List relationDefs = new List(); + protected HashSet disabledRelationships = new HashSet(); + + public Panel_Relations(Rect rect) + { + RectHeader = new Rect(5, 5, rect.width - 10, 30); + RectBorder = new Rect(0, 33, rect.width, rect.height - 33).ContractedBy(5); + RectBorder.height -= 50; + RectContent = RectBorder.ContractedBy(1); + RectScroll = new Rect(0, 0, RectContent.width, RectContent.height); + RectAddButton = new Rect(RectHeader.width / 2 - SizeAddButton.x / 2, 0, SizeAddButton.x, SizeAddButton.y); + ContentPadding = new Vector2(4, 4); + SizeRelation = new Vector2(RectScroll.width - (ContentPadding.x * 2), 70); + + relationDefs.AddRange(DefDatabase.AllDefs.ToList().FindAll((PawnRelationDef def) => { + MethodInfo info = def.workerClass.GetMethod("CreateRelation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + if (info == null) { + return false; + } + else { + return true; + } + })); + } + + public void Draw() + { + float cursor = 0; + DrawHeader(); + GUI.color = Page_ConfigureStartingPawnsCarefully.PortraitTabInactiveColor; + Widgets.DrawBox(RectBorder, 1); + GUI.color = Color.white; + GUI.BeginGroup(RectContent); + scrollView.Begin(RectScroll); + + cursor = DrawRelationships(cursor); + cursor = DrawFooterControls(cursor); + + scrollView.End(cursor); + GUI.EndGroup(); + + DrawRandomOption(); + } + + public void DrawHeader() + { + GUI.BeginGroup(RectHeader); + GUI.color = Color.white; + + // TODO: Disable button if there's only one pawn? What about other pawns, i.e. faction leaders, etc. + if (Widgets.ButtonText(RectAddButton, "EdB.PrepareCarefully.AddRelationship".Translate(), true, true, true)) { + CustomPawn customPawn = PrepareCarefully.Instance.State.CurrentPawn; + PawnRelationDef selectedRelationship = null; + CustomPawn selectedPawn = null; + List otherPawns = PrepareCarefully.Instance.Pawns.FindAll((CustomPawn p) => { + return p != customPawn; + }); + + Dialog_Options relationshipDialog = + new Dialog_Options(null) + { + ConfirmButtonLabel = "EdB.PrepareCarefully.Add", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectRelationship", + NameFunc = (PawnRelationDef def) => { + return def.GetGenderSpecificLabelCap(selectedPawn.Pawn); + }, + SelectedFunc = (PawnRelationDef def) => { + return def == selectedRelationship; + }, + SelectAction = (PawnRelationDef def) => { + selectedRelationship = def; + }, + EnabledFunc = (PawnRelationDef d) => { + return !disabledRelationships.Contains(d); + }, + ConfirmValidation = () => { + if (selectedRelationship == null) { + return "EdB.PrepareCarefully.ErrorMustSelectRelationship"; + } + else { + return null; + } + }, + CloseAction = () => { + PrepareCarefully.Instance.RelationshipManager.AddRelationship(selectedRelationship, customPawn, selectedPawn); + } + }; + + Dialog_Options colonistDialog = new Dialog_Options(otherPawns) { + ConfirmButtonLabel = "EdB.PrepareCarefully.Next", + CancelButtonLabel = "EdB.PrepareCarefully.Cancel", + HeaderLabel = "EdB.PrepareCarefully.SelectColonist", + NameFunc = (CustomPawn pawn) => { + return pawn.Name.ToStringFull; + }, + SelectedFunc = (CustomPawn pawn) => { + return pawn == selectedPawn; + }, + SelectAction = (CustomPawn pawn) => { + selectedPawn = pawn; + }, + ConfirmValidation = () => { + if (selectedPawn == null) { + return "EdB.PrepareCarefully.ErrorMustSelectColonist"; + } + else { + return null; + } + }, + CloseAction = () => { + SetDisabledRelationships(selectedPawn); + List relationDefs = new List(PrepareCarefully.Instance.RelationshipManager.AllowedRelationships); + relationDefs.Sort((PawnRelationDef a, PawnRelationDef b) => { + return a.GetGenderSpecificLabelCap(selectedPawn.Pawn).CompareTo(b.GetGenderSpecificLabelCap(selectedPawn.Pawn)); + }); + relationshipDialog.Options = relationDefs; + Find.WindowStack.Add(relationshipDialog); + } + }; + Find.WindowStack.Add(colonistDialog); + } + + GUI.EndGroup(); + } + + public float DrawRelationships(float cursor) + { + CustomPawn customPawn = PrepareCarefully.Instance.State.CurrentPawn; + foreach (var r in PrepareCarefully.Instance.RelationshipManager.AllRelationships) { + if (r.source == customPawn) { + cursor = DrawRelationship(cursor, r.def, r.source, r.target, r.removeable); + } + else if (r.target == customPawn) { + cursor = DrawRelationship(cursor, r.inverseDef, r.target, r.source, r.removeable); + } + } + return cursor; + } + + public float DrawRelationship(float cursor, PawnRelationDef def, CustomPawn source, CustomPawn target, bool removeable) + { + cursor += ContentPadding.y; + Vector2 relationshipBoxSize = SizeRelation; + if (scrollView.ScrollbarsVisible) { + relationshipBoxSize.x -= ScrollView.ScrollbarSize; + } + Rect relationshipBox = new Rect(ContentPadding.x, cursor, relationshipBoxSize.x, relationshipBoxSize.y); + + // Draw background box. + GUI.color = BackgroundBoxColor; + GUI.DrawTexture(relationshipBox, BaseContent.WhiteTex); + + GUI.BeginGroup(relationshipBox); + + // Draw pawn name. + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + Widgets.Label(new Rect(8, 6, relationshipBoxSize.x - 16, 24), target.Pawn.NameStringShort); + + // Draw relationship field. + Rect fieldRect = new Rect(16, 30, relationshipBoxSize.x - 32, 28); + GUI.color = Color.white; + Widgets.DrawAtlas(fieldRect, Textures.TextureFieldAtlas); + // TODO: Move this color to another file like we did with the textures. + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + if (def != null) { + Widgets.Label(new Rect(fieldRect.x, fieldRect.y + 1, fieldRect.width, fieldRect.height), def.GetGenderSpecificLabelCap(target.Pawn)); + } + + // Delete relation + Rect buttonRect = new Rect(relationshipBoxSize.x - 21, 3, 18, 18); + GUI.color = buttonRect.Contains(Event.current.mousePosition) ? Page_ConfigureStartingPawnsCarefully.ButtonHighlightColor : Page_ConfigureStartingPawnsCarefully.ButtonColor; + if (removeable) { + GUI.DrawTexture(buttonRect, Textures.TextureButtonDelete); + if (Widgets.ButtonInvisible(buttonRect, false)) { + SoundDefOf.TickTiny.PlayOneShotOnCamera(); + PrepareCarefully.Instance.RelationshipManager.DeleteRelationship(def, source, target); + } + } + else { + GUI.DrawTexture(buttonRect, Textures.TextureDerivedRelationship); + TooltipHandler.TipRegion(buttonRect, "EdB.PrepareCarefully.CannotDeleteRelationship".Translate()); + } + GUI.color = Color.white; + + GUI.EndGroup(); + + return cursor + relationshipBoxSize.y; + } + + public void SetDisabledRelationships(CustomPawn target) { + disabledRelationships.Clear(); + CustomPawn source = PrepareCarefully.Instance.State.CurrentPawn; + bool bloodRelation = false; + foreach (CustomRelationship r in PrepareCarefully.Instance.RelationshipManager.ExplicitRelationships) { + if (r.def.familyByBloodRelation) { + if ((r.source == source && r.target == target) || (r.source == target && r.target == source)) { + bloodRelation = true; + break; + } + } + } + if (bloodRelation) { + foreach (PawnRelationDef r in PrepareCarefully.Instance.RelationshipManager.AllowedRelationships) { + if (r.familyByBloodRelation) { + disabledRelationships.Add(r); + } + } + } + foreach (CustomRelationship r in PrepareCarefully.Instance.RelationshipManager.ExplicitRelationships) { + if ((r.source == source && r.target == target) || (r.source == target && r.target == source)) { + disabledRelationships.Add(r.def); + CarefullyPawnRelationDef extendedDef = DefDatabase.GetNamedSilentFail(r.def.defName); + if (extendedDef != null) { + if (extendedDef.conflicts != null) { + foreach (string conflictName in extendedDef.conflicts) { + PawnRelationDef conflict = DefDatabase.GetNamedSilentFail(conflictName); + if (conflict != null) { + disabledRelationships.Add(conflict); + } + } + } + } + if (r.inverseDef != null) { + disabledRelationships.Add(r.inverseDef); + extendedDef = DefDatabase.GetNamedSilentFail(r.inverseDef.defName); + if (extendedDef != null) { + if (extendedDef.conflicts != null) { + foreach (string conflictName in extendedDef.conflicts) { + PawnRelationDef conflict = DefDatabase.GetNamedSilentFail(conflictName); + if (conflict != null) { + disabledRelationships.Add(conflict); + } + } + } + } + + } + } + } + } + + public float DrawFooterControls(float cursor) + { + return cursor; + } + + public void DrawRandomOption() + { + GUI.color = Page_ConfigureStartingPawnsCarefully.TextColor; + string optionLabel = "EdB.PrepareCarefully.RandomizeRelationships".Translate(); + Vector2 size = Text.CalcSize(optionLabel); + float widthWithCheckbox = size.x + 24 + 10; + Rect rect = new Rect(RectBorder.x + RectBorder.width * 0.5f - widthWithCheckbox * 0.5f, RectBorder.y + RectBorder.height + 10, size.x + 10, 32); + Widgets.Label(rect, optionLabel); + GUI.color = Color.white; + TooltipHandler.TipRegion(rect, "EdB.PrepareCarefully.RandomizeRelationships.Tooltip".Translate()); + bool selected = PrepareCarefully.Instance.State.CurrentPawn.randomRelations; + bool enabled = !PrepareCarefully.Instance.State.CurrentPawn.HasRelationships; + Widgets.Checkbox(new Vector2(rect.x + rect.width, rect.y + 3), ref selected, 24, !enabled); + PrepareCarefully.Instance.State.CurrentPawn.randomRelations = selected; + if (enabled == false && PrepareCarefully.Instance.State.CurrentPawn.randomRelations != false) { + PrepareCarefully.Instance.State.CurrentPawn.randomRelations = false; + } + } + + } +} + diff --git a/Source/PawnColorUtils.cs b/Source/PawnColorUtils.cs new file mode 100644 index 0000000..6a56891 --- /dev/null +++ b/Source/PawnColorUtils.cs @@ -0,0 +1,103 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class PawnColorUtils + { + public static readonly Color[] Colors = new Color[] { + new Color(0.3882353f, 0.2745098f, 0.1411765f), + new Color(0.509804f, 0.3568628f, 0.1882353f), + new Color(0.8941177f, 0.6196079f, 0.3529412f), + new Color(1f, 0.9372549f, 0.7411765f), + new Color(1f, 0.9372549f, 0.8352941f), + new Color(0.9490196f, 0.9294118f, 0.8784314f), + }; + + public static readonly float[] ColorValues = new float[] { 0f, 0.1f, 0.25f, 0.5f, 0.75f, 1f }; + + public static readonly float[] ColorSelectors = new float[] { 0f, 0.05f, 0.2f, 0.285f, 0.785f, 1f }; + + + public static Color GetSkinColor(float value) + { + int leftIndex = GetColorLeftIndex(value); + if (leftIndex == Colors.Length - 1) { + return Colors[leftIndex]; + } + float t = Mathf.InverseLerp(ColorValues[leftIndex], ColorValues[leftIndex + 1], value); + return Color.Lerp(Colors[leftIndex], Colors[leftIndex + 1], t); + } + + public static float GetSkinValue(Color color) + { + int leftIndex = GetColorLeftIndex(color); + if (leftIndex == Colors.Length - 1) { + return 1.0f; + } + + int rightIndex = leftIndex + 1; + float t = (color.b - Colors[leftIndex].b) / (Colors[rightIndex].b - Colors[leftIndex].b); + + float value = Mathf.Lerp(ColorValues[leftIndex], ColorValues[rightIndex], t); + return value; + } + + public static float GetSkinLerpValue(Color color) + { + int leftIndex = GetColorLeftIndex(color); + if (leftIndex == Colors.Length - 1) { + return 0.0f; + } + + int rightIndex = leftIndex + 1; + float t = (color.b - Colors[leftIndex].b) / (Colors[rightIndex].b - Colors[leftIndex].b); + + return t; + } + + public static int GetColorLeftIndex(float value) + { + int result = 0; + for (int i = 0; i < Colors.Length; i++) { + if (value < ColorValues[i]) { + break; + } + result = i; + } + return result; + } + + public static int GetColorLeftIndex(Color color) + { + int result = Colors.Length - 1; + for (int i = 0; i < Colors.Length - 1; i++) { + Color color1 = Colors[i]; + Color color2 = Colors[i + 1]; + if (color.r >= color1.r && color.r <= color2.r + && color.g >= color1.g && color.g <= color2.g + && color.b >= color1.b && color.b <= color2.b) + { + result = i; + break; + } + } + if (result == Colors.Length - 1) { + result = Colors.Length - 2; + } + return result; + } + + public static Color FindColor(int colorIndex, float lerpValue) + { + Color color1 = Colors[colorIndex]; + Color color2 = Colors[colorIndex + 1]; + return Color.Lerp(color1, color2, lerpValue); + } + } +} + diff --git a/Source/PawnLayers.cs b/Source/PawnLayers.cs new file mode 100644 index 0000000..7d01750 --- /dev/null +++ b/Source/PawnLayers.cs @@ -0,0 +1,124 @@ +using RimWorld; +using System; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class PawnLayers + { + public const int BodyType = 0; + public const int BottomClothingLayer = 1; + public const int Pants = 2; + public const int MiddleClothingLayer = 3; + public const int TopClothingLayer = 4; + public const int HeadType = 5; + public const int Hair = 6; + public const int Hat = 7; + + public const int Count = Hat + 1; + + public static String Label(int layer) { + switch (layer) { + case BodyType: + return "EdB.PawnLayer.BodyType".Translate(); + case HeadType: + return "EdB.PawnLayer.HeadType".Translate(); + case Pants: + return "EdB.PawnLayer.Pants".Translate(); + case BottomClothingLayer: + return "EdB.PawnLayer.BottomClothingLayer".Translate(); + case MiddleClothingLayer: + return "EdB.PawnLayer.MiddleClothingLayer".Translate(); + case TopClothingLayer: + return "EdB.PawnLayer.TopClothingLayer".Translate(); + case Hair: + return "EdB.PawnLayer.Hair".Translate(); + case Hat: + return "EdB.PawnLayer.Hat".Translate(); + default: + return ""; + } + } + + public static int ToPawnLayerIndex(ApparelLayer layer) { + switch (layer) { + case ApparelLayer.OnSkin: + return BottomClothingLayer; + case ApparelLayer.Middle: + return MiddleClothingLayer; + case ApparelLayer.Shell: + return TopClothingLayer; + case ApparelLayer.Overhead: + return Hat; + default: + return -1; + } + } + + public static int ToPawnLayerIndex(ApparelProperties apparelProperties) { + ApparelLayer layer = apparelProperties.LastLayer; + if (layer == ApparelLayer.OnSkin && apparelProperties.bodyPartGroups.Count == 1 && apparelProperties.bodyPartGroups[0].Equals(BodyPartGroupDefOf.Legs)) { + return Pants; + } + else { + switch (layer) { + case ApparelLayer.OnSkin: + return BottomClothingLayer; + case ApparelLayer.Middle: + return MiddleClothingLayer; + case ApparelLayer.Shell: + return TopClothingLayer; + case ApparelLayer.Overhead: + return Hat; + default: + { + Log.Warning("Cannot find matching layer for apparel. Last layer: " + apparelProperties.LastLayer); + return -1; + } + } + } + } + + public static ApparelLayer ToApparelLayer(int layer) { + switch (layer) { + case Pants: + return ApparelLayer.OnSkin; + case BottomClothingLayer: + return ApparelLayer.OnSkin; + case MiddleClothingLayer: + return ApparelLayer.Middle; + case TopClothingLayer: + return ApparelLayer.Shell; + case Hat: + return ApparelLayer.Overhead; + default: + return ApparelLayer.OnSkin; + } + } + + public static bool IsApparelLayer(int layer) { + switch (layer) { + case BodyType: + return false; + case HeadType: + return false; + case Pants: + return true; + case BottomClothingLayer: + return true; + case MiddleClothingLayer: + return true; + case TopClothingLayer: + return true; + case Hair: + return false; + case Hat: + return true; + default: + return false; + } + } + + } +} + diff --git a/Source/PawnRelationWorker_Sibling.cs b/Source/PawnRelationWorker_Sibling.cs new file mode 100644 index 0000000..8335cf7 --- /dev/null +++ b/Source/PawnRelationWorker_Sibling.cs @@ -0,0 +1,203 @@ +using RimWorld; +using RimWorld.Planet; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class PawnRelationWorker_Sibling : PawnRelationWorker + { + // + // Static Methods + // + private static Pawn GenerateParent(Pawn generatedChild, Pawn existingChild, Gender genderToGenerate, PawnGenerationRequest childRequest, bool newlyGeneratedParentsWillBeSpousesIfNotGay) + { + float ageChronologicalYearsFloat = generatedChild.ageTracker.AgeChronologicalYearsFloat; + float ageChronologicalYearsFloat2 = existingChild.ageTracker.AgeChronologicalYearsFloat; + float num = (genderToGenerate != Gender.Male) ? 16 : 14; + float num2 = (genderToGenerate != Gender.Male) ? 45 : 50; + float num3 = (genderToGenerate != Gender.Male) ? 27 : 30; + float num4 = Mathf.Max(ageChronologicalYearsFloat, ageChronologicalYearsFloat2) + num; + float maxChronologicalAge = num4 + (num2 - num); + float midChronologicalAge = num4 + (num3 - num); + float value; + float value2; + float value3; + string last; + PawnRelationWorker_Sibling.GenerateParentParams(num4, maxChronologicalAge, midChronologicalAge, num, generatedChild, existingChild, childRequest, out value, out value2, out value3, out last); + bool allowGay = true; + if (newlyGeneratedParentsWillBeSpousesIfNotGay && last.NullOrEmpty() && Rand.Value < 0.8) { + if (genderToGenerate == Gender.Male && existingChild.GetMother() != null && !existingChild.GetMother().story.traits.HasTrait(TraitDefOf.Gay)) { + last = ((NameTriple)existingChild.GetMother().Name).Last; + allowGay = false; + } + else if (genderToGenerate == Gender.Female && existingChild.GetFather() != null && !existingChild.GetFather().story.traits.HasTrait(TraitDefOf.Gay)) { + last = ((NameTriple)existingChild.GetFather().Name).Last; + allowGay = false; + } + } + Faction faction = existingChild.Faction; + if (faction == null || faction.IsPlayer) { + bool tryMedievalOrBetter = faction != null && faction.def.techLevel >= TechLevel.Medieval; + Find.FactionManager.TryGetRandomNonColonyHumanlikeFaction(out faction, tryMedievalOrBetter); + } + float? fixedChronologicalAge = new float?(value2); + Gender? fixedGender = new Gender?(genderToGenerate); + float? fixedSkinWhiteness = new float?(value3); + PawnGenerationRequest request = new PawnGenerationRequest(existingChild.kindDef, faction, PawnGenerationContext.NonPlayer, true, false, true, true, false, false, 1, false, allowGay, null, new float?(value), fixedChronologicalAge, fixedGender, fixedSkinWhiteness, last); + Pawn pawn = PawnGenerator.GeneratePawn(request); + if (!Find.WorldPawns.Contains(pawn)) { + Find.WorldPawns.PassToWorld(pawn, PawnDiscardDecideMode.Keep); + } + return pawn; + } + + private static void GenerateParentParams(float minChronologicalAge, float maxChronologicalAge, float midChronologicalAge, float minBioAgeToHaveChildren, Pawn generatedChild, Pawn existingChild, PawnGenerationRequest childRequest, out float biologicalAge, out float chronologicalAge, out float skinWhiteness, out string lastName) + { + chronologicalAge = Rand.GaussianAsymmetric(midChronologicalAge, (midChronologicalAge - minChronologicalAge) / 2, (maxChronologicalAge - midChronologicalAge) / 2); + chronologicalAge = Mathf.Clamp(chronologicalAge, minChronologicalAge, maxChronologicalAge); + biologicalAge = Rand.Range(minBioAgeToHaveChildren, Mathf.Min(existingChild.RaceProps.lifeExpectancy, chronologicalAge)); + if (existingChild.GetFather() != null) { + skinWhiteness = ParentRelationUtility.GetRandomSecondParentSkinColor(existingChild.GetFather().story.skinWhiteness, existingChild.story.skinWhiteness, childRequest.FixedSkinWhiteness); + } + else if (existingChild.GetMother() != null) { + skinWhiteness = ParentRelationUtility.GetRandomSecondParentSkinColor(existingChild.GetMother().story.skinWhiteness, existingChild.story.skinWhiteness, childRequest.FixedSkinWhiteness); + } + else if (!childRequest.FixedSkinWhiteness.HasValue) { + skinWhiteness = PawnSkinColors.GetRandomSkinColorSimilarTo(existingChild.story.skinWhiteness, 0, 1); + } + else { + float num = Mathf.Min(childRequest.FixedSkinWhiteness.Value, existingChild.story.skinWhiteness); + float num2 = Mathf.Max(childRequest.FixedSkinWhiteness.Value, existingChild.story.skinWhiteness); + if (Rand.Value < 0.5) { + skinWhiteness = PawnSkinColors.GetRandomSkinColorSimilarTo(num, 0, num); + } + else { + skinWhiteness = PawnSkinColors.GetRandomSkinColorSimilarTo(num2, num2, 1); + } + } + lastName = null; + if (!ChildRelationUtility.DefinitelyHasNotBirthName(existingChild) && ChildRelationUtility.ChildWantsNameOfAnyParent(existingChild)) { + if (existingChild.GetMother() == null && existingChild.GetFather() == null) { + if (Rand.Value < 0.5) { + lastName = ((NameTriple)existingChild.Name).Last; + } + } + else { + string last = ((NameTriple)existingChild.Name).Last; + string b = null; + if (existingChild.GetMother() != null) { + b = ((NameTriple)existingChild.GetMother().Name).Last; + } + else if (existingChild.GetFather() != null) { + b = ((NameTriple)existingChild.GetFather().Name).Last; + } + if (last != b) { + lastName = last; + } + } + } + } + + private static void ResolveMyName(ref PawnGenerationRequest request, Pawn generated) + { + if (request.FixedLastName != null) { + return; + } + if (ChildRelationUtility.ChildWantsNameOfAnyParent(generated)) { + if (Rand.Value < 0.5) { + request.SetFixedLastName(((NameTriple)generated.GetFather().Name).Last); + } + else { + request.SetFixedLastName(((NameTriple)generated.GetMother().Name).Last); + } + } + } + + private static void ResolveMySkinColor(ref PawnGenerationRequest request, Pawn generated) + { + if (request.FixedSkinWhiteness.HasValue) { + return; + } + request.SetFixedSkinWhiteness(ChildRelationUtility.GetRandomChildSkinColor(generated.GetFather().story.skinWhiteness, generated.GetMother().story.skinWhiteness)); + } + + // + // Methods + // + public override void CreateRelation(Pawn generated, Pawn other, ref PawnGenerationRequest request) + { + bool otherPawnHasMother = other.GetMother() != null; + bool otherPawnHasFather = other.GetFather() != null; + if (generated.GetMother() != null && generated.GetFather() != null && !otherPawnHasMother && !otherPawnHasFather) { + other.SetMother(generated.GetMother()); + other.SetFather(generated.GetFather()); + } + else { + bool flag = other.GetMother() != null; + bool flag2 = other.GetFather() != null; + bool flag3 = Rand.Value < 0.85; + if (flag && LovePartnerRelationUtility.HasAnyLovePartner(other.GetMother())) { + flag3 = false; + } + if (flag2 && LovePartnerRelationUtility.HasAnyLovePartner(other.GetFather())) { + flag3 = false; + } + if (!flag) { + Pawn newMother = PawnRelationWorker_Sibling.GenerateParent(generated, other, Gender.Female, request, flag3); + other.SetMother(newMother); + } + generated.SetMother(other.GetMother()); + if (!flag2) { + Pawn newFather = PawnRelationWorker_Sibling.GenerateParent(generated, other, Gender.Male, request, flag3); + other.SetFather(newFather); + } + generated.SetFather(other.GetFather()); + if (!flag || !flag2) { + bool flag4 = other.GetMother().story.traits.HasTrait(TraitDefOf.Gay) || other.GetFather().story.traits.HasTrait(TraitDefOf.Gay); + if (flag4) { + other.GetFather().relations.AddDirectRelation(PawnRelationDefOf.ExLover, other.GetMother()); + } + else if (flag3) { + other.GetFather().relations.AddDirectRelation(PawnRelationDefOf.Spouse, other.GetMother()); + } + else { + LovePartnerRelationUtility.GiveRandomExLoverOrExSpouseRelation(other.GetFather(), other.GetMother()); + } + } + PawnRelationWorker_Sibling.ResolveMyName(ref request, generated); + PawnRelationWorker_Sibling.ResolveMySkinColor(ref request, generated); + } + } + + public override float GenerationChance(Pawn generated, Pawn other, PawnGenerationRequest request) + { + float num = 1; + float num2 = 1; + if (other.GetFather() != null || other.GetMother() != null) { + num = ChildRelationUtility.ChanceOfBecomingChildOf(generated, other.GetFather(), other.GetMother(), new PawnGenerationRequest?(request), null, null); + } + else if (request.FixedSkinWhiteness.HasValue) { + num2 = ChildRelationUtility.GetSkinSimilarityFactor(request.FixedSkinWhiteness.Value, other.story.skinWhiteness); + } + else { + num2 = PawnSkinColors.GetWhitenessCommonalityFactor(other.story.skinWhiteness); + } + float num3 = Mathf.Abs(generated.ageTracker.AgeChronologicalYearsFloat - other.ageTracker.AgeChronologicalYearsFloat); + float num4 = 1; + if (num3 > 40) { + num4 = 0.2f; + } + else if (num3 > 10) { + num4 = 0.65f; + } + return num * num2 * num4 * base.BaseGenerationChanceFactor(generated, other, request); + } + + public override bool InRelation(Pawn me, Pawn other) + { + return me != other && (me.GetMother() != null && me.GetFather() != null && me.GetMother() == other.GetMother() && me.GetFather() == other.GetFather()); + } + } +} diff --git a/Source/PrepareCarefully.cs b/Source/PrepareCarefully.cs new file mode 100644 index 0000000..69a9c99 --- /dev/null +++ b/Source/PrepareCarefully.cs @@ -0,0 +1,464 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public enum SortField + { + Name, + Cost + } + + public enum SortOrder + { + Ascending, + Descending + } + + public class PrepareCarefully + { + protected static PrepareCarefully instance = null; + public static PrepareCarefully Instance { + get { + if (instance == null) { + instance = new PrepareCarefully(); + } + return instance; + } + } + + public static void RemoveInstance() { + instance = null; + } + + protected EquipmentDatabase equipmentDatabase = null; + protected CostCalculator costCalculator = null; + + protected List pawns = new List(); + protected List equipment = new List(); + protected List removals = new List(); + protected bool active = false; + protected string filename = ""; + protected ImplantManager implantManager; + protected RelationshipManager relationshipManager; + protected HealthManager healthManager = new HealthManager(); + + public Page_ConfigureStartingPawns OriginalPage = null; + + public void NextPage() + { + if (OriginalPage != null) { + typeof(Page_ConfigureStartingPawns).GetMethod("DoNext", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(OriginalPage, null); + } + } + + protected Configuration config = new Configuration(); + public Configuration Config + { + get { + return config; + } + set { + config = value; + } + } + + protected State state = new State(); + public State State { + get { + return state; + } + } + + public RelationshipManager RelationshipManager { + get { + return relationshipManager; + } + } + + public HealthManager HealthManager { + get { + return healthManager; + } + } + + public SortField SortField { get; set; } + public SortOrder NameSortOrder { get; set; } + public SortOrder CostSortOrder { get; set; } + public int StartingPoints { get; set; } + + public int PointsRemaining { + get { + if (config.fixedPointsEnabled) { + return config.points - (int) Cost.total; + } + else { + return StartingPoints - (int) Cost.total; + } + } + } + + public PrepareCarefully() { + implantManager = new ImplantManager(); + NameSortOrder = SortOrder.Ascending; + CostSortOrder = SortOrder.Ascending; + SortField = SortField.Name; + } + + public void Configure(object o) + { + config = new Configuration(); + if (o == null) { + return; + } + CopyConfiguration("minColonists", o, 0); + CopyConfiguration("maxColonists", o, 0); + CopyConfiguration("points", o, -1); + } + + protected void CopyConfiguration(string fieldName, object o, object ignoreValue) + { + FieldInfo sourceField = o.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.Instance); + if (sourceField == null) { + return; + } + FieldInfo destField = typeof(Configuration).GetField(fieldName, BindingFlags.Public | BindingFlags.Instance); + if (destField == null) { + Log.Warning("Invalid Prepare Carefully configuration: " + fieldName); + return; + } + try { + object value = sourceField.GetValue(o); + if (object.Equals(value, ignoreValue)) { + return; + } + else { + destField.SetValue(this.config, value); + } + } + catch (Exception e) { + Log.Warning("Failed to set configuration in Prepare Carefully for " + fieldName); + Log.Warning(e.ToString()); + } + } + + public void Clear() + { + this.Active = false; + this.equipmentDatabase = new EquipmentDatabase(); + this.costCalculator = new CostCalculator(); + this.pawns.Clear(); + this.equipment.Clear(); + } + + protected Dictionary facadeToPawnMap = new Dictionary(); + protected Dictionary pawnToFacadeMap = new Dictionary(); + + public void Initialize() + { + Clear(); + InitializePawns(); + InitializeRelationshipManager(this.pawns); + InitializeDefaultEquipment(); + + this.StartingPoints = (int) this.Cost.total; + + this.state = new State(); + } + + // TODO: Alpha 14 + // Think about whether or not this is the best approach. Might need to do a bug report for the vanilla game? + // The tribal scenario adds a weapon with an invalid thing/stuff combination (jade knife). The + // knife ThingDef should allow the jade material, but it does not. We need this workaround to + // add the normally disallowed equipment to our equipment database. + protected EquipmentDatabaseEntry AddNonStandardScenarioEquipmentEntry(EquipmentKey key) + { + int type = equipmentDatabase.ClassifyThingDef(key.thingDef); + return equipmentDatabase.AddThingDefWithStuff(key.thingDef, key.stuffDef, type); + } + + protected void InitializeDefaultEquipment() + { + // Go through all of the scenario steps that scatter resources near the player starting location and add + // them to the resource/equipment list. + foreach (ScenPart part in Verse.Find.Scenario.AllParts) { + ScenPart_ScatterThingsNearPlayerStart nearPlayerStart = part as ScenPart_ScatterThingsNearPlayerStart; + if (nearPlayerStart != null) { + FieldInfo thingDefField = typeof(ScenPart_ScatterThingsNearPlayerStart).GetField("thingDef", BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo stuffDefField = typeof(ScenPart_ScatterThingsNearPlayerStart).GetField("stuff", BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo countField = typeof(ScenPart_ScatterThingsNearPlayerStart).GetField("count", BindingFlags.Instance | BindingFlags.NonPublic); + ThingDef thingDef = (ThingDef)thingDefField.GetValue(nearPlayerStart); + ThingDef stuffDef = (ThingDef)stuffDefField.GetValue(nearPlayerStart); + int count = (int)countField.GetValue(nearPlayerStart); + EquipmentKey key = new EquipmentKey(thingDef, stuffDef); + EquipmentDatabaseEntry entry = equipmentDatabase[key]; + if (entry == null) { + entry = AddNonStandardScenarioEquipmentEntry(key); + } + if (entry != null) { + AddEquipment(entry, count); + } + } + + // Go through all of the scenario steps that place starting equipment with the colonists and + // add them to the resource/equipment list. + ScenPart_StartingThing_Defined startingThing = part as ScenPart_StartingThing_Defined; + if (startingThing != null) { + FieldInfo thingDefField = typeof(ScenPart_StartingThing_Defined).GetField("thingDef", BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo stuffDefField = typeof(ScenPart_StartingThing_Defined).GetField("stuff", BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo countField = typeof(ScenPart_StartingThing_Defined).GetField("count", BindingFlags.Instance | BindingFlags.NonPublic); + ThingDef thingDef = (ThingDef)thingDefField.GetValue(startingThing); + ThingDef stuffDef = (ThingDef)stuffDefField.GetValue(startingThing); + int count = (int)countField.GetValue(startingThing); + EquipmentKey key = new EquipmentKey(thingDef, stuffDef); + EquipmentDatabaseEntry entry = equipmentDatabase[key]; + if (entry == null) { + entry = AddNonStandardScenarioEquipmentEntry(key); + } + if (entry != null) { + AddEquipment(entry, count); + } + } + + // Go through all of the scenario steps that spawn a pet and add the pet to the equipment/resource + // list. + ScenPart_StartingAnimal animal = part as ScenPart_StartingAnimal; + if (animal != null) { + FieldInfo animalCountField = typeof(ScenPart_StartingAnimal).GetField("count", BindingFlags.Instance | BindingFlags.NonPublic); + int count = (int)animalCountField.GetValue(animal); + for (int i = 0; i < count; i++) { + AddEquipment(RandomPet(animal)); + } + } + } + } + + private static EquipmentDatabaseEntry 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); + animalKindDef = animalKindDefs.RandomElementByWeight((PawnKindDef td) => td.RaceProps.petness); + } + + List entries = PrepareCarefully.Instance.EquipmentEntries.Animals.FindAll((EquipmentDatabaseEntry e) => { + return e.def == animalKindDef.race; + }); + if (entries.Count > 0) { + EquipmentDatabaseEntry entry = entries.RandomElement(); + return entry; + } + else { + return null; + } + } + + public bool Active + { + get { + return active; + } + set { + active = value; + } + } + public string Filename + { + get { + return filename; + } + set { + filename = value; + } + } + public void ClearPawns() + { + pawns.Clear(); + } + public void AddPawn(CustomPawn customPawn) + { + pawns.Add(customPawn); + } + public void RemovePawn(CustomPawn customPawn) + { + pawns.Remove(customPawn); + } + public List Pawns { + get { + return pawns; + } + } + public EquipmentDatabase EquipmentEntries + { + get { + if (equipmentDatabase == null) { + equipmentDatabase = new EquipmentDatabase(); + } + return equipmentDatabase; + } + } + + protected List colonists = new List(); + + public void CreateColonists() + { + colonists.Clear(); + foreach (CustomPawn customPawn in pawns) { + colonists.Add(customPawn.ConvertToPawn(false)); + } + + Dictionary pawnLookup = new Dictionary(); + + for (int i = 0; i < pawns.Count; i++) { + CustomPawn customPawn = pawns[i]; + Pawn pawn = colonists[i]; + pawnLookup[customPawn] = pawn; + } + + // Add relationships in two passes. Add the non-implied relationships first and then the implied + // relationships second. This tends to result in less weirdness. + AddRelationships(pawnLookup, RelationshipManager.ExplicitRelationships.Where((CustomRelationship r) => { + return r.def.implied == false; + })); + AddRelationships(pawnLookup, RelationshipManager.ExplicitRelationships.Where((CustomRelationship r) => { + return r.def.implied == true; + })); + } + + protected void AddRelationships(Dictionary lookup, IEnumerable relationships) + { + PawnGenerationRequest request = new PawnGenerationRequest(); + foreach (CustomRelationship r in RelationshipManager.ExplicitRelationships) { + PawnRelationWorker worker = this.relationshipManager.FindPawnRelationWorker(r.def); + if (worker.GetType().GetMethod("CreateRelation", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) == null) + { + continue; + } + Pawn source = lookup[r.source]; + Pawn target = lookup[r.target]; + worker.CreateRelation(source, target, ref request); + } + } + + public List Colonists { + get { + return colonists; + } + } + + public void ReplaceColonists() + { + List result = new List(); + foreach (CustomPawn customPawn in pawns) { + result.Add(customPawn.ConvertToPawn()); + } + Verse.Find.GameInitData.startingPawns = result; + } + + public List Equipment { + get { + SyncRemovals(); + return equipment; + } + } + + public bool AddEquipment(EquipmentDatabaseEntry entry) + { + SelectedEquipment e = Find(entry); + if (e == null) { + equipment.Add(new SelectedEquipment(entry)); + return true; + } + else { + e.count += entry.stackSize; + return false; + } + } + + public bool AddEquipment(EquipmentDatabaseEntry entry, int count) + { + SelectedEquipment e = Find(entry); + if (e == null) { + equipment.Add(new SelectedEquipment(entry, count)); + return true; + } + else { + e.count += count; + return false; + } + } + + public void RemoveEquipment(SelectedEquipment equipment) + { + removals.Add(equipment); + } + + public void RemoveEquipment(EquipmentDatabaseEntry entry) + { + SelectedEquipment e = Find(entry); + if (e != null) { + removals.Add(e); + } + } + + protected void SyncRemovals() + { + if (removals.Count > 0) { + foreach (var e in removals) { + equipment.Remove(e); + } + removals.Clear(); + } + } + + public SelectedEquipment Find(EquipmentDatabaseEntry entry) + { + return equipment.Find((SelectedEquipment e) => { + return e.def == entry.def && e.stuffDef == entry.stuffDef && e.gender == entry.gender; + }); + } + + CostDetails cost = new CostDetails(); + public CostDetails Cost + { + get { + if (costCalculator == null) { + costCalculator = new CostCalculator(); + } + costCalculator.Calculate(cost, this.pawns, this.equipment); + return cost; + } + } + + public void InitializePawns() + { + this.facadeToPawnMap.Clear(); + this.pawnToFacadeMap.Clear(); + foreach (Pawn p in Verse.Find.GameInitData.startingPawns) { + CustomPawn f = new CustomPawn(p); + facadeToPawnMap.Add(f, p); + pawnToFacadeMap.Add(p, f); + this.pawns.Add(f); + healthManager.InjuryManager.InitializePawnInjuries(p, f); + } + } + + public void InitializeRelationshipManager(List pawns) + { + List facades = new List(); + foreach (Pawn pawn in Verse.Find.GameInitData.startingPawns) { + facades.Add(pawnToFacadeMap[pawn]); + } + relationshipManager = new RelationshipManager(Verse.Find.GameInitData.startingPawns, facades); + } + } +} + diff --git a/Source/PresetFiles.cs b/Source/PresetFiles.cs new file mode 100644 index 0000000..3d19e83 --- /dev/null +++ b/Source/PresetFiles.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + 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" }); + } + catch (Exception e) { + Log.Error("Failed to get preset save directory"); + throw e; + } + } + } + + public static string FilePathForSavedPreset(string presetName) + { + return Path.Combine(SavedPresetsFolderPath, presetName + ".pcp"); + } + + // + // Static Properties + // + public static IEnumerable AllFiles { + get { + DirectoryInfo directoryInfo = new DirectoryInfo(SavedPresetsFolderPath); + if (!directoryInfo.Exists) { + directoryInfo.Create(); + } + return from f in directoryInfo.GetFiles() + where f.Extension == ".pcp" + orderby f.LastWriteTime descending + select f; + } + } + + // + // Static Methods + // + public static bool HavePresetNamed(string presetName) + { + foreach (string current in from f in AllFiles + select Path.GetFileNameWithoutExtension(f.Name)) { + if (current == presetName) { + return true; + } + } + return false; + } + + public static string UnusedDefaultName() + { + string text = string.Empty; + int num = 1; + do { + text = "Preset" + num.ToString(); + num++; + } + while (HavePresetNamed(text)); + return text; + } + } +} + diff --git a/Source/PresetLoader.cs b/Source/PresetLoader.cs new file mode 100644 index 0000000..3da2ad5 --- /dev/null +++ b/Source/PresetLoader.cs @@ -0,0 +1,47 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class PresetLoader + { + public static bool LoadFromFile(PrepareCarefully loadout, string presetName) + { + string version = ""; + bool result = false; + try { + Scribe.InitLoading(PresetFiles.FilePathForSavedPreset(presetName)); + Scribe_Values.LookValue(ref version, "version", "unknown", false); + } + catch (Exception e) { + Log.Error("Failed to load preset file"); + throw e; + } + finally { + Scribe.mode = LoadSaveMode.Inactive; + } + + if ("1".Equals(version)) { + Messages.Message("EdB.PrepareCarefully.PresetVersionNotSupported".Translate(), MessageSound.SeriousAlert); + return false; + } + else if ("2".Equals(version)) { + Messages.Message("EdB.PrepareCarefully.PresetVersionNotSupported".Translate(), MessageSound.SeriousAlert); + return false; + } + else if ("3".Equals(version)) { + result = new PresetLoaderVersion3().Load(loadout, presetName); + } + else { + throw new Exception("Invalid preset version"); + } + + return result; + } + } + +} + diff --git a/Source/PresetSaver.cs b/Source/PresetSaver.cs new file mode 100644 index 0000000..24a7e6c --- /dev/null +++ b/Source/PresetSaver.cs @@ -0,0 +1,57 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using Verse; + +namespace EdB.PrepareCarefully +{ + public static class PresetSaver + { + // + // Static Methods + // + public static void SaveToFile(PrepareCarefully data, string presetName) + { + try { + Scribe.InitWriting(PresetFiles.FilePathForSavedPreset(presetName), "preset"); + string versionStringFull = "3"; + Scribe_Values.LookValue(ref versionStringFull, "version", null, false); + bool usePoints = data.Config.pointsEnabled; + int startingPoints = PrepareCarefully.Instance.StartingPoints; + Scribe_Values.LookValue(ref usePoints, "usePoints", false, true); + Scribe_Values.LookValue(ref startingPoints, "startingPoints", 0, true); + string modString = GenText.ToCommaList(Enumerable.Select(LoadedModManager.RunningMods, (Func) (mod => mod.Name)), true); + Scribe_Values.LookValue(ref modString, "mods", null, false); + Scribe.EnterNode("colonists"); + foreach (CustomPawn customPawn in data.Pawns) { + SaveRecordPawnV3 pawn = new SaveRecordPawnV3(customPawn); + Scribe_Deep.LookDeep(ref pawn, "colonist"); + } + Scribe.ExitNode(); + + Scribe.EnterNode("relationships"); + foreach (var r in data.RelationshipManager.ExplicitRelationships) { + SaveRecordRelationshipV3 s = new SaveRecordRelationshipV3(r); + Scribe_Deep.LookDeep(ref s, "relationship"); + } + Scribe.ExitNode(); + + Scribe.EnterNode("equipment"); + foreach (var e in data.Equipment) { + SelectedEquipment customPawn = e; + Scribe_Deep.LookDeep(ref customPawn, "equipment"); + } + Scribe.ExitNode(); + } + catch (Exception e) { + Log.Error("Failed to save preset file"); + throw e; + } + finally { + Scribe.FinalizeWriting(); + Scribe.mode = LoadSaveMode.Inactive; + } + } + } +} diff --git a/Source/Randomizer.cs b/Source/Randomizer.cs new file mode 100644 index 0000000..9a38ba3 --- /dev/null +++ b/Source/Randomizer.cs @@ -0,0 +1,95 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class Randomizer + { + public Pawn GenerateColonist() + { + PawnKindDef kindDef = Faction.OfPlayer.def.basicMemberKind; + Pawn pawn = PawnGenerator.GeneratePawn(kindDef, Faction.OfPlayer); + return pawn; + } + + public void RandomizeAll(CustomPawn customPawn) + { + Pawn pawn = GenerateColonist(); + customPawn.InitializeWithPawn(pawn); + } + + public void RandomizeBackstory(CustomPawn customPawn) + { + Pawn pawn = GenerateColonist(); + customPawn.Adulthood = pawn.story.adulthood; + customPawn.Childhood = pawn.story.childhood; + } + + public void RandomizeTraits(CustomPawn customPawn) + { + Pawn pawn = GenerateColonist(); + List traits = pawn.story.traits.allTraits; + if (traits.Count > 0) { + customPawn.SetTrait(0, traits[0]); + } + else { + customPawn.SetTrait(0, null); + } + if (traits.Count > 1 && customPawn.GetTrait(0) != traits[1]) { + customPawn.SetTrait(1, traits[1]); + } + else { + customPawn.SetTrait(1, null); + } + if (traits.Count > 2 && customPawn.GetTrait(0) != traits[2] && customPawn.GetTrait(1) != traits[2]) { + customPawn.SetTrait(2, traits[2]); + } + else { + customPawn.SetTrait(2, null); + } + } + + public void RandomizePawn(CustomPawn customPawn) + { + Pawn pawn; + int tries = 0; + do { + pawn = GenerateColonist(); + tries++; + } + while (pawn.gender != customPawn.Gender && tries < 1000); + + customPawn.HairDef = pawn.story.hairDef; + customPawn.SetColor(PawnLayers.Hair, pawn.story.hairColor); + customPawn.HeadGraphicPath = pawn.story.HeadGraphicPath; + customPawn.SetColor(PawnLayers.BodyType, pawn.story.SkinColor); + + for (int i = 0; i < PawnLayers.Count; i++) { + if (PawnLayers.IsApparelLayer(i)) { + customPawn.SetSelectedStuff(i, null); + customPawn.SetSelectedApparel(i, null); + } + } + foreach (Apparel current in pawn.apparel.WornApparel) { + int layer = PawnLayers.ToPawnLayerIndex(current.def.apparel); + if (layer != -1) { + customPawn.SetSelectedStuff(layer, current.Stuff); + customPawn.SetSelectedApparel(layer, current.def); + } + } + } + + public void RandomizeName(CustomPawn customPawn) + { + Pawn pawn = GenerateColonist(); + pawn.gender = customPawn.Gender; + Name name = NameGenerator.GeneratePawnName(pawn, NameStyle.Full, null); + NameTriple nameTriple = name as NameTriple; + customPawn.Name = nameTriple; + } + } +} + diff --git a/Source/RelationshipList.cs b/Source/RelationshipList.cs new file mode 100644 index 0000000..307c3e3 --- /dev/null +++ b/Source/RelationshipList.cs @@ -0,0 +1,30 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class RelationshipList : List + { + public bool Contains(PawnRelationDef def, CustomPawn source, CustomPawn target) + { + return Find(def, source, target) != null; + } + + public CustomRelationship Find(PawnRelationDef def, CustomPawn source, CustomPawn target) + { + foreach (var r in this) { + if (r.def == def && r.source == source && r.target == target) { + return r; + } + else if (r.inverseDef == def && r.source == target && r.target == source) { + return r; + } + } + return null; + } + } +} + diff --git a/Source/RelationshipManager.cs b/Source/RelationshipManager.cs new file mode 100644 index 0000000..c764937 --- /dev/null +++ b/Source/RelationshipManager.cs @@ -0,0 +1,302 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class RelationshipManager + { + protected Randomizer randomizer = new Randomizer(); + protected List allowedRelationships = new List(); + protected Dictionary inverseRelationships = new Dictionary(); + protected Dictionary facadeToSimulatedPawnMap = new Dictionary(); + protected Dictionary simulatedPawnToFacadeMap = new Dictionary(); + protected List deletionList = new List(); + protected bool dirty = true; + + protected RelationshipList relationships = new RelationshipList(); + protected RelationshipList derivedRelationships = new RelationshipList(); + + public RelationshipManager(List originalPawns, List correspondingFacades) + { + + + PopulateAllowedRelationships(); + PopulateInverseRelationships(); + InitializeRelationships(originalPawns, correspondingFacades); + ResetSimulation(); + } + + protected void PopulateAllowedRelationships() + { + allowedRelationships.AddRange(DefDatabase.AllDefs.ToList().FindAll((PawnRelationDef def) => { + CarefullyPawnRelationDef extended = DefDatabase.GetNamedSilentFail(def.defName); + if (extended != null && extended.animal) { + return false; + } + MethodInfo info = def.workerClass.GetMethod("CreateRelation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + if (info == null) { + return false; + } + else { + return true; + } + })); + + } + + protected void PopulateInverseRelationships() + { + foreach (var def in DefDatabase.AllDefs) { + PawnRelationDef inverse = null; + CarefullyPawnRelationDef extended = DefDatabase.GetNamedSilentFail(def.defName); + if (extended != null && extended.inverse != null) { + inverse = DefDatabase.GetNamedSilentFail(extended.inverse); + } + else { + inverse = ComputeInverseRelationship(def); + } + if (inverse != null) { + inverseRelationships[def] = inverse; + //Log.Message(def.defName + " is inverse of " + inverse.defName); + } + } + } + + public PawnRelationDef FindInverseRelationship(PawnRelationDef def) + { + PawnRelationDef inverse; + if (inverseRelationships.TryGetValue(def, out inverse)) { + return inverse; + } + else { + return null; + } + } + + public PawnRelationDef ComputeInverseRelationship(PawnRelationDef def) + { + Pawn source = randomizer.GenerateColonist(); + Pawn target = randomizer.GenerateColonist(); + MethodInfo info = def.workerClass.GetMethod("CreateRelation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + if (info == null) { + return null; + } + var worker = FindPawnRelationWorker(def); + PawnGenerationRequest req = new PawnGenerationRequest(); + worker.CreateRelation(source, target, ref req); + foreach (PawnRelationDef d in PawnRelationUtility.GetRelations(target, source)) { + return d; + } + return null; + } + + public PawnRelationWorker FindPawnRelationWorker(PawnRelationDef def) + { + CarefullyPawnRelationDef carefullyDef = DefDatabase.GetNamedSilentFail(def.defName); + if (carefullyDef == null || carefullyDef.workerClass == null) { + return def.Worker; + } + else { + PawnRelationWorker worker = carefullyDef.Worker; + if (worker != null) { + return carefullyDef.Worker; + } + else { + return def.Worker; + } + } + } + + public void InitializeRelationships(List pawns, List correspondingFacades) + { + // Create a map so that we can lookup pawn facades based on their matching original pawn. + Dictionary pawnToFacadeMap = new Dictionary(); + int pawnCount = pawns.Count; + for (int i = 0; i < pawns.Count; i++) { + pawnToFacadeMap.Add(pawns[i], correspondingFacades[i]); + } + + // Go through each pawn and check for relationships between it and all other pawns. + foreach (Pawn pawn in pawns) { + foreach (Pawn other in pawns) { + if (pawn == other) { + continue; + } + + // Find the corresponding pawn facades. + CustomPawn thisPawnFacade = pawnToFacadeMap[pawn]; + CustomPawn otherPawnFacade = pawnToFacadeMap[other]; + + // Go through each relationship between the two pawns. + foreach (PawnRelationDef def in PawnRelationUtility.GetRelations(pawn, other)) { + // If no relationship records exists for this relationship, add it. + if (!relationships.Contains(def, thisPawnFacade, otherPawnFacade)) { + relationships.Add(new CustomRelationship(def, FindInverseRelationship(def), thisPawnFacade, otherPawnFacade)); + } + } + } + } + } + + public void Clear() + { + this.relationships.Clear(); + this.derivedRelationships.Clear(); + Clean(); + } + + protected void ResetSimulation() { + facadeToSimulatedPawnMap.Clear(); + simulatedPawnToFacadeMap.Clear(); + foreach (CustomPawn customPawn in PrepareCarefully.Instance.Pawns) { + Pawn simulatedPawn = randomizer.GenerateColonist(); + simulatedPawn.relations.ClearAllRelations(); + simulatedPawn.Name = new NameTriple(customPawn.Name.First, customPawn.Name.Nick, customPawn.Name.Last); + facadeToSimulatedPawnMap.Add(customPawn, simulatedPawn); + simulatedPawnToFacadeMap.Add(simulatedPawn, customPawn); + } + + AddAllRelationshipsToSimulation(false); + AddAllRelationshipsToSimulation(true); + + dirty = true; + } + + protected void AddAllRelationshipsToSimulation(bool implied) + { + PawnGenerationRequest request = new PawnGenerationRequest(); + foreach (CustomRelationship r in relationships) { + if (r.def.implied == implied) { + if (r.def.workerClass.GetMethod("CreateRelation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) == null) { + continue; + } + Pawn simulatedSource = facadeToSimulatedPawnMap[r.source]; + Pawn simulatedTarget = facadeToSimulatedPawnMap[r.target]; + FindPawnRelationWorker(r.def).CreateRelation(simulatedSource, simulatedTarget, ref request); + } + } + } + + public IEnumerable AllowedRelationships { + get { + return allowedRelationships; + } + } + + public IEnumerable ExplicitRelationships { + get { + return relationships; + } + } + + public IEnumerable AllRelationships { + get { + return relationships.Concat(DerivedRelationships); + } + } + + public RelationshipList DerivedRelationships { + get { + if (dirty) { + Clean(); + } + return derivedRelationships; + } + } + + protected void Clean() { + DeleteRelationships(); + ResetSimulation(); + RecreateDerivedRelationships(); + ResetPawnRelationshipsFlag(); + dirty = false; + } + + protected void ResetPawnRelationshipsFlag() + { + foreach (var p in PrepareCarefully.Instance.Pawns) { + p.HasRelationships = false; + } + foreach (var r in relationships) { + r.source.HasRelationships = true; + r.target.HasRelationships = true; + } + foreach (var r in derivedRelationships) { + r.source.HasRelationships = true; + r.target.HasRelationships = true; + } + } + + protected void DeleteRelationships() + { + foreach (var r in deletionList) { + relationships.Remove(r); + } + deletionList.Clear(); + } + + public void RecreateDerivedRelationships() + { + derivedRelationships.Clear(); + + // Go through each pawn and check for relationships between it and all other pawns. + foreach (Pawn pawn in simulatedPawnToFacadeMap.Keys) { + foreach (Pawn other in simulatedPawnToFacadeMap.Keys) { + if (pawn == other) { + continue; + } + + // Find the corresponding pawn facades. + CustomPawn thisPawnFacade = simulatedPawnToFacadeMap[pawn]; + CustomPawn otherPawnFacade = simulatedPawnToFacadeMap[other]; + + // Go through each relationship between the two pawns. + foreach (PawnRelationDef def in PawnRelationUtility.GetRelations(pawn, other)) { + // Don't add a derived relationship if we don't know what it's inverse is. + PawnRelationDef inverse = FindInverseRelationship(def); + if (inverse == null) { + Log.Warning("Did not add derived relationship. Could not determine inverse relationship."); + continue; + } + // If no explicit relationship records exists for this relationship, add it as a derived relationship. + if (!relationships.Contains(def, thisPawnFacade, otherPawnFacade) + && !derivedRelationships.Contains(def, thisPawnFacade, otherPawnFacade)) { + derivedRelationships.Add(new CustomRelationship(def, inverse, thisPawnFacade, otherPawnFacade, false)); + } + } + } + } + } + + public void AddRelationship(PawnRelationDef def, CustomPawn source, CustomPawn target) { + if (def.workerClass.GetMethod("CreateRelation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) == null) { + return; + } + this.relationships.Add(new CustomRelationship(def, FindInverseRelationship(def), source, target)); + dirty = true; + } + + public void DeleteRelationship(PawnRelationDef def, CustomPawn source, CustomPawn target) { + CustomRelationship toRemove = relationships.Find(def, source, target); + if (toRemove != null) { + deletionList.Add(toRemove); + } + dirty = true; + } + + public void DeletePawnRelationships(CustomPawn pawn) { + List toDelete = new List(); + foreach (var r in relationships) { + if (r.source == pawn || r.target == pawn) { + deletionList.Add(r); + } + } + dirty = true; + } + } +} + diff --git a/Source/ScrollView.cs b/Source/ScrollView.cs new file mode 100644 index 0000000..368bd2a --- /dev/null +++ b/Source/ScrollView.cs @@ -0,0 +1,98 @@ +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class ScrollView + { + public static readonly float ScrollbarSize = 15; + private float contentHeight; + private Vector2 position = Vector2.zero; + private Rect viewRect; + private Rect contentRect; + private bool consumeScrollEvents = true; + + public float ViewHeight { + get { + return viewRect.height; + } + } + + public float ViewWidth { + get { + return viewRect.width; + } + } + + public float ContentWidth { + get { + return contentRect.width; + } + } + + public float ContentHeight { + get { + return contentHeight; + } + } + + public Vector2 Position { + get { + return position; + } + } + + public bool ScrollbarsVisible { + get { + return ContentHeight > ViewHeight; + } + } + + public ScrollView() { + + } + + public ScrollView(bool consumeScrollEvents) { + this.consumeScrollEvents = consumeScrollEvents; + } + + public void Begin(Rect viewRect) + { + this.viewRect = viewRect; + this.contentRect = new Rect(0, 0, viewRect.width - 16, contentHeight); + if (consumeScrollEvents) { + Widgets.BeginScrollView(viewRect, ref position, contentRect); + } + else { + BeginScrollView(viewRect, ref position, contentRect); + } + } + + public void End(float yPosition) + { + if (Event.current.type == EventType.Layout) { + contentHeight = yPosition; + } + Widgets.EndScrollView(); + } + + protected static void BeginScrollView(Rect outRect, ref Vector2 scrollPosition, Rect viewRect) + { + Vector2 vector = scrollPosition; + Vector2 vector2 = GUI.BeginScrollView(outRect, scrollPosition, viewRect); + Vector2 vector3; + if (Event.current.type == EventType.MouseDown) { + vector3 = vector; + } + else { + vector3 = vector2; + } + if (Event.current.type == EventType.ScrollWheel && Mouse.IsOver(outRect)) { + vector3 += Event.current.delta * 40; + } + scrollPosition = vector3; + } + } +} + diff --git a/Source/SelectedEquipment.cs b/Source/SelectedEquipment.cs new file mode 100644 index 0000000..cbece68 --- /dev/null +++ b/Source/SelectedEquipment.cs @@ -0,0 +1,102 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class SelectedEquipment : IExposable, IEquipment + { + public int count; + public ThingDef def; + public ThingDef stuffDef; + public Gender gender = Gender.None; + + public int Count + { + get { + return count; + } + } + + public ThingDef ThingDef + { + get { + return def; + } + } + + public ThingDef StuffDef + { + get { + return stuffDef; + } + } + + public Gender Gender + { + get { + return gender; + } + } + + public SelectedEquipment() + { + } + + public SelectedEquipment(EquipmentDatabaseEntry entry) + { + count = 1; + def = entry.def; + stuffDef = entry.stuffDef; + gender = entry.gender; + } + + public SelectedEquipment(EquipmentDatabaseEntry entry, int count) + { + this.count = count; + def = entry.def; + stuffDef = entry.stuffDef; + gender = entry.gender; + } + + public SelectedEquipment(ThingDef def, int count) + { + this.count = count; + this.def = def; + } + + public SelectedEquipment(ThingDef def, ThingDef stuffDef, int count) + { + this.count = count; + this.def = def; + this.stuffDef = stuffDef; + } + + public SelectedEquipment(ThingDef def, ThingDef stuffDef, Gender gender, int count) + { + this.count = count; + this.def = def; + this.stuffDef = stuffDef; + this.gender = gender; + } + + public void ExposeData() + { + Scribe_Values.LookValue(ref this.def.defName, "def", null, false); + string stuffDefName = this.stuffDef != null ? this.stuffDef.defName : null; + Scribe_Values.LookValue(ref stuffDefName, "stuffDef", null, false); + string genderName = this.gender != Gender.None ? this.gender.ToString() : null; + Scribe_Values.LookValue(ref genderName, "gender", null, false); + Scribe_Values.LookValue(ref this.count, "count", 0, false); + } + + public EquipmentKey EquipmentKey { + get { + return new EquipmentKey(def, stuffDef, gender); + } + } + } +} + diff --git a/Source/StandardEquipment.cs b/Source/StandardEquipment.cs new file mode 100644 index 0000000..fa7a242 --- /dev/null +++ b/Source/StandardEquipment.cs @@ -0,0 +1,166 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class StandardEquipment : IEquipment + { + public ThingDef thingDef; + public ThingDef stuffDef; + public Gender gender = Gender.None; + + protected int numStacks = -1; + protected int minNumStacks = -1; + protected int maxNumStacks = -1; + + protected int countPerStack = -1; + protected int minCountPerStack = -1; + protected int maxCountPerStack = -1; + + public StandardEquipment() + { + } + + public StandardEquipment(ThingDef def, int count) + { + this.thingDef = def; + this.NumStacks = 1; + this.CountPerStack = count; + } + + public StandardEquipment(ThingDef def, ThingDef stuffDef, int count) + { + this.thingDef = def; + this.stuffDef = stuffDef; + this.NumStacks = 1; + this.CountPerStack = count; + } + + public StandardEquipment(ThingDef def, ThingDef stuffDef, int numStacks, int minCountPerStack, int maxCountPerStack) + { + this.thingDef = def; + this.stuffDef = stuffDef; + this.NumStacks = numStacks; + this.minCountPerStack = minCountPerStack; + this.maxCountPerStack = maxCountPerStack; + } + + public StandardEquipment(ThingDef def, ThingDef stuffDef, int minNumStacks, int maxNumStacks, int minCountPerStack, int maxCountPerStack) + { + this.thingDef = def; + this.stuffDef = stuffDef; + this.minNumStacks = minNumStacks; + this.maxNumStacks = maxNumStacks; + this.minCountPerStack = minCountPerStack; + this.maxCountPerStack = maxCountPerStack; + } + + public ThingDef ThingDef + { + get { + return thingDef; + } + } + + public ThingDef StuffDef + { + get { + return stuffDef; + } + } + + public Gender Gender + { + get { + return gender; + } + } + + public int Count + { + get { + int stacks; + if (numStacks > -1) { + stacks = numStacks; + } + else { + stacks = new IntRange(minNumStacks, maxNumStacks).RandomInRange; + } + if (countPerStack > -1) { + return stacks * countPerStack; + } + + int count = 0; + IntRange range = new IntRange(minCountPerStack, maxCountPerStack); + for (int i = 0; i < numStacks; i++) { + count += range.RandomInRange; + } + return count; + } + } + + public int NumStacks + { + get { + return numStacks; + } + set { + numStacks = minNumStacks = maxNumStacks = value; + } + } + + public int MinNumStacks + { + get { + return minNumStacks; + } + set { + minNumStacks = value; + } + } + + public int MaxNumStacks + { + get { + return maxNumStacks; + } + set { + maxNumStacks = value; + } + } + + public int CountPerStack + { + get { + return countPerStack; + } + set { + countPerStack = minCountPerStack = maxCountPerStack = value; + } + } + + public int MinCountPerStack + { + get { + return minCountPerStack; + } + set { + minCountPerStack = value; + } + } + + public int MaxCountPerStack + { + get { + return maxCountPerStack; + } + set { + maxCountPerStack = value; + } + } + + } +} + diff --git a/Source/State.cs b/Source/State.cs new file mode 100644 index 0000000..1581d54 --- /dev/null +++ b/Source/State.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace EdB.PrepareCarefully +{ + public class State + { + protected int currentPawnIndex; + + public int CurrentPawnIndex { + get { + return currentPawnIndex; + } + set { + currentPawnIndex = value; + } + } + + public CustomPawn CurrentPawn { + get { + return PrepareCarefully.Instance.Pawns[currentPawnIndex]; + } + } + } +} + diff --git a/Source/TabRecordExtensions.cs b/Source/TabRecordExtensions.cs new file mode 100644 index 0000000..408f328 --- /dev/null +++ b/Source/TabRecordExtensions.cs @@ -0,0 +1,78 @@ +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public static class TabRecordExtensions + { + public static void DrawTab(this TabRecord tab, Rect rect) + { + Rect drawRect = new Rect(rect); + drawRect.width = 30; + Rect drawRect2 = new Rect(rect); + drawRect2.width = 30; + drawRect2.x = rect.x + rect.width - 30; + Rect texRect = new Rect(0.53125f, 0, 0.46875f, 1); + Rect drawRect3 = new Rect(rect); + drawRect3.x += drawRect.width; + drawRect3.width -= 60; + Rect texRect2 = new Rect(30, 0, 4, (float)Textures.TextureTabAtlas.height).ToUVRect(new Vector2((float)Textures.TextureTabAtlas.width, (float)Textures.TextureTabAtlas.height)); + Widgets.DrawTexturePart(drawRect, new Rect(0, 0, 0.46875f, 1), Textures.TextureTabAtlas); + Widgets.DrawTexturePart(drawRect3, texRect2, Textures.TextureTabAtlas); + Widgets.DrawTexturePart(drawRect2, texRect, Textures.TextureTabAtlas); + Rect rect2 = rect; + if (rect.Contains(Event.current.mousePosition)) { + GUI.color = Color.yellow; + } + Widgets.Label(new Rect(rect2.x - 6, rect2.y, rect2.width, rect2.height), tab.label); + GUI.color = Color.white; + if (!tab.selected) { + Rect drawRect4 = new Rect(rect); + drawRect4.y += rect.height; + drawRect4.y -= 1; + drawRect4.height = 1; + Rect texRect3 = new Rect(0.5f, 0.01f, 0.01f, 0.01f); + Widgets.DrawTexturePart(drawRect4, texRect3, Textures.TextureTabAtlas); + } + } + + public static void DrawNewColonistTab(this TabRecord tab, Rect rect) + { + if (rect.Contains(Event.current.mousePosition)) { + GUI.color = new Color(1, 1, 1, 0.7f); + } + else { + GUI.color = new Color(1, 1, 1, 0.2f); + } + Rect drawRect = new Rect(rect); + drawRect.width = 30; + Rect drawRect2 = new Rect(rect); + drawRect2.width = 30; + drawRect2.x = rect.x + rect.width - 30; + Rect texRect = new Rect(0.53125f, 0, 0.46875f, 1); + Rect drawRect3 = new Rect(rect); + drawRect3.x += drawRect.width; + drawRect3.width -= 60; + Rect texRect2 = new Rect(30, 0, 4, (float)Textures.TextureTabAtlas.height).ToUVRect(new Vector2((float)Textures.TextureTabAtlas.width, (float)Textures.TextureTabAtlas.height)); + Widgets.DrawTexturePart(drawRect, new Rect(0, 0, 0.46875f, 1), Textures.TextureTabAtlas); + Widgets.DrawTexturePart(drawRect3, texRect2, Textures.TextureTabAtlas); + Widgets.DrawTexturePart(drawRect2, texRect, Textures.TextureTabAtlas); + Rect rect2 = rect; + GUI.color = new Color(0.7f, 0.7f, 0.7f); + if (rect.Contains(Event.current.mousePosition)) { + Rect drawRect4 = new Rect(rect); + drawRect4.y += rect.height; + drawRect4.y -= 1; + drawRect4.height = 1; + Rect texRect3 = new Rect(0.5f, 0.01f, 0.01f, 0.01f); + Widgets.DrawTexturePart(drawRect4, texRect3, Textures.TextureTabAtlas); + + GUI.color = Color.yellow; + } + Widgets.Label(rect2, tab.label); + GUI.color = Color.white; + } + } +} + diff --git a/Source/Textures.cs b/Source/Textures.cs new file mode 100644 index 0000000..f5d216d --- /dev/null +++ b/Source/Textures.cs @@ -0,0 +1,81 @@ +using RimWorld; +using System; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + [StaticConstructorOnStartup] + public static class Textures + { + public static Texture2D TexturePassionMajor; + public static Texture2D TexturePassionMinor; + public static Texture2D TextureFieldAtlas; + public static Texture2D TexturePortraitBackground; + public static Texture2D TextureButtonPrevious; + public static Texture2D TextureButtonNext; + public static Texture2D TextureButtonRandom; + public static Texture2D TextureButtonRandomLarge; + public static Texture2D TexturePassionNone; + public static Texture2D TextureButtonDelete; + public static Texture2D TextureButtonDeleteTab; + public static Texture2D TextureButtonDeleteTabHighlight; + public static Texture2D TextureButtonReset; + public static Texture2D TextureButtonClearSkills; + public static Texture2D TextureAlert; + public static Texture2D TextureAlertSmall; + public static Texture2D TextureDerivedRelationship; + public static Texture2D TextureButtonAdd; + public static Texture2D TextureRadioButtonOff; + public static Texture2D TextureDeleteX; + public static Texture2D TextureAlternateRow; + public static Texture2D TextureSkillBarFill; + public static Texture2D TextureSortAscending; + public static Texture2D TextureSortDescending; + public static Texture2D TextureTabAtlas; + + static Textures() { + LoadTextures(); + } + + public static void Reset() + { + LongEventHandler.ExecuteWhenFinished(() => { + LoadTextures(); + }); + } + + private static void LoadTextures() + { + TexturePassionMajor = ContentFinder.Get("UI/Icons/PassionMajor", true); + TexturePassionMinor = ContentFinder.Get("UI/Icons/PassionMinor", true); + TextureRadioButtonOff = ContentFinder.Get("UI/Widgets/RadioButOff", true); + TexturePortraitBackground = ContentFinder.Get("EdB/PrepareCarefully/CharMakerPortraitBG", true); + TextureFieldAtlas = ContentFinder.Get("EdB/PrepareCarefully/FieldAtlas", true); + TextureButtonPrevious = ContentFinder.Get("EdB/PrepareCarefully/ButtonPrevious", true); + TextureButtonNext = ContentFinder.Get("EdB/PrepareCarefully/ButtonNext", true); + TextureButtonRandom = ContentFinder.Get("EdB/PrepareCarefully/ButtonRandom", true); + TextureButtonRandomLarge = ContentFinder.Get("EdB/PrepareCarefully/ButtonRandomLarge", true); + TexturePassionNone = ContentFinder.Get("EdB/PrepareCarefully/NoPassion", true); + TextureButtonDelete = ContentFinder.Get("EdB/PrepareCarefully/ButtonDelete", true); + TextureButtonDeleteTab = ContentFinder.Get("EdB/PrepareCarefully/ButtonDeleteTab", true); + TextureButtonDeleteTabHighlight = ContentFinder.Get("EdB/PrepareCarefully/ButtonDeleteTabHighlight", true); + TextureButtonReset = ContentFinder.Get("EdB/PrepareCarefully/ButtonReset", true); + TextureButtonClearSkills = ContentFinder.Get("EdB/PrepareCarefully/ButtonClear", true); + TextureAlert = ContentFinder.Get("EdB/PrepareCarefully/Alert", true); + TextureAlertSmall = ContentFinder.Get("EdB/PrepareCarefully/AlertSmall", true); + TextureDerivedRelationship = ContentFinder.Get("EdB/PrepareCarefully/DerivedRelationship", true); + TextureButtonAdd = ContentFinder.Get("EdB/PrepareCarefully/ButtonAdd", true); + TextureDeleteX = ContentFinder.Get("UI/Buttons/Delete", true); + TextureSortAscending = ContentFinder.Get("EdB/PrepareCarefully/SortAscending", true); + TextureSortDescending = ContentFinder.Get("EdB/PrepareCarefully/SortDescending", true); + TextureTabAtlas = ContentFinder.Get("UI/Widgets/TabAtlas", true); + + TextureAlternateRow = SolidColorMaterials.NewSolidColorTexture(new Color(1, 1, 1, 0.05f)); + TextureSkillBarFill = SolidColorMaterials.NewSolidColorTexture(new Color(1f, 1f, 1f, 0.1f)); + + + } + } +} + diff --git a/Source/Version3/ColonistLoaderVersion3.cs b/Source/Version3/ColonistLoaderVersion3.cs new file mode 100644 index 0000000..a752ef7 --- /dev/null +++ b/Source/Version3/ColonistLoaderVersion3.cs @@ -0,0 +1,55 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + + +namespace EdB.PrepareCarefully +{ + public class ColonistLoaderVersion3 + { + public bool Load(PrepareCarefully loadout, Page_ConfigureStartingPawnsCarefully charMakerPage, string colonistName) + { + SaveRecordPawnV3 pawnRecord = new SaveRecordPawnV3(); + string modString = ""; + string version = ""; + try { + Scribe.InitLoading(ColonistFiles.FilePathForSavedColonist(colonistName)); + Scribe_Values.LookValue(ref version, "version", "unknown", false); + Scribe_Values.LookValue(ref modString, "mods", "", false); + + try { + Scribe_Deep.LookDeep(ref pawnRecord, "colonist", null); + } + catch (Exception e) { + Messages.Message(modString, MessageSound.Silent); + Messages.Message("EdB.ColonistLoadFailed".Translate(), MessageSound.RejectInput); + Log.Warning(e.ToString()); + Log.Warning("Colonist was created with the following mods: " + modString); + return false; + } + } + catch (Exception e) { + Log.Error("Failed to load preset file"); + throw e; + } + finally { + Scribe.mode = LoadSaveMode.Inactive; + } + + PresetLoaderVersion3 loader = new PresetLoaderVersion3(); + charMakerPage.AddColonist(loader.LoadPawn(pawnRecord)); + if (loader.Failed) { + Messages.Message(loader.ModString, MessageSound.Silent); + Messages.Message("EdB.ColonistThingDefFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning("Preset was created with the following mods: " + modString); + return false; + } + + return true; + } + } +} + diff --git a/Source/Version3/PresetLoaderVersion3.cs b/Source/Version3/PresetLoaderVersion3.cs new file mode 100644 index 0000000..5b72369 --- /dev/null +++ b/Source/Version3/PresetLoaderVersion3.cs @@ -0,0 +1,474 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class PresetLoaderVersion3 + { + public bool Failed = false; + public string ModString = ""; + + public PresetLoaderVersion3() + { + } + + public bool Load(PrepareCarefully loadout, string presetName) + { + List pawns = new List(); + List savedRelationships = new List(); + Failed = false; + int startingPoints = 0; + bool usePoints = false; + try { + Scribe.InitLoading(PresetFiles.FilePathForSavedPreset(presetName)); + + Scribe_Values.LookValue(ref usePoints, "usePoints", true, false); + Scribe_Values.LookValue(ref startingPoints, "startingPoints", 0, false); + PrepareCarefully.Instance.StartingPoints = startingPoints; + Scribe_Values.LookValue(ref ModString, "mods", "", false); + + try { + Scribe_Collections.LookList(ref pawns, "colonists", LookMode.Deep, null); + } + catch (Exception e) { + Messages.Message(ModString, MessageSound.Silent); + Messages.Message("EdB.PresetPawnLoadFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning(e.ToString()); + Log.Warning("Preset was created with the following mods: " + ModString); + return false; + } + + try { + Scribe_Collections.LookList(ref savedRelationships, "relationships", LookMode.Deep, null); + } + catch (Exception e) { + Messages.Message(ModString, MessageSound.Silent); + Messages.Message("EdB.PresetPawnLoadFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning(e.ToString()); + Log.Warning("Preset was created with the following mods: " + ModString); + return false; + } + + List tempEquipment = new List(); + Scribe_Collections.LookList(ref tempEquipment, "equipment", LookMode.Deep, null); + + List equipment = new List(tempEquipment.Count); + foreach (var e in tempEquipment) { + ThingDef thingDef = DefDatabase.GetNamedSilentFail(e.def); + ThingDef stuffDef = null; + Gender gender = Gender.None; + if (!string.IsNullOrEmpty(e.stuffDef)) { + stuffDef = DefDatabase.GetNamedSilentFail(e.stuffDef); + } + if (!string.IsNullOrEmpty(e.gender)) { + try { + gender = (Gender) Enum.Parse(typeof(Gender), e.gender); + } + catch (Exception) { + Log.Warning("Failed to load gender value for animal."); + Failed = true; + continue; + } + } + if (thingDef != null) { + if (string.IsNullOrEmpty(e.stuffDef)) { + equipment.Add(new SelectedEquipment(thingDef, null, gender, e.count)); + } + else { + if (stuffDef != null) { + EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[new EquipmentKey(thingDef, stuffDef, gender)]; + if (entry == null) { + string thing = thingDef != null ? thingDef.defName : "null"; + string stuff = stuffDef != null ? stuffDef.defName : "null"; + Log.Warning(string.Format("Could not load equipment/resource from the preset. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); + Failed = true; + continue; + } + else { + equipment.Add(new SelectedEquipment(thingDef, stuffDef, gender, e.count)); + } + } + else { + Log.Warning("Could not load stuff definition \"" + e.stuffDef + "\" for item \"" + e.def + "\""); + Failed = true; + } + } + } + else { + Log.Warning("Could not load thing definition \"" + e.def + "\""); + Failed = true; + } + } + loadout.Equipment.Clear(); + foreach (var e in equipment) { + loadout.Equipment.Add(e); + } + + // After loading items using the Scribe methods, the saveables that were loaded get + // put into this saveablesToPostLoad map. This post-load initialization is only + // applicable for when we load a save game. We need to clear our saveables out of + // there so that they don't cause errors later. + HashSet saveables = (HashSet) (typeof(PostLoadInitter).GetField("saveablesToPostLoad", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null)); + saveables.Clear(); + + PrepareCarefully.Instance.Config.pointsEnabled = usePoints; + } + catch (Exception e) { + Log.Error("Failed to load preset file"); + throw e; + } + finally { + Scribe.mode = LoadSaveMode.Inactive; + } + + List pawnModels = new List(); + try { + foreach (SaveRecordPawnV3 p in pawns) { + pawnModels.Add(LoadPawn(p)); + } + } + catch (Exception e) { + Messages.Message(ModString, MessageSound.Silent); + Messages.Message("EdB.PresetPawnLoadFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning(e.ToString()); + Log.Warning("Preset was created with the following mods: " + ModString); + return false; + } + + + List relationships = new List(); + try { + foreach (SaveRecordRelationshipV3 r in savedRelationships) { + CustomRelationship relationship = LoadRelationship(r, pawnModels); + if (relationship == null) { + Messages.Message(ModString, MessageSound.Silent); + Messages.Message("EdB.PresetRelationshipLoadFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning("Failed to load relationship: " + r.relation); + Log.Warning("Preset was created with the following mods: " + ModString); + } + else { + relationships.Add(relationship); + } + } + } + catch (Exception e) { + Messages.Message(ModString, MessageSound.Silent); + Messages.Message("EdB.PresetRelationshipLoadFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning(e.ToString()); + Log.Warning("Preset was created with the following mods: " + ModString); + return false; + } + + loadout.ClearPawns(); + foreach (CustomPawn p in pawnModels) { + loadout.AddPawn(p); + } + + loadout.RelationshipManager.Clear(); + foreach (CustomRelationship r in relationships) { + loadout.RelationshipManager.AddRelationship(r.def, r.source, r.target); + } + + if (Failed) { + Messages.Message(ModString, MessageSound.Silent); + Messages.Message("EdB.PresetThingDefFailed".Translate(), MessageSound.SeriousAlert); + Log.Warning("Preset was created with the following mods: " + ModString); + return false; + } + + return true; + } + + public CustomPawn LoadPawn(SaveRecordPawnV3 record) + { + // TODO: Ahlpa 14 Evaluate + Pawn source = new Randomizer().GenerateColonist(); + //Pawn source = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfPlayer); + + CustomPawn pawn = new CustomPawn(source); + pawn.Gender = record.gender; + if (record.age > 0) { + pawn.ChronologicalAge = record.age; + pawn.BiologicalAge = record.age; + } + if (record.chronologicalAge > 0) { + pawn.ChronologicalAge = record.chronologicalAge; + } + if (record.biologicalAge > 0) { + pawn.BiologicalAge = record.biologicalAge; + } + + pawn.FirstName = record.firstName; + pawn.NickName = record.nickName; + pawn.LastName = record.lastName; + + HairDef h = FindHairDef(record.hairDef); + if (h != null) { + pawn.HairDef = h; + } + else { + Log.Warning("Could not load hair definition \"" + record.hairDef + "\""); + Failed = true; + } + + pawn.HeadGraphicPath = record.headGraphicPath; + pawn.SetColor(PawnLayers.Hair, record.hairColor); + pawn.SetColor(PawnLayers.HeadType, record.skinColor); + Backstory backstory = FindBackstory(record.childhood); + if (backstory != null) { + pawn.Childhood = backstory; + } + else { + Log.Warning("Could not load childhood backstory definition \"" + record.childhood + "\""); + Failed = true; + } + backstory = FindBackstory(record.adulthood); + if (backstory != null) { + pawn.Adulthood = backstory; + } + else { + Log.Warning("Could not load adulthood backstory definition \"" + record.adulthood + "\""); + Failed = true; + } + + int traitCount = pawn.Traits.Count(); + for (int i = 0; i < traitCount; i++) { + pawn.ClearTrait(i); + } + for (int i = 0; i < record.traitNames.Count; i++) { + string traitName = record.traitNames[i]; + if (i >= traitCount) { + break; + } + Trait trait = FindTrait(traitName, record.traitDegrees[i]); + if (trait != null) { + pawn.SetTrait(i, trait); + } + else { + Log.Warning("Could not load trait definition \"" + traitName + "\""); + Failed = true; + } + } + + for (int i = 0; i < record.skillNames.Count; i++) { + string name = record.skillNames[i]; + SkillDef def = FindSkillDef(pawn.Pawn, name); + if (def == null) { + Log.Warning("Could not load skill definition \"" + name + "\""); + Failed = true; + continue; + } + pawn.passions[def] = record.passions[i]; + pawn.SetSkillAdjustment(def, record.skillValues[i]); + pawn.ResetOriginalSkillsAndPassions(); + } + if (record.originalPassions != null && record.originalPassions.Count == record.skillNames.Count) { + for (int i = 0; i < record.skillNames.Count; i++) { + string name = record.skillNames[i]; + SkillDef def = FindSkillDef(pawn.Pawn, name); + if (def == null) { + Log.Warning("Could not load skill definition \"" + name + "\""); + Failed = true; + continue; + } + //pawn.originalPassions[def] = record.originalPassions[i]; + } + } + + for (int i = 0; i < PawnLayers.Count; i++) { + if (PawnLayers.IsApparelLayer(i)) { + pawn.SetSelectedApparel(i, null); + pawn.SetSelectedStuff(i, null); + } + } + for (int i = 0; i < record.apparelLayers.Count; i++) { + int layer = record.apparelLayers[i]; + ThingDef def = DefDatabase.GetNamedSilentFail(record.apparel[i]); + if (def == null) { + Log.Warning("Could not load thing definition for apparel \"" + record.apparel[i] + "\""); + Failed = true; + continue; + } + ThingDef stuffDef = null; + if (!string.IsNullOrEmpty(record.apparelStuff[i])) { + stuffDef = DefDatabase.GetNamedSilentFail(record.apparelStuff[i]); + if (stuffDef == null) { + Log.Warning("Could not load stuff definition \"" + record.apparelStuff[i] + "\" for apparel \"" + record.apparel[i] + "\""); + Failed = true; + continue; + } + } + pawn.SetSelectedApparel(layer, def); + pawn.SetSelectedStuff(layer, stuffDef); + pawn.SetColor(layer, record.apparelColors[i]); + } + + for (int i = 0; i < record.implants.Count; i++) { + SaveRecordImplantV3 implantRecord = record.implants[i]; + BodyPartRecord bodyPart = PrepareCarefully.Instance.HealthManager.ImplantManager.FindReplaceableBodyPartByName(implantRecord.bodyPart); + if (bodyPart == null) { + Log.Warning("Could not find replaceable body part definition \"" + implantRecord.bodyPart + "\""); + Failed = true; + continue; + } + if (implantRecord.recipe != null) { + RecipeDef recipeDef = FindRecipeDef(implantRecord.recipe); + if (recipeDef == null) { + Log.Warning("Could not find recipe definition \"" + implantRecord.recipe + "\""); + Failed = true; + continue; + } + bool found = false; + foreach (var p in recipeDef.appliedOnFixedBodyParts) { + if (p.defName.Equals(bodyPart.def.defName)) { + found = true; + break; + } + } + if (!found) { + Log.Warning("Body part \"" + bodyPart.def.defName + "\" does not match recipe used to replace it"); + Failed = true; + continue; + } + Implant implant = new Implant(); + implant.BodyPartRecord = bodyPart; + implant.recipe = recipeDef; + implant.label = implant.Label; + pawn.AddImplant(implant); + } + } + + foreach (var injuryRecord in record.injuries) { + HediffDef def = DefDatabase.GetNamedSilentFail(injuryRecord.hediffDef); + if (def == null) { + Log.Warning("Could not find hediff definition \"" + injuryRecord.hediffDef + "\""); + Failed = true; + continue; + } + InjuryOption option = PrepareCarefully.Instance.HealthManager.InjuryManager.FindOptionByHediffDef(def); + if (option == null) { + Log.Warning("Could not find injury option for \"" + injuryRecord.hediffDef + "\""); + Failed = true; + continue; + } + BodyPartRecord bodyPart = null; + if (injuryRecord.bodyPart != null) { + bodyPart = PrepareCarefully.Instance.HealthManager.FirstBodyPartRecord(injuryRecord.bodyPart); + if (bodyPart == null) { + Log.Warning("Could not find body part \"" + injuryRecord.bodyPart + "\""); + Failed = true; + continue; + } + } + Injury injury = new Injury(); + injury.Option = option; + injury.BodyPartRecord = bodyPart; + if (injuryRecord.severity != null) { + injury.Severity = injuryRecord.Severity; + } + pawn.AddInjury(injury); + } + + pawn.RandomInjuries = record.randomInjuries; + pawn.RandomRelations = record.randomRelations; + pawn.ClearCachedAbilities(); + pawn.ClearCachedLifeStage(); + + //Log.Message("Loaded pawn: " + pawn.Name); + //Log.Message(" Market Value: \n " + StatDefOf.MarketValue.Worker.GetExplanation(StatRequest.For(pawn.Pawn), ToStringNumberSense.Absolute)); + + return pawn; + } + + public CustomRelationship LoadRelationship(SaveRecordRelationshipV3 saved, List pawns) + { + CustomRelationship result = new CustomRelationship(); + + foreach (var p in pawns) { + if (p.Name.ToStringFull == saved.source) { + result.source = p; + } + if (p.Name.ToStringFull == saved.target) { + result.target = p; + } + } + + result.def = DefDatabase.GetNamedSilentFail(saved.relation); + if (result.def != null) { + result.inverseDef = PrepareCarefully.Instance.RelationshipManager.FindInverseRelationship(result.def); + } + if (result.def == null) { + Log.Warning("Couldn't find relationship definition: " + saved.relation); + return null; + } + else if (result.source == null) { + Log.Warning("Couldn't find relationship source pawn: " + saved.source); + return null; + } + else if (result.target == null) { + Log.Warning("Couldn't find relationship target pawn: " + saved.source); + return null; + } + else if (result.inverseDef == null) { + Log.Warning("Couldn't determine inverse relationship: " + saved.relation); + return null; + } + return result; + } + + public RecipeDef FindRecipeDef(string name) + { + return DefDatabase.GetNamedSilentFail(name); + } + + public HairDef FindHairDef(string name) + { + return DefDatabase.GetNamedSilentFail(name); + } + + public Backstory FindBackstory(string name) + { + return BackstoryDatabase.allBackstories.Values.ToList().Find((Backstory b) => { + return b.uniqueSaveKey.Equals(name); + }); + } + + public Trait FindTrait(string name, int degree) + { + foreach (TraitDef def in DefDatabase.AllDefs) { + if (!def.defName.Equals(name)) { + continue; + } + List degreeData = def.degreeDatas; + int count = degreeData.Count; + if (count > 0) { + for (int i = 0; i < count; i++) { + if (degree == degreeData[i].degree) { + Trait trait = new Trait(def, degreeData[i].degree); + return trait; + } + } + } + else { + return new Trait(def, 0); + } + } + return null; + } + + public SkillDef FindSkillDef(Pawn pawn, string name) + { + foreach (var skill in pawn.skills.skills) { + if (skill.def.defName.Equals(name)) { + return skill.def; + } + } + return null; + } + } +} + diff --git a/Source/Version3/SaveRecordImplantV3.cs b/Source/Version3/SaveRecordImplantV3.cs new file mode 100644 index 0000000..6aca323 --- /dev/null +++ b/Source/Version3/SaveRecordImplantV3.cs @@ -0,0 +1,31 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class SaveRecordImplantV3 : IExposable + { + public string bodyPart = null; + public string recipe = null; + + public SaveRecordImplantV3() { + } + + public SaveRecordImplantV3(Implant option) + { + this.bodyPart = option.BodyPartRecord.def.defName; + this.recipe = option.recipe != null ? option.recipe.defName : null; + } + + public void ExposeData() + { + Scribe_Values.LookValue(ref this.bodyPart, "bodyPart", null, false); + Scribe_Values.LookValue(ref recipe, "recipe", null, false); + } + } +} + diff --git a/Source/Version3/SaveRecordInjuryV3.cs b/Source/Version3/SaveRecordInjuryV3.cs new file mode 100644 index 0000000..576d968 --- /dev/null +++ b/Source/Version3/SaveRecordInjuryV3.cs @@ -0,0 +1,42 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class SaveRecordInjuryV3 : IExposable + { + public string bodyPart = null; + public string hediffDef = null; + public string severity = null; + + public SaveRecordInjuryV3() { + } + + public SaveRecordInjuryV3(Injury injury) + { + this.bodyPart = injury.BodyPartRecord != null ? injury.BodyPartRecord.def.defName : null; + this.hediffDef = injury.Option.HediffDef != null ? injury.Option.HediffDef.defName : null; + if (injury.Severity != 0) { + this.severity = injury.Severity.ToString(); + } + } + + public void ExposeData() + { + Scribe_Values.LookValue(ref this.hediffDef, "hediffDef", null, false); + Scribe_Values.LookValue(ref this.bodyPart, "bodyPart", null, false); + Scribe_Values.LookValue(ref this.severity, "severity", null, false); + } + + public float Severity { + get { + return float.Parse(severity); + } + } + } +} + diff --git a/Source/Version3/SaveRecordPawnV3.cs b/Source/Version3/SaveRecordPawnV3.cs new file mode 100644 index 0000000..f2e1bbf --- /dev/null +++ b/Source/Version3/SaveRecordPawnV3.cs @@ -0,0 +1,278 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class SaveRecordPawnV3 : IExposable + { + public Gender gender; + public string adulthood; + public string childhood; + public List traitNames = new List(); + public List traitDegrees = new List(); + public Color skinColor; + public string hairDef; + public Color hairColor; + public string headGraphicPath; + public string firstName; + public string lastName; + public string nickName; + public int age; + public int biologicalAge; + public int chronologicalAge; + public List skillNames = new List(); + public List skillValues = new List(); + public List passions = new List(); + public List originalPassions = new List(); + public List apparel = new List(); + public List apparelLayers = new List(); + public List apparelStuff = new List(); + public List apparelColors = new List(); + public bool randomInjuries = true; + public bool randomRelations = false; + public List implants = new List(); + public List injuries = new List(); + + public SaveRecordPawnV3() + { + + } + + public SaveRecordPawnV3(CustomPawn pawn) + { + this.gender = pawn.Gender; + this.adulthood = pawn.Adulthood.uniqueSaveKey; + this.childhood = pawn.Childhood.uniqueSaveKey; + this.skinColor = pawn.SkinColor; + this.hairDef = pawn.HairDef.defName; + this.hairColor = pawn.GetColor(PawnLayers.Hair); + this.headGraphicPath = pawn.HeadGraphicPath; + 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.GetSkillAdjustments(skill.def)); + this.passions.Add(pawn.passions[skill.def]); + this.originalPassions.Add(pawn.originalPassions[skill.def]); + } + for (int layer = 0; layer < PawnLayers.Count; layer++) { + ThingDef thingDef = pawn.GetAcceptedApparel(layer); + ThingDef stuffDef = pawn.GetSelectedStuff(layer); + Color color = pawn.GetColor(layer); + if (thingDef != null) { + this.apparelLayers.Add(layer); + this.apparel.Add(thingDef.defName); + this.apparelStuff.Add(stuffDef != null ? stuffDef.defName : ""); + this.apparelColors.Add(color); + } + } + this.randomInjuries = pawn.RandomInjuries; + foreach (Implant implant in pawn.Implants) { + this.implants.Add(new SaveRecordImplantV3(implant)); + } + foreach (Injury injury in pawn.Injuries) { + this.injuries.Add(new SaveRecordInjuryV3(injury)); + } + } + + public void ExposeData() + { + Scribe_Values.LookValue(ref this.gender, "gender", Gender.Male, false); + Scribe_Values.LookValue(ref this.childhood, "childhood", null, false); + Scribe_Values.LookValue(ref this.adulthood, "adulthood", null, false); + Scribe_Collections.LookList(ref this.traitNames, "traitNames", LookMode.Value, null); + Scribe_Collections.LookList(ref this.traitDegrees, "traitDegrees", LookMode.Value, null); + Scribe_Values.LookValue(ref this.skinColor, "skinColor", Color.white, false); + Scribe_Values.LookValue(ref this.hairDef, "hairDef", null, false); + Scribe_Values.LookValue(ref this.hairColor, "hairColor", Color.white, false); + Scribe_Values.LookValue(ref this.headGraphicPath, "headGraphicPath", null, false); + Scribe_Values.LookValue(ref this.firstName, "firstName", null, false); + Scribe_Values.LookValue(ref this.nickName, "nickName", null, false); + Scribe_Values.LookValue(ref this.lastName, "lastName", null, false); + if (Scribe.mode == LoadSaveMode.LoadingVars) { + Scribe_Values.LookValue(ref this.age, "age", 0, false); + } + Scribe_Values.LookValue(ref this.biologicalAge, "biologicalAge", 0, false); + Scribe_Values.LookValue(ref this.chronologicalAge, "chronologicalAge", 0, false); + Scribe_Collections.LookList(ref this.skillNames, "skillNames", LookMode.Value, null); + Scribe_Collections.LookList(ref this.skillValues, "skillValues", LookMode.Value, null); + Scribe_Collections.LookList(ref this.passions, "passions", LookMode.Value, null); + Scribe_Collections.LookList(ref this.apparel, "apparel", LookMode.Value, null); + Scribe_Collections.LookList(ref this.apparelLayers, "apparelLayers", LookMode.Value, null); + Scribe_Collections.LookList(ref this.apparelStuff, "apparelStuff", LookMode.Value, null); + Scribe_Collections.LookList(ref this.apparelColors, "apparelColors", LookMode.Value, null); + Scribe_Values.LookValue(ref this.randomInjuries, "randomInjuries", false, true); + + if (Scribe.mode == LoadSaveMode.Saving) { + Scribe_Collections.LookList(ref this.implants, "implants", LookMode.Deep, null); + } + else { + if (Scribe.curParent["implants"] != null) { + Scribe_Collections.LookList(ref this.implants, "implants", LookMode.Deep, null); + } + } + + if (Scribe.mode == LoadSaveMode.Saving) { + Scribe_Collections.LookList(ref this.injuries, "injuries", LookMode.Deep, null); + } + else { + if (Scribe.curParent["implants"] != null) { + Scribe_Collections.LookList(ref this.injuries, "injuries", LookMode.Deep, null); + } + } + } + + public CustomPawn CreatePawn() + { + // TODO: Evaluate + //Pawn source = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); + Pawn source = new Randomizer().GenerateColonist(); + + source.health = new Pawn_HealthTracker(source); + + CustomPawn pawn = new CustomPawn(source); + pawn.Gender = this.gender; + if (age > 0) { + pawn.ChronologicalAge = age; + pawn.BiologicalAge = age; + } + if (chronologicalAge > 0) { + pawn.ChronologicalAge = chronologicalAge; + } + if (biologicalAge > 0) { + pawn.BiologicalAge = biologicalAge; + } + pawn.FirstName = this.firstName; + pawn.NickName = this.nickName; + pawn.LastName = this.lastName; + + HairDef h = FindHairDef(this.hairDef); + if (h != null) { + pawn.HairDef = h; + } + + pawn.HeadGraphicPath = this.headGraphicPath; + pawn.SetColor(PawnLayers.Hair, hairColor); + pawn.SetColor(PawnLayers.HeadType, skinColor); + Backstory backstory = FindBackstory(childhood); + if (backstory != null) { + pawn.Childhood = backstory; + } + backstory = FindBackstory(adulthood); + if (backstory != null) { + pawn.Adulthood = backstory; + } + + int traitCount = pawn.Traits.Count(); + for (int i = 0; i < traitCount; i++) { + pawn.ClearTrait(i); + } + for (int i = 0; i < traitNames.Count; i++) { + string traitName = traitNames[i]; + if (i >= traitCount) { + break; + } + Trait trait = FindTrait(traitName, traitDegrees[i]); + if (trait != null) { + pawn.SetTrait(i, trait); + } + } + + for (int i = 0; i < this.skillNames.Count; i++) { + string name = this.skillNames[i]; + SkillDef def = FindSkillDef(pawn.Pawn, name); + if (def == null) { + continue; + } + pawn.passions[def] = this.passions[i]; + pawn.SetSkillAdjustment(def, this.skillValues[i]); + } + + for (int i = 0; i < PawnLayers.Count; i++) { + if (PawnLayers.IsApparelLayer(i)) { + pawn.SetSelectedApparel(i, null); + pawn.SetSelectedStuff(i, null); + } + } + for (int i = 0; i < this.apparelLayers.Count; i++) { + int layer = this.apparelLayers[i]; + ThingDef def = DefDatabase.GetNamedSilentFail(this.apparel[i]); + if (def == null) { + continue; + } + ThingDef stuffDef = null; + if (!string.IsNullOrEmpty(this.apparelStuff[i])) { + stuffDef = DefDatabase.GetNamedSilentFail(this.apparelStuff[i]); + if (stuffDef == null) { + continue; + } + } + pawn.SetSelectedApparel(layer, def); + pawn.SetSelectedStuff(layer, stuffDef); + pawn.SetColor(layer, this.apparelColors[i]); + } + + return pawn; + } + + public HairDef FindHairDef(string name) + { + return DefDatabase.GetNamedSilentFail(name); + } + + public Backstory FindBackstory(string name) + { + return BackstoryDatabase.allBackstories.Values.ToList().Find((Backstory b) => { + return b.uniqueSaveKey.Equals(name); + }); + } + + public Trait FindTrait(string name, int degree) + { + foreach (TraitDef def in DefDatabase.AllDefs) { + if (!def.defName.Equals(name)) { + continue; + } + List degreeData = def.degreeDatas; + int count = degreeData.Count; + if (count > 0) { + for (int i = 0; i < count; i++) { + if (degree == degreeData[i].degree) { + Trait trait = new Trait(def, degreeData[i].degree); + return trait; + } + } + } + else { + return new Trait(def, 0); + } + } + return null; + } + + public SkillDef FindSkillDef(Pawn pawn, string name) + { + foreach (var skill in pawn.skills.skills) { + if (skill.def.defName.Equals(name)) { + return skill.def; + } + } + return null; + } + } +} + diff --git a/Source/Version3/SaveRecordRelationshipV3.cs b/Source/Version3/SaveRecordRelationshipV3.cs new file mode 100644 index 0000000..797c77d --- /dev/null +++ b/Source/Version3/SaveRecordRelationshipV3.cs @@ -0,0 +1,36 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace EdB.PrepareCarefully +{ + public class SaveRecordRelationshipV3 : IExposable + { + public string source; + public string target; + public string relation; + + public SaveRecordRelationshipV3() + { + + } + + public SaveRecordRelationshipV3(CustomRelationship relationship) + { + this.source = relationship.source.Name.ToStringFull; + this.target = relationship.target.Name.ToStringFull; + this.relation = relationship.def.defName; + } + + public void ExposeData() + { + Scribe_Values.LookValue(ref this.source, "source", null, true); + Scribe_Values.LookValue(ref this.target, "target", null, true); + Scribe_Values.LookValue(ref this.relation, "relation", null, true); + } + } +} + From c8f461b22b8791bd5a0462af9bbf1646f8e284db Mon Sep 17 00:00:00 2001 From: eedibee Date: Sat, 16 Jul 2016 16:42:48 -0700 Subject: [PATCH 02/29] Added git ignore file --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8cb77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vs +*.userprefs +bin +obj From 52847f074a1956897b706afe8014868d91909ff7 Mon Sep 17 00:00:00 2001 From: eedibee Date: Sat, 16 Jul 2016 16:57:10 -0700 Subject: [PATCH 03/29] Updated version number to 0.14.0.1 to better reflect beta status --- EdBPrepareCarefully.csproj | 2 +- EdBPrepareCarefully.sln | 6 +++--- Properties/AssemblyInfo.cs | 2 +- Resources/About/About.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index 243891b..a4208b1 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -7,7 +7,7 @@ Library EdB.PrepareCarefully EdBPrepareCarefully - 0.14.1.1 + 0.14.0.1 False v3.5 ScenPart_PlayerPawnsArriveMethodCarefully.manifest diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln index 185eeec..ccd01a3 100644 --- a/EdBPrepareCarefully.sln +++ b/EdBPrepareCarefully.sln @@ -23,9 +23,9 @@ Global $2.FileWidth = 120 $2.TabsToSpaces = False $2.EolMarker = Unix - $2.inheritsSet = null + $2.inheritsSet = VisualStudio $2.inheritsScope = text/plain - $2.scope = application/xml + $2.scope = text/plain $0.CSharpFormattingPolicy = $3 $3.IndentSwitchBody = True $3.ElseNewLinePlacement = NewLine @@ -195,6 +195,6 @@ Global $29.IncludeStaticEntities = True $0.VersionControlPolicy = $31 $31.inheritsSet = Mono - version = 0.14.1.1 + version = 0.14.0.1 EndGlobalSection EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index e3fffe5..facae9c 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("0.14.1.1")] +[assembly: AssemblyVersion("0.14.0.1")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Resources/About/About.xml b/Resources/About/About.xml index a64f6b4..4b326e1 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -8,6 +8,6 @@ If you get a starting setup that you like, save it as a preset so that you can start your game the same way later. -[Version 0.14.1.1] +[Version 0.14.0.1] \ No newline at end of file From ebc901e5741a73aae469f757c35f1061a7b79f8e Mon Sep 17 00:00:00 2001 From: eedibee Date: Sun, 17 Jul 2016 12:24:49 -0700 Subject: [PATCH 04/29] Fix for cached disabled work types --- Source/CustomPawn.cs | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index c76a65e..b2120e2 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -798,23 +798,6 @@ protected void ComputePawnSkillLevels() { ComputeBaseSkillLevels(); foreach (var record in pawn.skills.skills) { SkillDef def = record.def; - /* - int level = baseSkillLevels[def]; - if (level < 0) { - level = 0; - } - level += skillAdjustments[def]; - if (level > 20) { - skillAdjustments[def] -= (level - 20); - level = 20; - } - if (level < 0) { - level = 0; - } - if (IsDisabled(def)) { - level = 0; - } - */ pawn.skills.GetSkill(def).level = GetSkillLevel(def); pawn.skills.GetSkill(def).passion = passions[def]; } @@ -959,6 +942,7 @@ public string ResetIncapableOf() else { incapable = null; } + CustomPawn.ClearCachedDisabledWorkTypes(this.pawn.story); return incapable; } @@ -1029,6 +1013,8 @@ protected Pawn CopyPawn(Pawn pawn) // Need to use reflection to set the private graphic path field. typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, pawn.story.HeadGraphicPath); result.story.crownType = pawn.story.crownType; + // Clear cached values from the story tracker. + CustomPawn.ClearCachedDisabledWorkTypes(pawn.story); // Copy apparel. List pawnApparelList = (List)typeof(Pawn_ApparelTracker).GetField("wornApparel", @@ -1056,6 +1042,12 @@ protected Pawn CopyPawn(Pawn pawn) return result; } + // TODO: There's probably a better place for this utility method. + public static void ClearCachedDisabledWorkTypes(Pawn_StoryTracker story) + { + typeof(Pawn_StoryTracker).GetField("cachedDisabledWorkTypes", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(story, null); + } + public Pawn ConvertToPawn(bool resolveGraphics) { // TODO: Evaluate //Pawn pawn = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); @@ -1077,6 +1069,11 @@ public Pawn ConvertToPawn(bool resolveGraphics) { pawn.story.hairColor = colors[PawnLayers.Hair]; // Need to use reflection to set the private graphic path method. typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, HeadGraphicPath); + // Clear cached values from the story tracker. + // TODO: It might make more sense to create a new instance of Pawn_StoryTracker, but need + // to make sure all of the details are filled in with that approach. + CustomPawn.ClearCachedDisabledWorkTypes(pawn.story); + pawn.Name = this.pawn.Name; pawn.ageTracker.BirthAbsTicks = this.pawn.ageTracker.BirthAbsTicks; From f5b29e21b485cfcb28ffd2762a9acb18a066861c Mon Sep 17 00:00:00 2001 From: eedibee Date: Sun, 17 Jul 2016 12:29:18 -0700 Subject: [PATCH 05/29] Fixed text alignment when rendering point display --- Source/Page_PrepareCarefully.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Page_PrepareCarefully.cs b/Source/Page_PrepareCarefully.cs index 64b95de..d08a9fb 100644 --- a/Source/Page_PrepareCarefully.cs +++ b/Source/Page_PrepareCarefully.cs @@ -80,11 +80,12 @@ protected void DrawCost(Rect parentRect) TipSignal tip = new TipSignal(() => tooltipText, tooltipText.GetHashCode()); TooltipHandler.TipRegion(rect, tip); - // Removed points. - /* GUI.color = ColorText; Text.Anchor = TextAnchor.UpperLeft; Text.Font = GameFont.Small; + + // Removed points. + /* string optionLabel; float optionTop = parentRect.height - 97; optionLabel = "EdB.PrepareCarefully.UsePoints".Translate(); From 82993acf821ac9b76e9214ee7b9f8206953667af Mon Sep 17 00:00:00 2001 From: eedibee Date: Sun, 17 Jul 2016 15:44:08 -0700 Subject: [PATCH 06/29] Updated version number of beta 2 release --- EdBPrepareCarefully.csproj | 2 +- EdBPrepareCarefully.sln | 6 +++--- Properties/AssemblyInfo.cs | 2 +- Resources/About/About.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index a4208b1..33d3645 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -7,7 +7,7 @@ Library EdB.PrepareCarefully EdBPrepareCarefully - 0.14.0.1 + 0.14.0.2 False v3.5 ScenPart_PlayerPawnsArriveMethodCarefully.manifest diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln index ccd01a3..c104c29 100644 --- a/EdBPrepareCarefully.sln +++ b/EdBPrepareCarefully.sln @@ -23,9 +23,9 @@ Global $2.FileWidth = 120 $2.TabsToSpaces = False $2.EolMarker = Unix - $2.inheritsSet = VisualStudio + $2.inheritsSet = null $2.inheritsScope = text/plain - $2.scope = text/plain + $2.scope = application/xml $0.CSharpFormattingPolicy = $3 $3.IndentSwitchBody = True $3.ElseNewLinePlacement = NewLine @@ -195,6 +195,6 @@ Global $29.IncludeStaticEntities = True $0.VersionControlPolicy = $31 $31.inheritsSet = Mono - version = 0.14.0.1 + version = 0.14.0.2 EndGlobalSection EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index facae9c..0725b29 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("0.14.0.1")] +[assembly: AssemblyVersion("0.14.0.2")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Resources/About/About.xml b/Resources/About/About.xml index 4b326e1..cfac525 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -8,6 +8,6 @@ If you get a starting setup that you like, save it as a preset so that you can start your game the same way later. -[Version 0.14.0.1] +[Version 0.14.0.2] \ No newline at end of file From d105e24165cbae25441808ddf87bce4476ab5978 Mon Sep 17 00:00:00 2001 From: edbmods Date: Tue, 19 Jul 2016 21:01:28 -0700 Subject: [PATCH 07/29] Updated target version to 1238 and 0.14.0.3 --- EdBPrepareCarefully.csproj | 2 +- EdBPrepareCarefully.sln | 6 +++--- Properties/AssemblyInfo.cs | 2 +- Resources/About/About.xml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index 33d3645..d2a2e13 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -7,7 +7,7 @@ Library EdB.PrepareCarefully EdBPrepareCarefully - 0.14.0.2 + 0.14.0.3 False v3.5 ScenPart_PlayerPawnsArriveMethodCarefully.manifest diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln index c104c29..5be4aea 100644 --- a/EdBPrepareCarefully.sln +++ b/EdBPrepareCarefully.sln @@ -23,9 +23,9 @@ Global $2.FileWidth = 120 $2.TabsToSpaces = False $2.EolMarker = Unix - $2.inheritsSet = null + $2.inheritsSet = VisualStudio $2.inheritsScope = text/plain - $2.scope = application/xml + $2.scope = text/plain $0.CSharpFormattingPolicy = $3 $3.IndentSwitchBody = True $3.ElseNewLinePlacement = NewLine @@ -195,6 +195,6 @@ Global $29.IncludeStaticEntities = True $0.VersionControlPolicy = $31 $31.inheritsSet = Mono - version = 0.14.0.2 + version = 0.14.0.3 EndGlobalSection EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 0725b29..fc6f01a 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("0.14.0.2")] +[assembly: AssemblyVersion("0.14.0.3")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Resources/About/About.xml b/Resources/About/About.xml index cfac525..f6af3f9 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -3,11 +3,11 @@ EdB Prepare Carefully EdB - 0.14.1234 + 0.14.1238 Customize your colonists, choose your gear and prepare carefully for your crash landing! If you get a starting setup that you like, save it as a preset so that you can start your game the same way later. -[Version 0.14.0.2] +[Version 0.14.0.3] \ No newline at end of file From 648060ad5cc4c1c347c8775805e14132c25099b3 Mon Sep 17 00:00:00 2001 From: edbmods Date: Tue, 19 Jul 2016 21:33:59 -0700 Subject: [PATCH 08/29] README tweaks --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 740e917..deedae9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # EdB Prepare Carefully +## Building Prepare Carefully + +Open the solution file in either Xamarin Studio/MonoDevelop or in Visual Studio. + Note that the solution has dependencies on the following DLLs: - Assembly-CSharp.dll - UnityEngine.dll +You'll need to add those dependencies to your project. Since they might reference file paths on your local development environment, I did not include them in the project files. + The result of the build will be the following DLL: - EdBPrepareCarefully.dll -This DLL should be packaged with the contents of the Resources directory, inside the Assemblies directory. +This DLL should be packaged with the contents of the `Resources` directory, inside a `Resources/Assemblies` directory. The build does not automate the creation of the mod distribution directory. From c2367bf77dd0ef7b16a1a275a1fabdfad97ee7c9 Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 20 Jul 2016 16:15:48 -0700 Subject: [PATCH 09/29] Added call to reset textures when mods are reloaded --- Source/ModController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ModController.cs b/Source/ModController.cs index 2b4ed44..f5b8efc 100644 --- a/Source/ModController.cs +++ b/Source/ModController.cs @@ -129,7 +129,7 @@ public Window TopWindow public void ResetTextures() { - + Textures.Reset(); } public bool ModEnabled From 3d255160dbde80569c1db25cad70917f3ca8ff1f Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 20 Jul 2016 18:19:54 -0700 Subject: [PATCH 10/29] Updated version to 0.14.0.4 --- EdBPrepareCarefully.csproj | 2 +- EdBPrepareCarefully.sln | 6 +++--- Properties/AssemblyInfo.cs | 2 +- Resources/About/About.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index d2a2e13..f2aa673 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -7,7 +7,7 @@ Library EdB.PrepareCarefully EdBPrepareCarefully - 0.14.0.3 + 0.14.0.4 False v3.5 ScenPart_PlayerPawnsArriveMethodCarefully.manifest diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln index 5be4aea..08fc718 100644 --- a/EdBPrepareCarefully.sln +++ b/EdBPrepareCarefully.sln @@ -23,9 +23,9 @@ Global $2.FileWidth = 120 $2.TabsToSpaces = False $2.EolMarker = Unix - $2.inheritsSet = VisualStudio + $2.inheritsSet = null $2.inheritsScope = text/plain - $2.scope = text/plain + $2.scope = application/xml $0.CSharpFormattingPolicy = $3 $3.IndentSwitchBody = True $3.ElseNewLinePlacement = NewLine @@ -195,6 +195,6 @@ Global $29.IncludeStaticEntities = True $0.VersionControlPolicy = $31 $31.inheritsSet = Mono - version = 0.14.0.3 + version = 0.14.0.4 EndGlobalSection EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index fc6f01a..72fbaed 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("0.14.0.3")] +[assembly: AssemblyVersion("0.14.0.4")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Resources/About/About.xml b/Resources/About/About.xml index f6af3f9..dbe7859 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -8,6 +8,6 @@ If you get a starting setup that you like, save it as a preset so that you can start your game the same way later. -[Version 0.14.0.3] +[Version 0.14.0.4] \ No newline at end of file From d0b55a0a17ea2cd2058a963e42395dd49fa3ebe9 Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 27 Jul 2016 19:18:02 -0700 Subject: [PATCH 11/29] Fixed backstory pre-selection when clicking on the adult backstory name --- Source/Page_ConfigureStartingPawnsCarefully.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Page_ConfigureStartingPawnsCarefully.cs b/Source/Page_ConfigureStartingPawnsCarefully.cs index 8bdbfcb..0953858 100644 --- a/Source/Page_ConfigureStartingPawnsCarefully.cs +++ b/Source/Page_ConfigureStartingPawnsCarefully.cs @@ -1794,7 +1794,7 @@ protected void ShowApparelStuffDialog(int layer) protected void ShowBackstoryDialog(CustomPawn customPawn, BackstorySlot slot) { - Backstory originalBackstory = customPawn.Childhood; + Backstory originalBackstory = (slot == BackstorySlot.Childhood) ? customPawn.Childhood : customPawn.Adulthood; Backstory selectedBackstory = originalBackstory; Dialog_Options dialog = new Dialog_Options(slot == BackstorySlot.Childhood ? this.sortedChildhoodBackstories : this.sortedAdulthoodBackstories) { NameFunc = (Backstory backstory) => { From 61715bbc7c47988f527def771193278f82d502f0 Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 27 Jul 2016 19:21:36 -0700 Subject: [PATCH 12/29] Added better error handling during cost calculation --- Source/CostCalculator.cs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Source/CostCalculator.cs b/Source/CostCalculator.cs index 68684fe..424367c 100644 --- a/Source/CostCalculator.cs +++ b/Source/CostCalculator.cs @@ -231,13 +231,45 @@ public double CalculateEquipmentCost(SelectedEquipment customPawn) 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 { - Thing thing = ThingMaker.MakeThing(def, stuffDef); - return thing.MarketValue; + // TODO: Alpha 15 + // Should look at ThingMaker.MakeThing() to decide which validations we need to do + // before calling that method to avoid null pointer exceptions. Should re-evaluate + // for each new alpha 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 { From 43158e8086a652d2444aca2e57484be5fdcc6a15 Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 27 Jul 2016 21:16:17 -0700 Subject: [PATCH 13/29] Fixes to support scenario forced hediffs (i.e. cryptosleep sickness, malnutrition, etc). Renamed injury to condition. Tweaked logic to check to see if a part must be selected for a hediff and other tweaks to support hediffs added to the whole body instead to a specific part. --- .../English/Keyed/EdBPrepareCarefully.xml | 11 ++- Source/CustomBodyPart.cs | 2 +- Source/Injury.cs | 19 +++- Source/InjuryManager.cs | 86 ++++++++++++++++--- Source/Panel_Health.cs | 35 +++++--- 5 files changed, 118 insertions(+), 35 deletions(-) diff --git a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml index 1ec0145..c5bec2d 100644 --- a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml +++ b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml @@ -103,7 +103,7 @@ Close This relationship cannot be deleted because it was automatically added due to another relationship. Delete the original relationship to remove this one as well. Random Injuries - You must remove all injuries from the colonist to enable random injury generation. Injuries will be determined based on the colonist's age. + You must remove all conditions from the colonist to enable random injury generation. Injuries will be determined based on the colonist's age. Add Implant... Select an Implant Procedure You must select an implant procedure @@ -111,9 +111,9 @@ You must select a location Select a severity level You must select a severity level - Add Injury... - Select an Injury - You must select an injury + Add Condition... + Select a condition + You must select a condition {0} ({1}) Implants Furniture @@ -129,4 +129,7 @@ Pre-alpha 13 versions of saved colonists are not supported. Implants: {0} + + Whole Body + \ No newline at end of file diff --git a/Source/CustomBodyPart.cs b/Source/CustomBodyPart.cs index 29bafa6..0d0270f 100644 --- a/Source/CustomBodyPart.cs +++ b/Source/CustomBodyPart.cs @@ -15,7 +15,7 @@ public abstract BodyPartRecord BodyPartRecord { public virtual string PartName { get { - return BodyPartRecord != null ? BodyPartRecord.def.LabelCap : "No part!"; + return BodyPartRecord != null ? BodyPartRecord.def.LabelCap : "EdB.PrepareCarefully.WholeBody".Translate(); } } diff --git a/Source/Injury.cs b/Source/Injury.cs index 9a8b642..7747fb7 100644 --- a/Source/Injury.cs +++ b/Source/Injury.cs @@ -90,14 +90,25 @@ public override void AddToPawn(CustomPawn customPawn, Pawn pawn) { this.hediff = hediff; } else if (Option.IsOldInjury) { - Hediff_Injury hediff = (Hediff_Injury) HediffMaker.MakeHediff(Option.HediffDef, pawn, null); + Hediff hediff = HediffMaker.MakeHediff(Option.HediffDef, pawn, null); hediff.Severity = this.Severity; - hediff.TryGetComp().IsOld = true; - // TODO: This should not be hard-coded. - hediff.TryGetComp().painFactor = 4; + + HediffComp_GetsOld getsOld = hediff.TryGetComp(); + if (getsOld != null) { + hediff.TryGetComp().IsOld = true; + // TODO: Pain factor should not be hard-coded. + hediff.TryGetComp().painFactor = 4; + } + pawn.health.AddHediff(hediff, BodyPartRecord, null); this.hediff = hediff; } + else { + Hediff hediff = HediffMaker.MakeHediff(Option.HediffDef, pawn, null); + hediff.Severity = this.Severity; + pawn.health.AddHediff(hediff, null, null); + this.hediff = hediff; + } pawn.health.capacities.Clear(); } diff --git a/Source/InjuryManager.cs b/Source/InjuryManager.cs index 83f265e..af62578 100644 --- a/Source/InjuryManager.cs +++ b/Source/InjuryManager.cs @@ -21,8 +21,9 @@ public IEnumerable Options { public void InitializeOptions() { - // Add long-term chronic. + // Add long-term chronic conditions from the giver that adds new hediffs as pawns age. HediffGiverSetDef giverSetDef = DefDatabase.GetNamedSilentFail("OrganicStandard"); + HashSet addedDefs = new HashSet(); if (giverSetDef != null) { foreach (var g in giverSetDef.hediffGivers) { if (g.GetType() == typeof(HediffGiver_Birthday)) { @@ -36,34 +37,74 @@ public void InitializeOptions() option.ValidParts.AddRange(g.partsToAffect); } options.Add(option); + if (!addedDefs.Contains(g.hediff)) { + addedDefs.Add(g.hediff); + } } } } - // Add old injury options. + // 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); + HashSet scenPartDefSet = new HashSet(scenPartDefs); + + // Add injury options. List oldInjuries = new List(); foreach (var hd in DefDatabase.AllDefs) { - HediffCompProperties props = hd.CompPropsFor(typeof(HediffComp_GetsOld)); - if (props != null) { - if (hd.defName == "MissingBodyPart") { - continue; - } - String label; - if (props.oldLabel != null) { - label = props.oldLabel.CapitalizeFirst(); + // TODO: Missing body part seems to be a special case. The hediff giver doesn't itself remove + // limbs, so disable it until we can add special-case handling. + if (hd.defName == "MissingBodyPart") { + continue; + } + + // Filter out defs that were already added as a chronic condition. + if (addedDefs.Contains(hd)) { + continue; + } + + // Filter out implants. + if (hd.hediffClass == typeof(Hediff_AddedPart)) { + continue; + } + + // If it's old injury, use the old injury properties to get the label. + HediffCompProperties getsOldProperties = hd.CompPropsFor(typeof(HediffComp_GetsOld)); + String label; + if (getsOldProperties != null) { + if (getsOldProperties.oldLabel != null) { + label = getsOldProperties.oldLabel.CapitalizeFirst(); } else { Log.Warning("Could not find label for old injury: " + hd.defName); continue; } - InjuryOption option = new InjuryOption(); - option.HediffDef = hd; + } + // If it's not an old injury, make sure it's one of the available hediffs that can + // be added via ScenPart_ForcedHediff. If it's not, filter it out. + else { + if (!scenPartDefSet.Contains(hd)) { + continue; + } + label = hd.LabelCap; + } + + // Add the injury option. + InjuryOption option = new InjuryOption(); + option.HediffDef = hd; + option.Label = label; + if (getsOldProperties != null) { option.IsOldInjury = true; - option.Label = label; - oldInjuries.Add(option); } + else { + option.ValidParts = new List(); + } + oldInjuries.Add(option); } + + // Disambiguate duplicate injury labels. HashSet labels = new HashSet(); HashSet duplicateLabels = new HashSet(); @@ -123,6 +164,23 @@ public InjuryOption FindOptionByHediffDef(HediffDef def) } return null; } + + public bool DoesStageKillPawn(HediffDef def, HediffStage stage) + { + if (stage.capMods != null) { + foreach (var c in stage.capMods) { + if (c.capacity == PawnCapacityDefOf.Consciousness) { + if (c.setMax == 0f) { + return true; + } + else if (c.offset == -1f) { + return true; + } + } + } + } + return false; + } } } diff --git a/Source/Panel_Health.cs b/Source/Panel_Health.cs index c1af494..b88900a 100644 --- a/Source/Panel_Health.cs +++ b/Source/Panel_Health.cs @@ -149,20 +149,31 @@ public void DrawHeader() customPawn.AddInjury(injury); } else { - foreach (var p in selectedInjury.ValidParts) { - BodyPartRecord record = PrepareCarefully.Instance.HealthManager.FirstBodyPartRecord(p); - if (record != null) { - Injury injury= new Injury(); - injury.BodyPartRecord = record; - injury.Option = selectedInjury; - if (selectedSeverity != null) { - injury.Severity = selectedSeverity.Value; + if (selectedInjury.ValidParts.Count > 0) { + foreach (var p in selectedInjury.ValidParts) { + BodyPartRecord record = PrepareCarefully.Instance.HealthManager.FirstBodyPartRecord(p); + if (record != null) { + Injury injury = new Injury(); + injury.BodyPartRecord = record; + injury.Option = selectedInjury; + if (selectedSeverity != null) { + injury.Severity = selectedSeverity.Value; + } + customPawn.AddInjury(injury); + } + else { + Log.Warning("Could not find body part record for definition: " + p.defName); } - customPawn.AddInjury(injury); } - else { - Log.Warning("Could not find body part record for definition: " + p.defName); + } + else { + Injury injury = new Injury(); + injury.BodyPartRecord = null; + injury.Option = selectedInjury; + if (selectedSeverity != null) { + injury.Severity = selectedSeverity.Value; } + customPawn.AddInjury(injury); } } }; @@ -243,7 +254,7 @@ Dialog_Options injuryOptionDialog }, SelectAction = (InjuryOption option) => { selectedInjury = option; - if (option.ValidParts == null || option.ValidParts.Count == 0) { + if (option.ValidParts == null) { bodyPartSelectionRequired = true; } else { From a1f0620b42a087da3e8f1804ac46f54ff56c9cff Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 27 Jul 2016 22:07:05 -0700 Subject: [PATCH 14/29] Added a warning alert when the prepare carefully genstep is not detected --- .../English/Keyed/EdBPrepareCarefully.xml | 6 +++++ Source/Page_ConfigureStartingPawns.cs | 4 ++++ Source/PrepareCarefully.cs | 22 ++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml index c5bec2d..f46dc14 100644 --- a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml +++ b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml @@ -131,5 +131,11 @@ Whole Body + Warning + There is a problem with your mod configuration. + +Prepare Carefully is a map generation mod. It appears that you have another mod enabled that also modifies the game's map generation and is overriding Prepare Carefully. + +Your colonist customizations will be ignored. \ No newline at end of file diff --git a/Source/Page_ConfigureStartingPawns.cs b/Source/Page_ConfigureStartingPawns.cs index c2af3ff..d719cf1 100644 --- a/Source/Page_ConfigureStartingPawns.cs +++ b/Source/Page_ConfigureStartingPawns.cs @@ -73,6 +73,10 @@ public override void DoWindowContents(Rect rect) Action prepareCarefullyAction = () => { PrepareCarefully.Instance.Initialize(); Find.WindowStack.Add(new Page_ConfigureStartingPawnsCarefully()); + if (!PrepareCarefully.Instance.FindScenPart()) { + Find.WindowStack.Add(new Dialog_Confirm("EdB.PrepareCarefully.ModConfigProblem.Description".Translate(), + delegate {}, true, "EdB.PrepareCarefully.ModConfigProblem.Title".Translate(), false)); + } }; base.DoBottomButtons(rect, "Start".Translate(), "EdB.PrepareCarefully".Translate(), prepareCarefullyAction, true); } diff --git a/Source/PrepareCarefully.cs b/Source/PrepareCarefully.cs index 69a9c99..8e533c2 100644 --- a/Source/PrepareCarefully.cs +++ b/Source/PrepareCarefully.cs @@ -458,7 +458,27 @@ public void InitializeRelationshipManager(List pawns) facades.Add(pawnToFacadeMap[pawn]); } relationshipManager = new RelationshipManager(Verse.Find.GameInitData.startingPawns, facades); - } + } + + public bool FindScenPart() + { + if (DefDatabase.AllDefs.Count() == 1) { + MapGeneratorDef def = DefDatabase.AllDefs.First(); + if (def != null) { + foreach (var g in def.genSteps) { + if (g.GetType().FullName.Equals("EdB.PrepareCarefully.Genstep_ScenParts")) { + return true; + } + } + return false; + } + } + // TODO: We can't figure this out in every situation. If there's more than one + // map generator, the game is going to pick one at random, and we can't know at this + // point which one it's going to pick. In that case, we'll assume that everything is + // good. + return true; + } } } From df1b50568bb49cf93de49ea728c27fd262dee867 Mon Sep 17 00:00:00 2001 From: edbmods Date: Wed, 27 Jul 2016 22:58:02 -0700 Subject: [PATCH 15/29] Fixes to clear disabled work cache and reset starting work priorities --- Source/CustomPawn.cs | 14 +++++++----- Source/Genstep_ScenParts.cs | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index b2120e2..780b9e4 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -1039,13 +1039,9 @@ protected Pawn CopyPawn(Pawn pawn) // Copy relationships result.relations = pawn.relations; - return result; - } + ClearCachedDisabledWorkTypes(result.story); - // TODO: There's probably a better place for this utility method. - public static void ClearCachedDisabledWorkTypes(Pawn_StoryTracker story) - { - typeof(Pawn_StoryTracker).GetField("cachedDisabledWorkTypes", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(story, null); + return result; } public Pawn ConvertToPawn(bool resolveGraphics) { @@ -1110,6 +1106,7 @@ public Pawn ConvertToPawn(bool resolveGraphics) { } pawn.relations.ClearAllRelations(); + ClearCachedDisabledWorkTypes(pawn.story); return pawn; } @@ -1210,6 +1207,11 @@ public void ClearCachedLifeStage() { FieldInfo field = typeof(Pawn_AgeTracker).GetField("cachedLifeStageIndex", BindingFlags.NonPublic | BindingFlags.Instance); field.SetValue(pawn.ageTracker, -1); } + + public static void ClearCachedDisabledWorkTypes(Pawn_StoryTracker story) + { + typeof(Pawn_StoryTracker).GetField("cachedDisabledWorkTypes", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(story, null); + } } } diff --git a/Source/Genstep_ScenParts.cs b/Source/Genstep_ScenParts.cs index bc94f88..a6d98a5 100644 --- a/Source/Genstep_ScenParts.cs +++ b/Source/Genstep_ScenParts.cs @@ -43,10 +43,55 @@ public override void Generate() SpawnColonistsWithEquipment(arriveMethodPart); ApplyColonistHealthCustomizations(); + PrepForMapGen(); SpawnStartingResources(); } } + // From MapInitier_NewGame.PreForMapGen() with a change to make an error into a warning. + public static void PrepForMapGen() + { + foreach (Pawn current in Find.GameInitData.startingPawns) { + current.SetFactionDirect(Faction.OfPlayer); + PawnComponentsUtility.AddAndRemoveDynamicComponents(current, false); + } + foreach (Pawn current2 in Find.GameInitData.startingPawns) { + current2.workSettings.DisableAll(); + } + foreach (WorkTypeDef w in DefDatabase.AllDefs) { + if (w.alwaysStartActive) { + foreach (Pawn current3 in from col in Find.GameInitData.startingPawns + where !col.story.WorkTypeIsDisabled(w) + select col) { + current3.workSettings.SetPriority(w, 3); + } + } + else { + bool flag = false; + foreach (Pawn current4 in Find.GameInitData.startingPawns) { + if (!current4.story.WorkTypeIsDisabled(w) && current4.skills.AverageOfRelevantSkillsFor(w) >= 6f) { + current4.workSettings.SetPriority(w, 3); + flag = true; + } + } + if (!flag) { + IEnumerable source = from col in Find.GameInitData.startingPawns + where !col.story.WorkTypeIsDisabled(w) + select col; + if (source.Any()) { + Pawn pawn = source.InRandomOrder(null).MaxBy((Pawn c) => c.skills.AverageOfRelevantSkillsFor(w)); + pawn.workSettings.SetPriority(w, 3); + } + else if (w.requireCapableColonist) { + // EdB: Show warning instead of an error. + //Log.Error("No colonist could do requireCapableColonist work type " + w); + Log.Warning("No colonist can do what is thought to be a required work type " + w.gerundLabel); + } + } + } + } + } + // Copy of ScenPart_PlayerPawnsArriveMethod.GenerateIntoMap(), but with changes to spawn custom // equipment. public void SpawnColonistsWithEquipment(ScenPart_PlayerPawnsArriveMethod arriveMethodPart) From b404518748ae8081f5576c38baf26c3d1c892d36 Mon Sep 17 00:00:00 2001 From: edbmods Date: Thu, 28 Jul 2016 19:19:27 -0700 Subject: [PATCH 16/29] Updated version number to 0.14.0.5. Tweaked mod description. Added preview image. --- EdBPrepareCarefully.csproj | 2 +- EdBPrepareCarefully.sln | 6 +++--- Properties/AssemblyInfo.cs | 2 +- Resources/About/About.xml | 6 +++--- Resources/About/preview.png | Bin 0 -> 167409 bytes Resources/CHANGELOG.txt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 Resources/About/preview.png diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index f2aa673..013b65a 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -7,7 +7,7 @@ Library EdB.PrepareCarefully EdBPrepareCarefully - 0.14.0.4 + 0.14.0.5 False v3.5 ScenPart_PlayerPawnsArriveMethodCarefully.manifest diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln index 08fc718..5a1ef6a 100644 --- a/EdBPrepareCarefully.sln +++ b/EdBPrepareCarefully.sln @@ -23,9 +23,9 @@ Global $2.FileWidth = 120 $2.TabsToSpaces = False $2.EolMarker = Unix - $2.inheritsSet = null + $2.inheritsSet = VisualStudio $2.inheritsScope = text/plain - $2.scope = application/xml + $2.scope = text/plain $0.CSharpFormattingPolicy = $3 $3.IndentSwitchBody = True $3.ElseNewLinePlacement = NewLine @@ -195,6 +195,6 @@ Global $29.IncludeStaticEntities = True $0.VersionControlPolicy = $31 $31.inheritsSet = Mono - version = 0.14.0.4 + version = 0.14.0.5 EndGlobalSection EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 72fbaed..836ceeb 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("0.14.0.4")] +[assembly: AssemblyVersion("0.14.0.5")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Resources/About/About.xml b/Resources/About/About.xml index dbe7859..82b870d 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -3,11 +3,11 @@ EdB Prepare Carefully EdB - 0.14.1238 + 0.14.1241 Customize your colonists, choose your gear and prepare carefully for your crash landing! -If you get a starting setup that you like, save it as a preset so that you can start your game the same way later. +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 0.14.0.4] +[Version 0.14.0.5] \ No newline at end of file diff --git a/Resources/About/preview.png b/Resources/About/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..4cccaa0c7068c92d3ccaad00bf3d0e8cc3a21b66 GIT binary patch literal 167409 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%0APCz!KfN9vTIBvx6~#fUA>q!2xCeI9 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¬!X5_wg72VL6}SOB1j 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 a00RKG8Ypf$Fl2QA0000_QKWx literal 0 HcmV?d00001 diff --git a/Resources/CHANGELOG.txt b/Resources/CHANGELOG.txt index 1c97852..b754df7 100644 --- a/Resources/CHANGELOG.txt +++ b/Resources/CHANGELOG.txt @@ -1,6 +1,6 @@ _____________________________________________________________________________ - Version 0.14.1.1 (2016-07-16) + Version 0.14.0.5 (2016-07-28) _____________________________________________________________________________ - Added support for Alpha 14. From 7272e1ee51deb7fd237e0cf6c51add20d53c014a Mon Sep 17 00:00:00 2001 From: edbmods Date: Thu, 28 Jul 2016 19:40:33 -0700 Subject: [PATCH 17/29] Made it more difficult to kill a colonist by applying injuries --- Source/Dialog_Options.cs | 2 +- Source/InjuryManager.cs | 3 +++ Source/Panel_Health.cs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Source/Dialog_Options.cs b/Source/Dialog_Options.cs index e66e73f..9e264ec 100644 --- a/Source/Dialog_Options.cs +++ b/Source/Dialog_Options.cs @@ -172,7 +172,7 @@ public override void DoWindowContents(Rect inRect) GUI.color = new Color(0.65f, 0.65f, 0.65f); Widgets.Label(new Rect(0, cursor + 2, ContentSize.x - 32, height), name); Texture2D image = Textures.TextureRadioButtonOff; - Vector2 topLeft = new Vector2(itemRect.x + itemRect.width - 32, itemRect.y + itemRect.height / 2 - 16); + Vector2 topLeft = new Vector2(itemRect.x + itemRect.width - 24, itemRect.y + itemRect.height / 2 - 12); GUI.color = new Color(1, 1, 1, 0.28f); GUI.DrawTexture(new Rect(topLeft.x, topLeft.y, 24, 24), image); GUI.color = Color.white; diff --git a/Source/InjuryManager.cs b/Source/InjuryManager.cs index af62578..bd95689 100644 --- a/Source/InjuryManager.cs +++ b/Source/InjuryManager.cs @@ -167,6 +167,9 @@ public InjuryOption FindOptionByHediffDef(HediffDef def) public bool DoesStageKillPawn(HediffDef def, HediffStage stage) { + if (def.lethalSeverity > -1.0f && stage.minSeverity >= def.lethalSeverity) { + return true; + } if (stage.capMods != null) { foreach (var c in stage.capMods) { if (c.capacity == PawnCapacityDefOf.Consciousness) { diff --git a/Source/Panel_Health.cs b/Source/Panel_Health.cs index b88900a..30b788e 100644 --- a/Source/Panel_Health.cs +++ b/Source/Panel_Health.cs @@ -24,6 +24,7 @@ public class Panel_Health public IEnumerable implantRecipes; public List partRemovalList = new List(); protected HashSet disabledBodyParts = new HashSet(); + protected HashSet disabledInjuryOptions = new HashSet(); protected List severityOptions = new List(); protected List oldInjurySeverities = new List(); @@ -138,6 +139,8 @@ public void DrawHeader() Dialog_Options severityDialog; Dialog_Options bodyPartDialog; + ResetInjuryOptionEnabledState(customPawn); + Action addInjuryAction = () => { if (bodyPartSelectionRequired) { Injury injury = new Injury(); @@ -261,6 +264,9 @@ Dialog_Options injuryOptionDialog bodyPartSelectionRequired = false; } }, + EnabledFunc = (InjuryOption option) => { + return !disabledInjuryOptions.Contains(option); + }, ConfirmValidation = () => { if (selectedInjury == null) { return "EdB.PrepareCarefully.ErrorMustSelectInjury"; @@ -383,6 +389,28 @@ Dialog_Options injuryOptionDialog GUI.EndGroup(); } + protected void ResetInjuryOptionEnabledState(CustomPawn pawn) + { + Log.Warning("ResetInjuryOptionEnabledState()"); + disabledInjuryOptions.Clear(); + InjuryManager injuryManager = PrepareCarefully.Instance.HealthManager.InjuryManager; + foreach (var injuryOption in injuryManager.Options) { + InjuryOption option = injuryOption; + if (option.IsOldInjury) { + continue; + } + Injury injury = pawn.Injuries.FirstOrDefault((Injury i) => { + if (i.Option == option) { + Log.Warning("Pawn has the injury already: " + i.Option.HediffDef.defName); + } + return (i.Option == option); + }); + if (injury != null) { + disabledInjuryOptions.Add(injuryOption); + } + } + } + protected void ResetBodyPartEnabledState(IEnumerable parts, CustomPawn pawn) { disabledBodyParts.Clear(); @@ -404,7 +432,14 @@ protected void ResetSeverityOptions(InjuryOption injuryOption) int variant = 1; InjurySeverity previous = null; + foreach (var stage in injuryOption.HediffDef.stages) { + + // Filter out a stage if it will definitely kill the pawn. + if (PrepareCarefully.Instance.HealthManager.InjuryManager.DoesStageKillPawn(injuryOption.HediffDef, stage)) { + continue; + } + InjurySeverity value = null; if (stage.minSeverity == 0) { value = new InjurySeverity(0.001f, stage); From 3cf48760a39bd7dfc262ffa812e6ce7f1d09407c Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 19:10:33 -0700 Subject: [PATCH 18/29] Refactored skill level code to fix various issues and to make it a little less confusing --- Source/CostCalculator.cs | 6 +- Source/CustomPawn.cs | 585 +++++++++--------- .../Page_ConfigureStartingPawnsCarefully.cs | 30 +- Source/Randomizer.cs | 2 +- Source/Version3/PresetLoaderVersion3.cs | 7 +- Source/Version3/SaveRecordPawnV3.cs | 8 +- 6 files changed, 325 insertions(+), 313 deletions(-) diff --git a/Source/CostCalculator.cs b/Source/CostCalculator.cs index 424367c..2496216 100644 --- a/Source/CostCalculator.cs +++ b/Source/CostCalculator.cs @@ -124,13 +124,13 @@ public void CalculatePawnCost(ColonistCostDetails cost, CustomPawn pawn) cost.marketValue -= injuryValue; } - double skillCount = pawn.passions.Keys.Count(); + double skillCount = pawn.currentPassions.Keys.Count(); double passionLevelCount = 0; double passionLevelCost = 20; double passionateSkillCount = 0; - foreach (SkillDef def in pawn.passions.Keys) + foreach (SkillDef def in pawn.currentPassions.Keys) { - Passion passion = pawn.passions[def]; + Passion passion = pawn.currentPassions[def]; int level = pawn.GetSkillLevel(def); if (passion == Passion.Major) { diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index 780b9e4..e92bb32 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -17,13 +17,19 @@ public class ApparelConflict public class CustomPawn { - public Dictionary baseSkillLevels = new Dictionary(); - public Dictionary requiredSkillAdjustments = new Dictionary(); - protected Dictionary skillAdjustments = new Dictionary(); - protected Dictionary originalSkillAdjustments = new Dictionary(); - public Dictionary disabledSkills = new Dictionary(); - public Dictionary passions = new Dictionary(); + // The pawn's skill values before customization, without modifiers for backstories and traits. + // These values are saved so that the user can click the "Reset" button to restore them. + protected Dictionary originalSkillLevels = new Dictionary(); + + // The pawn's current skill levels, without modifiers for backstories and traits. + protected Dictionary currentSkillLevels = new Dictionary(); + + // The pawn's skill value modifiers from selected backstories and traits. + protected Dictionary skillLevelModifiers = new Dictionary(); + public Dictionary originalPassions = new Dictionary(); + public Dictionary currentPassions = new Dictionary(); + public bool randomRelations = false; protected string incapable = null; protected Pawn pawn; @@ -120,14 +126,17 @@ public void InitializeWithPawn(Pawn pawn) } // Set the skills. - ComputeBaseSkillLevels(); - foreach (SkillRecord record in pawn.skills.skills) { - skillAdjustments[record.def] = record.level - baseSkillLevels[record.def] - requiredSkillAdjustments[record.def]; - originalSkillAdjustments[record.def] = skillAdjustments[record.def]; - passions[record.def] = record.passion; - originalPassions[record.def] = record.passion; - } - ComputePawnSkillLevels(); + InitializeSkillLevelsAndPassions(); + ComputeSkillLevelModifiers(); + + //ComputeSkillModifiersFromBackstoriesAndTraits(); + //ComputeUnadjustedSkillValues(); + //ComputeBaseSkillLevels(); + //foreach (SkillRecord record in pawn.skills.skills) { + // skillAdjustments[record.def] = record.level - baseSkillLevels[record.def] - requiredSkillAdjustments[record.def]; + // originalSkillAdjustments[record.def] = skillAdjustments[record.def]; + //} + //ComputePawnSkillLevels(); graphics.Clear(); colors.Clear(); @@ -178,6 +187,212 @@ public void InitializeWithPawn(Pawn pawn) pawn.health.capacities.Clear(); } + public void InitializeSkillLevelsAndPassions() + { + // Save the original passions and set the current values to the same. + foreach (SkillRecord record in pawn.skills.skills) { + originalPassions[record.def] = record.passion; + currentPassions[record.def] = record.passion; + } + + // Compute and save the original unmodified skill levels. + // If the user's original, modified skill level was zero, we dont actually know what + // their original unadjusted value was. For example, if they have the brawler trait + // (-6 shooting) and their shooting level is zero, what was the original skill level? + // We don't know. It could have been anywhere from 0 to 6. + // We could maybe borrow some code from Pawn_StoryTracker.FinalLevelOfSkill() to be + // smarter about computing the value (i.e. factoring in the pawn's age, etc.), but + // instead we'll just pick a random number from the correct range if this happens. + foreach (var record in pawn.skills.skills) { + int negativeAdjustment = 0; + int positiveAdjustment = 0; + int modifier = ComputeSkillModifier(record.def); + if (modifier < 0) { + negativeAdjustment = -modifier; + } + else if (modifier > 0) { + positiveAdjustment = modifier; + } + + // When figuring out the unadjusted value, take into account the special + // case where the adjusted value is 0 or 20. + int value = record.level; + if (value == 0 && negativeAdjustment > 0) { + value = Rand.RangeInclusive(1, negativeAdjustment); + } + else if (value == 20 && positiveAdjustment > 0) { + value = Rand.RangeInclusive(20 - positiveAdjustment, 20); + } + else { + value -= positiveAdjustment; + value += negativeAdjustment; + } + + originalSkillLevels[record.def] = value; + } + + // Set the current values to the original values. + foreach (SkillRecord record in pawn.skills.skills) { + currentSkillLevels[record.def] = originalSkillLevels[record.def]; + } + } + + public void RestoreSkillLevelsAndPassions() + { + // Restore the original passions. + foreach (SkillRecord record in pawn.skills.skills) { + currentPassions[record.def] = originalPassions[record.def]; + } + + // Restore the original skill levels. + ApplyOriginalSkillLevels(); + } + + // Restores the current skill level values to the saved, original values. + public void ApplyOriginalSkillLevels() + { + foreach (var record in pawn.skills.skills) { + currentSkillLevels[record.def] = originalSkillLevels[record.def]; + } + CopySkillLevelsToPawn(); + } + + public void UpdateSkillLevelsForNewBackstoryOrTrait() + { + ComputeSkillLevelModifiers(); + // Clear caches. + ResetIncapableOf(); + pawn.health.capacities.Clear(); + } + + // Computes the skill level modifiers that the pawn gets from the selected backstories and traits. + public void ComputeSkillLevelModifiers() + { + foreach (var record in pawn.skills.skills) { + skillLevelModifiers[record.def] = ComputeSkillModifier(record.def); + } + } + protected int ComputeSkillModifier(SkillDef def) + { + int value = 0; + if (pawn.story.childhood.skillGainsResolved.ContainsKey(def)) { + value += pawn.story.childhood.skillGainsResolved[def]; + } + if (pawn.story.adulthood.skillGainsResolved.ContainsKey(def)) { + value += pawn.story.adulthood.skillGainsResolved[def]; + } + foreach (Trait trait in this.traits) { + if (trait != null) { + foreach (TraitDegreeData data in trait.def.degreeDatas) { + if (data.degree == trait.Degree) { + foreach (var pair in data.skillGains) { + SkillDef skillDef = pair.Key; + if (skillDef == def) { + value += pair.Value; + } + } + break; + } + } + } + } + return value; + } + + public void DecrementSkillLevel(SkillDef def) + { + SetSkillLevel(def, GetSkillLevel(def) - 1); + } + + public void IncrementSkillLevel(SkillDef def) + { + SetSkillLevel(def, GetSkillLevel(def) + 1); + } + + public int GetSkillLevel(SkillDef def) + { + if (this.IsSkillDisabled(def)) { + return 0; + } + else { + int value = currentSkillLevels[def] + skillLevelModifiers[def]; + if (value < SkillRecord.MinLevel) { + return SkillRecord.MinLevel; + } + else if (value > SkillRecord.MaxLevel) { + value = SkillRecord.MaxLevel; + } + return value; + } + } + + public void SetSkillLevel(SkillDef def, int value) + { + if (value > 20) { + value = 20; + } + else if (value < 0) { + value = 0; + } + int modifier = skillLevelModifiers[def]; + if (value < modifier) { + currentSkillLevels[def] = 0; + } + else { + currentSkillLevels[def] = value - modifier; + } + CopySkillLevelsToPawn(); + } + + // Any time a skill changes, update the underlying pawn with the new values. + protected void CopySkillLevelsToPawn() + { + foreach (var record in pawn.skills.skills) { + pawn.skills.GetSkill(record.def).level = GetSkillLevel(record.def); + } + + } + + // Set all unmodified skill levels to zero. + public void ClearSkills() + { + foreach (var record in pawn.skills.skills) { + currentSkillLevels[record.def] = 0; + } + CopySkillLevelsToPawn(); + } + + public bool IsSkillDisabled(SkillDef def) + { + return pawn.skills.GetSkill(def).TotallyDisabled == true; + } + + public int GetSkillModifier(SkillDef def) + { + return skillLevelModifiers[def]; + } + + public int GetUnmodifiedSkillLevel(SkillDef def) + { + return currentSkillLevels[def]; + } + + public void SetUnmodifiedSkillLevel(SkillDef def, int value) + { + currentSkillLevels[def] = value; + CopySkillLevelsToPawn(); + } + + public int GetOriginalSkillLevel(SkillDef def) + { + return originalSkillLevels[def]; + } + + public void SetOriginalSkillLevel(SkillDef def, int value) + { + originalSkillLevels[def] = value; + } + protected bool ApparelIsTintedByDefault(ThingDef def, ThingDef stuffDef) { if (stuffDef == null) { @@ -291,35 +506,35 @@ public bool IsBodyPartReplaced(BodyPartRecord record) { } public void IncreasePassion(SkillDef def) { - if (IsDisabled(def)) { + if (IsSkillDisabled(def)) { return; } - if (passions[def] == Passion.None) { - passions[def] = Passion.Minor; + if (currentPassions[def] == Passion.None) { + currentPassions[def] = Passion.Minor; } - else if (passions[def] == Passion.Minor) { - passions[def] = Passion.Major; + else if (currentPassions[def] == Passion.Minor) { + currentPassions[def] = Passion.Major; } - else if (passions[def] == Passion.Major) { - passions[def] = Passion.None; + else if (currentPassions[def] == Passion.Major) { + currentPassions[def] = Passion.None; } - pawn.skills.GetSkill(def).passion = passions[def]; + pawn.skills.GetSkill(def).passion = currentPassions[def]; } public void DecreasePassion(SkillDef def) { - if (IsDisabled(def)) { + if (IsSkillDisabled(def)) { return; } - if (passions[def] == Passion.None) { - passions[def] = Passion.Major; + if (currentPassions[def] == Passion.None) { + currentPassions[def] = Passion.Major; } - else if (passions[def] == Passion.Minor) { - passions[def] = Passion.None; + else if (currentPassions[def] == Passion.Minor) { + currentPassions[def] = Passion.None; } - else if (passions[def] == Passion.Major) { - passions[def] = Passion.Minor; + else if (currentPassions[def] == Passion.Major) { + currentPassions[def] = Passion.Minor; } - pawn.skills.GetSkill(def).passion = passions[def]; + pawn.skills.GetSkill(def).passion = currentPassions[def]; } public List AllAcceptedApparel { @@ -548,8 +763,7 @@ public Backstory Childhood { } set { pawn.story.childhood = value; - ComputePawnSkillLevels(); - this.pawn.health.capacities.Clear(); + ResetBackstories(); } } @@ -559,25 +773,14 @@ public Backstory Adulthood { } set { pawn.story.adulthood = value; - ComputePawnSkillLevels(); - this.pawn.health.capacities.Clear(); - ResetBodyType(); + ResetBackstories(); } } - protected void CheckSkills() { - foreach (var record in pawn.skills.skills) { - int value = GetSkillLevel(record.def); - if (value > 20) { - skillAdjustments[record.def] -= (value - 20); - } - if (IsDisabled(record.def)) { - record.passion = Passion.None; - } - else { - record.passion = passions[record.def]; - } - } + protected void ResetBackstories() + { + UpdateSkillLevelsForNewBackstoryOrTrait(); + ResetBodyType(); } public string HeadGraphicPath { @@ -627,9 +830,7 @@ protected void ResetTraits() { pawn.story.traits.GainTrait(trait); } } - ResetIncapableOf(); - pawn.health.capacities.Clear(); - ComputePawnSkillLevels(); + UpdateSkillLevelsForNewBackstoryOrTrait(); } public bool HasTrait(Trait trait) { @@ -766,170 +967,6 @@ protected void ResetBodyType() } } - public void ResetSkills() - { - foreach (var record in pawn.skills.skills) { - this.skillAdjustments[record.def] = this.originalSkillAdjustments[record.def]; - this.passions[record.def] = this.originalPassions[record.def]; - } - ComputePawnSkillLevels(); - } - - public void ClearSkills() - { - foreach (var record in pawn.skills.skills) { - this.skillAdjustments[record.def] = 0; - this.passions[record.def] = Passion.None; - } - ComputePawnSkillLevels(); - } - - public void ResetOriginalSkillsAndPassions() - { - foreach (var record in pawn.skills.skills) { - this.originalSkillAdjustments[record.def] = this.skillAdjustments[record.def]; - this.originalPassions[record.def] = this.passions[record.def]; - } - } - - protected void ComputePawnSkillLevels() { - ResetIncapableOf(); - ResetDisabledSkills(); - ComputeBaseSkillLevels(); - foreach (var record in pawn.skills.skills) { - SkillDef def = record.def; - pawn.skills.GetSkill(def).level = GetSkillLevel(def); - pawn.skills.GetSkill(def).passion = passions[def]; - } - } - - protected void ComputeBaseSkillLevels() - { - foreach (var record in pawn.skills.skills) { - baseSkillLevels[record.def] = 0; - } - foreach (SkillDef def in pawn.story.childhood.skillGainsResolved.Keys) { - baseSkillLevels[def] += pawn.story.childhood.skillGainsResolved[def]; - } - foreach (SkillDef def in pawn.story.adulthood.skillGainsResolved.Keys) { - baseSkillLevels[def] += pawn.story.adulthood.skillGainsResolved[def]; - } - foreach (Trait trait in pawn.story.traits.allTraits) { - foreach (TraitDegreeData data in trait.def.degreeDatas) { - foreach (var pair in data.skillGains) { - SkillDef def = pair.Key; - baseSkillLevels[def] += pair.Value; - } - } - } - foreach (var pair in baseSkillLevels) { - //requiredSkillAdjustments[pair.Key] = pair.Value >= -3 ? 0 : (-pair.Value - 3); - requiredSkillAdjustments[pair.Key] = pair.Value < 0 ? -pair.Value : 0; - } - } - - public int GetBaseSkillLevel(SkillDef def) - { - return baseSkillLevels[def]; - } - - public int GetSkillAdjustments(SkillDef def) - { - return skillAdjustments[def]; - } - - public int GetRequiredSkillAdjustments(SkillDef def) - { - return requiredSkillAdjustments[def]; - } - - public int GetSkillLevel(SkillDef def) - { - if (this.IsDisabled(def)) { - return 0; - } - else { - int level = baseSkillLevels[def] + requiredSkillAdjustments[def] + skillAdjustments[def]; - return level; - } - } - - public void IncreaseSkill(SkillDef def) - { - if (!IsDisabled(def) && GetSkillLevel(def) < 20) { - skillAdjustments[def]++; - pawn.skills.GetSkill(def).level = GetSkillLevel(def); - } - } - - public void SetSkillLevel(SkillDef def, int value) - { - value -= baseSkillLevels[def]; - if (value > 20) { - value = 20; - } - else if (value < 0) { - value = 0; - } - if (!IsDisabled(def)) { - skillAdjustments[def] = value; - pawn.skills.GetSkill(def).level = GetSkillLevel(def); - } - } - - // Maximum skill level is always 20. - protected int MaximumSkillLevel(SkillDef def) { - return 20; - } - - // Minimum skill level is normal 0, but it may be higher if backstories and traits adjust it. - protected int MinimumSkillLevel(SkillDef def) { - int value = UnadjustedSkillLevel(def); - if (value < 0) { - value = 0; - } - return value; - } - - protected int UnadjustedSkillLevel(SkillDef def) { - int value = 0; - value += pawn.story.childhood.skillGainsResolved[def]; - value += pawn.story.adulthood.skillGainsResolved[def]; - foreach (Trait trait in this.traits) { - if (trait != null) { - foreach (TraitDegreeData data in trait.def.degreeDatas) { - foreach (var pair in data.skillGains) { - SkillDef skillDef = pair.Key; - if (skillDef == def) { - value += pair.Value; - } - } - } - } - } - return value; - } - - public void SetSkillAdjustment(SkillDef def, int value) - { - if (value < 0) { - value = 0; - } - if (baseSkillLevels[def] + value > 20) { - value -= (baseSkillLevels[def] - 20); - } - skillAdjustments[def] = value; - pawn.skills.GetSkill(def).level = GetSkillLevel(def); - } - - public void DecreaseSkill(SkillDef def) - { - if (!IsDisabled(def) && skillAdjustments[def] > 0) { - skillAdjustments[def]--; - pawn.skills.GetSkill(def).level = GetSkillLevel(def); - } - } - public string ResetIncapableOf() { List incapableList = new List(); @@ -946,44 +983,12 @@ public string ResetIncapableOf() return incapable; } - public void ResetDisabledSkills() - { - foreach (var record in pawn.skills.skills) { - disabledSkills[record.def] = false; - } - foreach (var record in pawn.skills.skills) { - if (CheckForDisabledSkill(record.def)) { - disabledSkills[record.def] = true; - } - } - } - - protected bool CheckForDisabledSkill(SkillDef def) - { - foreach (WorkTypeDef w in DefDatabase.AllDefs) { - using (List.Enumerator enumerator = w.relevantSkills.GetEnumerator()) { - while (enumerator.MoveNext()) { - if (enumerator.Current == def && pawn.story.WorkTypeIsDisabled(w)) { - return true; - } - } - } - } - return false; - } - - public bool IsDisabled(SkillDef skill) - { - return pawn.skills.GetSkill(skill).TotallyDisabled; - //return disabledSkills[skill]; - } - public bool IsApparelConflict() { return false; } - protected Pawn CopyPawn(Pawn pawn) + protected Pawn CopyPawn(Pawn source) { // TODO: Evaluate //Pawn result = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); @@ -992,33 +997,31 @@ protected Pawn CopyPawn(Pawn pawn) // Reset health to remove any old injuries. result.health = new Pawn_HealthTracker(result); - result.gender = pawn.gender; + result.gender = source.gender; // Copy age. - result.ageTracker.BirthAbsTicks = pawn.ageTracker.BirthAbsTicks; - result.ageTracker.AgeBiologicalTicks = pawn.ageTracker.AgeBiologicalTicks; + result.ageTracker.BirthAbsTicks = source.ageTracker.BirthAbsTicks; + result.ageTracker.AgeBiologicalTicks = source.ageTracker.AgeBiologicalTicks; // Copy story. - result.story.adulthood = pawn.story.adulthood; - result.story.childhood = pawn.story.childhood; + result.story.adulthood = source.story.adulthood; + result.story.childhood = source.story.childhood; result.story.traits = new TraitSet(result); - foreach (var t in pawn.story.traits.allTraits) { + foreach (var t in source.story.traits.allTraits) { result.story.traits.allTraits.Add(t); } - result.story.skinWhiteness = pawn.story.skinWhiteness; - NameTriple name = pawn.Name as NameTriple; + result.story.skinWhiteness = source.story.skinWhiteness; + NameTriple name = source.Name as NameTriple; result.Name = new NameTriple(name.First, name.Nick, name.Last); - result.story.hairDef = pawn.story.hairDef; - result.story.hairColor = pawn.story.hairColor; + result.story.hairDef = source.story.hairDef; + result.story.hairColor = source.story.hairColor; // Need to use reflection to set the private graphic path field. - typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, pawn.story.HeadGraphicPath); - result.story.crownType = pawn.story.crownType; - // Clear cached values from the story tracker. - CustomPawn.ClearCachedDisabledWorkTypes(pawn.story); + typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(source.story, source.story.HeadGraphicPath); + result.story.crownType = source.story.crownType; // Copy apparel. List pawnApparelList = (List)typeof(Pawn_ApparelTracker).GetField("wornApparel", - BindingFlags.NonPublic | BindingFlags.Instance).GetValue(pawn.apparel); + BindingFlags.NonPublic | BindingFlags.Instance).GetValue(source.apparel); List resultApparelList = (List)typeof(Pawn_ApparelTracker).GetField("wornApparel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(result.apparel); resultApparelList.Clear(); @@ -1028,16 +1031,19 @@ protected Pawn CopyPawn(Pawn pawn) // Copy skills. result.skills.skills.Clear(); - foreach (var s in pawn.skills.skills) { + foreach (var s in source.skills.skills) { SkillRecord record = new SkillRecord(result, s.def); record.level = s.level; record.passion = s.passion; record.xpSinceLastLevel = s.xpSinceLastLevel; result.skills.skills.Add(record); + if (record.level < 0) { + Log.Error("WTF"); + } } // Copy relationships - result.relations = pawn.relations; + result.relations = source.relations; ClearCachedDisabledWorkTypes(result.story); @@ -1045,38 +1051,36 @@ protected Pawn CopyPawn(Pawn pawn) } public Pawn ConvertToPawn(bool resolveGraphics) { - // TODO: Evaluate - //Pawn pawn = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist, Faction.OfColony); - Pawn pawn = new Randomizer().GenerateColonist(); + Pawn result = new Randomizer().GenerateColonist(); - pawn.gender = this.pawn.gender; - pawn.story.adulthood = Adulthood; - pawn.story.childhood = Childhood; - TraitSet traitSet = new TraitSet(pawn); + result.gender = this.pawn.gender; + result.story.adulthood = Adulthood; + result.story.childhood = Childhood; + TraitSet traitSet = new TraitSet(result); traitSet.allTraits.Clear(); foreach (Trait trait in traits) { if (trait != null) { traitSet.allTraits.Add(trait); } } - pawn.story.traits = traitSet; - pawn.story.skinWhiteness = this.pawn.story.skinWhiteness; - pawn.story.hairDef = this.pawn.story.hairDef; - pawn.story.hairColor = colors[PawnLayers.Hair]; + result.story.traits = traitSet; + result.story.skinWhiteness = this.pawn.story.skinWhiteness; + result.story.hairDef = this.pawn.story.hairDef; + result.story.hairColor = colors[PawnLayers.Hair]; // Need to use reflection to set the private graphic path method. - typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pawn.story, HeadGraphicPath); + typeof(Pawn_StoryTracker).GetField("headGraphicPath", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(result.story, HeadGraphicPath); // Clear cached values from the story tracker. // TODO: It might make more sense to create a new instance of Pawn_StoryTracker, but need // to make sure all of the details are filled in with that approach. - CustomPawn.ClearCachedDisabledWorkTypes(pawn.story); + CustomPawn.ClearCachedDisabledWorkTypes(result.story); - pawn.Name = this.pawn.Name; + result.Name = this.pawn.Name; - pawn.ageTracker.BirthAbsTicks = this.pawn.ageTracker.BirthAbsTicks; - pawn.ageTracker.AgeBiologicalTicks = this.pawn.ageTracker.AgeBiologicalTicks; + result.ageTracker.BirthAbsTicks = this.pawn.ageTracker.BirthAbsTicks; + result.ageTracker.AgeBiologicalTicks = this.pawn.ageTracker.AgeBiologicalTicks; FieldInfo wornApparelField = typeof(Pawn_ApparelTracker).GetField("wornApparel", BindingFlags.Instance | BindingFlags.NonPublic); - List apparel = (List)wornApparelField.GetValue(pawn.apparel); + List apparel = (List)wornApparelField.GetValue(result.apparel); apparel.Clear(); AddApparel(PawnLayers.Pants, apparel); @@ -1085,14 +1089,17 @@ public Pawn ConvertToPawn(bool resolveGraphics) { AddApparel(PawnLayers.TopClothingLayer, apparel); AddApparel(PawnLayers.Hat, apparel); - foreach (SkillRecord skill in pawn.skills.skills) { + foreach (SkillRecord skill in result.skills.skills) { int value = this.GetSkillLevel(skill.def); if (value < 0) { value = 0; } + if (value > 20) { + value = 20; + } skill.level = value; - if (!IsDisabled(skill.def)) { - skill.passion = this.passions[skill.def]; + if (!IsSkillDisabled(skill.def)) { + skill.passion = this.currentPassions[skill.def]; skill.xpSinceLastLevel = Rand.Range(skill.XpRequiredForLevelUp * 0.1f, skill.XpRequiredForLevelUp * 0.5f); } else { @@ -1102,13 +1109,13 @@ public Pawn ConvertToPawn(bool resolveGraphics) { } if (resolveGraphics) { - pawn.Drawer.renderer.graphics.ResolveAllGraphics(); + result.Drawer.renderer.graphics.ResolveAllGraphics(); } - pawn.relations.ClearAllRelations(); - ClearCachedDisabledWorkTypes(pawn.story); + result.relations.ClearAllRelations(); + ClearCachedDisabledWorkTypes(result.story); - return pawn; + return result; } public Pawn ConvertToPawn() diff --git a/Source/Page_ConfigureStartingPawnsCarefully.cs b/Source/Page_ConfigureStartingPawnsCarefully.cs index 0953858..9328abb 100644 --- a/Source/Page_ConfigureStartingPawnsCarefully.cs +++ b/Source/Page_ConfigureStartingPawnsCarefully.cs @@ -388,7 +388,7 @@ public override void DoWindowContents(Rect inRect) try { GUI.BeginGroup(innerRect); DrawNameAndDescription(); - DrawRandomizeAll(); + DrawRandomizeAllButton(); DrawGenderAndAge(); DrawColonistSaveButtons(); DrawPortrait(); @@ -583,7 +583,7 @@ public CustomPawn SelectedPawn { protected static Rect SectionRectSkills = new Rect(SectionRectGenderAndAge.x + SectionRectGenderAndAge.width + SectionPadding.x, 0, RightColumnWidth, 400); protected static Rect SectionRectIncapable = new Rect(SectionRectSkills.x, SectionRectSkills.y + SectionRectSkills.height + SectionPadding.y, RightColumnWidth, 105); - protected void DrawRandomizeAll() + protected void DrawRandomizeAllButton() { CustomPawn customPawn = CurrentPawn; GUI.color = SectionBackgroundColor; @@ -915,7 +915,7 @@ protected void DrawAppearance(CustomPawn customPawn) GUI.DrawTexture(randomRect, Textures.TextureButtonRandom); if (Widgets.ButtonInvisible(randomRect, false)) { SoundDefOf.TickLow.PlayOneShotOnCamera(); - randomizer.RandomizePawn(customPawn); + randomizer.RandomizeAppearance(customPawn); } } @@ -2006,7 +2006,7 @@ protected void DrawSkills() GUI.DrawTexture(RectButtonResetSkills, Textures.TextureButtonReset); if (Widgets.ButtonInvisible(RectButtonResetSkills, false)) { SoundDefOf.TickLow.PlayOneShotOnCamera(); - CurrentPawn.ResetSkills(); + CurrentPawn.RestoreSkillLevelsAndPassions(); } int skillCount = customPawn.Pawn.skills.skills.Count; @@ -2040,7 +2040,7 @@ protected void DrawSkills() Rect rect = new Rect(skillsRect.width, 4, 16, 16); for (int i = 0; i < skillCount; i++) { - if (customPawn.IsDisabled(customPawn.Pawn.skills.skills[i].def)) { + if (customPawn.IsSkillDisabled(customPawn.Pawn.skills.skills[i].def)) { rect.y += spacing; continue; } @@ -2078,8 +2078,8 @@ protected void DrawSkills() Rect position = new Rect(skillLevelLabelWidth + 10, 2, 24, 24); for (int i = 0; i < skillCount; i++) { SkillRecord skill = customPawn.Pawn.skills.skills[i]; - if (!customPawn.IsDisabled(skill.def)) { - Passion passion = customPawn.passions[skill.def]; + if (!customPawn.IsSkillDisabled(skill.def)) { + Passion passion = customPawn.currentPassions[skill.def]; if (passion > Passion.None) { Texture2D image = (passion != Passion.Major) ? Textures.TexturePassionMinor : Textures.TexturePassionMajor; GUI.color = Color.white; @@ -2138,13 +2138,13 @@ private void DrawSkill(SkillRecord skill, Vector2 topLeft, float skillWidth) Widgets.Label(rect2, skill.def.skillLabel); Rect position = new Rect(rect2.xMax, 0, 24, 24); int level = customPawn.GetSkillLevel(skill.def); - bool disabled = customPawn.IsDisabled(skill.def); + bool disabled = customPawn.IsSkillDisabled(skill.def); if (!disabled) { float barSize = (level > 0 ? (float)level : 0) / 20f; Rect screenRect = new Rect(position.xMax, 0, rect.width - position.xMax, rect.height); FillableBar(screenRect, barSize, Textures.TextureSkillBarFill); - int baseLevel = customPawn.GetBaseSkillLevel(skill.def); + int baseLevel = customPawn.GetSkillModifier(skill.def); float baseBarSize = (baseLevel > 0 ? (float)baseLevel : 0) / 20f; screenRect = new Rect(position.xMax, 0, rect.width - position.xMax, rect.height); FillableBar(screenRect, baseBarSize, Textures.TextureSkillBarFill); @@ -2155,8 +2155,8 @@ private void DrawSkill(SkillRecord skill, Vector2 topLeft, float skillWidth) if (Widgets.ButtonInvisible(screenRect, false)) { Vector2 pos = Event.current.mousePosition; - float x = pos.x - screenRect.x; - int value = (int) Math.Round((x / screenRect.width) * 20); + float x = pos.x - screenRect.x - 2; + int value = (int) Math.Ceiling((x / screenRect.width) * 20); SoundDefOf.TickTiny.PlayOneShotOnCamera(); SetSkillLevel(skill, value); } @@ -2175,7 +2175,11 @@ private void DrawSkill(SkillRecord skill, Vector2 topLeft, float skillWidth) Widgets.Label(rect3, label); GUI.color = Color.white; GUI.EndGroup(); + TooltipHandler.TipRegion(rect, new TipSignal(GetSkillDescription(skill), skill.def.GetHashCode() * 397945)); + //TooltipHandler.TipRegion(rect, new TipSignal("Unmodified: " + CurrentPawn.xGetUnmodifiedSkillLevel(skill.def) + // + "\n" + "Modifiers: " + CurrentPawn.xGetSkillModifier(skill.def) + // + "\n" + GetSkillDescription(skill), skill.def.GetHashCode() * 397945)); } // EdB: Copy of private static SkillUI.GetSkillDescription(). @@ -2482,14 +2486,14 @@ protected void IncreaseSkill(int skillIndex) { CustomPawn pawn = CurrentPawn; SkillRecord record = pawn.Pawn.skills.skills[skillIndex]; - pawn.IncreaseSkill(record.def); + pawn.IncrementSkillLevel(record.def); } protected void DecreaseSkill(int skillIndex) { CustomPawn pawn = CurrentPawn; SkillRecord record = pawn.Pawn.skills.skills[skillIndex]; - pawn.DecreaseSkill(record.def); + pawn.DecrementSkillLevel(record.def); } protected void IncreasePassion(int skillIndex) diff --git a/Source/Randomizer.cs b/Source/Randomizer.cs index 9a38ba3..7556964 100644 --- a/Source/Randomizer.cs +++ b/Source/Randomizer.cs @@ -52,7 +52,7 @@ public void RandomizeTraits(CustomPawn customPawn) } } - public void RandomizePawn(CustomPawn customPawn) + public void RandomizeAppearance(CustomPawn customPawn) { Pawn pawn; int tries = 0; diff --git a/Source/Version3/PresetLoaderVersion3.cs b/Source/Version3/PresetLoaderVersion3.cs index 5b72369..e5dc126 100644 --- a/Source/Version3/PresetLoaderVersion3.cs +++ b/Source/Version3/PresetLoaderVersion3.cs @@ -262,9 +262,10 @@ public CustomPawn LoadPawn(SaveRecordPawnV3 record) Failed = true; continue; } - pawn.passions[def] = record.passions[i]; - pawn.SetSkillAdjustment(def, record.skillValues[i]); - pawn.ResetOriginalSkillsAndPassions(); + pawn.currentPassions[def] = record.passions[i]; + pawn.originalPassions[def] = record.passions[i]; + pawn.SetOriginalSkillLevel(def, record.skillValues[i]); + pawn.SetUnmodifiedSkillLevel(def, record.skillValues[i]); } if (record.originalPassions != null && record.originalPassions.Count == record.skillNames.Count) { for (int i = 0; i < record.skillNames.Count; i++) { diff --git a/Source/Version3/SaveRecordPawnV3.cs b/Source/Version3/SaveRecordPawnV3.cs index f2e1bbf..bcae67f 100644 --- a/Source/Version3/SaveRecordPawnV3.cs +++ b/Source/Version3/SaveRecordPawnV3.cs @@ -65,8 +65,8 @@ public SaveRecordPawnV3(CustomPawn pawn) } foreach (var skill in pawn.Pawn.skills.skills) { this.skillNames.Add(skill.def.defName); - this.skillValues.Add(pawn.GetSkillAdjustments(skill.def)); - this.passions.Add(pawn.passions[skill.def]); + 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++) { @@ -198,8 +198,8 @@ public CustomPawn CreatePawn() if (def == null) { continue; } - pawn.passions[def] = this.passions[i]; - pawn.SetSkillAdjustment(def, this.skillValues[i]); + pawn.currentPassions[def] = this.passions[i]; + pawn.SetUnmodifiedSkillLevel(def, this.skillValues[i]); } for (int i = 0; i < PawnLayers.Count; i++) { From 0280a13f852113770cb2219b400af4b8746ddd7f Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 19:14:41 -0700 Subject: [PATCH 19/29] Removed commented-out skill initialization code --- Source/CustomPawn.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index e92bb32..f8347c9 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -129,15 +129,6 @@ public void InitializeWithPawn(Pawn pawn) InitializeSkillLevelsAndPassions(); ComputeSkillLevelModifiers(); - //ComputeSkillModifiersFromBackstoriesAndTraits(); - //ComputeUnadjustedSkillValues(); - //ComputeBaseSkillLevels(); - //foreach (SkillRecord record in pawn.skills.skills) { - // skillAdjustments[record.def] = record.level - baseSkillLevels[record.def] - requiredSkillAdjustments[record.def]; - // originalSkillAdjustments[record.def] = skillAdjustments[record.def]; - //} - //ComputePawnSkillLevels(); - graphics.Clear(); colors.Clear(); PawnGraphicSet pawnGraphics = pawn.Drawer.renderer.graphics; From d0f9dcce309e41efc087ebbefde5e896f538e65b Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 19:17:41 -0700 Subject: [PATCH 20/29] Added error checking to avoid trying to assign relationships between deleted pawns --- Source/PrepareCarefully.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Source/PrepareCarefully.cs b/Source/PrepareCarefully.cs index 8e533c2..c5ff31a 100644 --- a/Source/PrepareCarefully.cs +++ b/Source/PrepareCarefully.cs @@ -342,8 +342,21 @@ protected void AddRelationships(Dictionary lookup, IEnumerable { continue; } - Pawn source = lookup[r.source]; - Pawn target = lookup[r.target]; + Pawn source = null; + Pawn target = null; + if (!lookup.TryGetValue(r.source, out source)) { + Log.Warning("Prepare Carefully could not find source pawn when trying to create relationship (" + r.def.defName + "): " + r.source.Name); + continue; + } + if (!lookup.TryGetValue(r.target, out target)) { + Log.Warning("Prepare Carefully could not find target pawn when trying to create relationship (" + r.def.defName + "): " + r.target.Name); + continue; + } + + // TODO: If either of those lookups are failing, we're not tracking pawns properly + // somewhere. I've seen those lookups fail after deleting colonists (but not + // consistently). Should track down the root cause. + worker.CreateRelation(source, target, ref request); } } From 93cfb4fc2c07717f1f621d499e69be32d416a38e Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 19:26:04 -0700 Subject: [PATCH 21/29] Removed unused Genstep classes. Removed debug log messages. Removed a couple of comments. --- Source/CustomPawn.cs | 3 - Source/Genstep_Colonists.cs | 295 ----------------------- Source/Genstep_SpawnStartingResources.cs | 144 ----------- Source/RelationshipManager.cs | 1 - Source/Version3/PresetLoaderVersion3.cs | 3 - 5 files changed, 446 deletions(-) delete mode 100644 Source/Genstep_Colonists.cs delete mode 100644 Source/Genstep_SpawnStartingResources.cs diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index f8347c9..d38d0d7 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -1028,9 +1028,6 @@ protected Pawn CopyPawn(Pawn source) record.passion = s.passion; record.xpSinceLastLevel = s.xpSinceLastLevel; result.skills.skills.Add(record); - if (record.level < 0) { - Log.Error("WTF"); - } } // Copy relationships diff --git a/Source/Genstep_Colonists.cs b/Source/Genstep_Colonists.cs deleted file mode 100644 index c97e8d6..0000000 --- a/Source/Genstep_Colonists.cs +++ /dev/null @@ -1,295 +0,0 @@ -using RimWorld; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Verse; - -namespace EdB.PrepareCarefully -{ - // TODO: Alpha 14 - // This is the Alpha 13 map generation step that spawns the colonists into the map, along with - // their equipment. It is no longer needed but is left for reference. - public class Genstep_Colonists : Genstep - { - public Genstep_Colonists() - { - } - - public override void Generate() - { - if (PrepareCarefully.Instance.Active && PrepareCarefully.Instance.Colonists != null - && PrepareCarefully.Instance.Colonists.Count > 0) - { - GeneratePrepareCarefullyColonistsAndEquipment(); - } - else { - RimWorld.GenStep_ScenParts genstep = new RimWorld.GenStep_ScenParts(); - genstep.Generate(); - } - } - - // A copy of RimWorld.Genstep_GenerateColonists.Generate() with modifications. - public void GeneratePrepareCarefullyColonistsAndEquipment() - { - // EdB: Copy our colonists into the Find.GameInitData. - Find.GameInitData.startingPawns = PrepareCarefully.Instance.Colonists; - - foreach (Pawn current in Find.GameInitData.startingPawns) { - current.SetFactionDirect(Faction.OfPlayer); - PawnComponentsUtility.AddAndRemoveDynamicComponents(current, false); - // TODO: Alpha 14 - /* - current.needs.mood.thoughts.TryGainThought(ThoughtDefOf.NewColonyOptimism); - foreach (Pawn current2 in Find.GameInitData.startingPawns) { - if (current2 != current) { - Thought_SocialMemory thought_SocialMemory = (Thought_SocialMemory)ThoughtMaker.MakeThought(ThoughtDefOf.CrashedTogether); - thought_SocialMemory.SetOtherPawn(current2); - current.needs.mood.thoughts.TryGainThought(thought_SocialMemory); - } - } - */ - } - - // EdB: Call our modified version of the work settings methods. - //Genstep_Colonists.CreateInitialWorkSettings(); - CreateInitialWorkSettings(); - - bool startedDirectInEditor = Find.GameInitData.QuickStarted; - List> list = new List>(); - foreach (Pawn current3 in Find.GameInitData.startingPawns) { - if (Find.GameInitData.startedFromEntry && Rand.Value < 0.5) { - current3.health.AddHediff(HediffDefOf.CryptosleepSickness, null, null); - } - // EdB: Don't give the default equipment to the colonists - //List list2 = new List(); - //list2.Add(current3); - //Thing thing = ThingMaker.MakeThing(ThingDefOf.MealSurvivalPack, null); - //thing.stackCount = 10; - //list2.Add(thing); - //Thing thing2 = ThingMaker.MakeThing(ThingDefOf.Medicine, null); - //thing2.stackCount = 8; - //list2.Add(thing2); - //list.Add(list2); - } - // EdB: Do not add the default weapons and pet. - //List list3 = new List { - // ThingMaker.MakeThing(ThingDefOf.Gun_SurvivalRifle, null), - // ThingMaker.MakeThing(ThingDefOf.Gun_Pistol, null), - // ThingMaker.MakeThing(ThingDefOf.MeleeWeapon_Knife, ThingDefOf.Plasteel), - // Genstep_Colonists.GenerateRandomPet() - //}; - // EdB: Don't try to assign the default equipment to the colony here. - //int num = 0; - //foreach (Thing current4 in list3) { - // current4.SetFactionDirect(Faction.OfPlayer); - // list[num].Add(current4); - // num++; - // if (num >= list.Count) { - // num = 0; - // } - //} - // EdB: Don't do the default drop pod setup. - //bool canInstaDropDuringInit = startedDirectInEditor; - //DropPodUtility.DropThingGroupsNear(MapGenerator.PlayerStartSpot, list, 110, canInstaDropDuringInit, true, true); - - // EdB: We add our custom steps at the end. - // EdB: Add injuries and custom body modifications. - int index = 0; - foreach (Pawn pawn in Find.GameInitData.startingPawns) { - pawn.health = new Pawn_HealthTracker(pawn); - CustomPawn customPawn = PrepareCarefully.Instance.Pawns[index++]; - if (customPawn.RandomInjuries) { - AgeInjuryUtility.GenerateRandomOldAgeInjuries(pawn, true); - } - } - for (int i = 0; i < PrepareCarefully.Instance.Pawns.Count; i++) { - CustomPawn customPawn = PrepareCarefully.Instance.Pawns[i]; - Pawn pawn = Find.GameInitData.startingPawns[i]; - foreach (Injury injury in customPawn.Injuries) { - injury.AddToPawn(customPawn, pawn); - } - foreach (Implant implant in customPawn.Implants) { - implant.AddToPawn(customPawn, pawn); - } - } - - // EdB: Prepare the custom inventory in the drop pods. - List> pods = new List>(); - for (int i = 0; i < Find.GameInitData.startingPawns.Count; i++) { - pods.Add(new List()); - pods[i].Add(Find.GameInitData.startingPawns[i]); - } - int pawnCount = pods.Count; - - List weapons = new List(); - List food = new List(); - List apparel = new List(); - List animals = new List(); - List other = new List(); - - int maxStack = 50; - foreach (var e in PrepareCarefully.Instance.Equipment) { - EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[e.EquipmentKey]; - if (entry == null) { - string thing = e.def != null ? e.def.defName : "null"; - string stuff = e.stuffDef != null ? e.stuffDef.defName : "null"; - Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); - continue; - } - if (entry.gear) { - int count = e.Count; - int idealStackCount = count / pawnCount; - while (count > 0) { - int stackCount = idealStackCount; - if (stackCount < 1) { - stackCount = 1; - } - if (stackCount > entry.def.stackLimit) { - stackCount = entry.def.stackLimit; - } - if (stackCount > maxStack) { - stackCount = maxStack; - } - if (stackCount > count) { - stackCount = count; - } - - Thing thing = null; - if (entry.def.MadeFromStuff && entry.stuffDef == null) { - if (entry.def.apparel != null) { - thing = ThingMaker.MakeThing(entry.def, ThingDef.Named("Synthread")); - } - else { - Log.Warning("Could not add item. Item is \"made from stuff\" but no material was specified and there is no known default."); - } - } - else { - thing = ThingMaker.MakeThing(entry.def, entry.stuffDef); - } - - if (thing != null) { - thing.stackCount = stackCount; - count -= stackCount; - - if (entry.def.weaponTags != null && entry.def.weaponTags.Count > 0) { - thing.SetFactionDirect(Faction.OfPlayer); - weapons.Add(thing); - } - else if (entry.def.apparel != null) { - apparel.Add(thing); - } - else if (entry.def.ingestible != null) { - food.Add(thing); - } - else { - other.Add(thing); - } - } - } - } - else if (entry.animal) { - int count = e.Count; - for (int i = 0; i < count; i++) { - Thing animal = CreateAnimal(entry); - if (animal != null) { - animals.Add(animal); - } - } - } - } - - List combined = new List(); - combined.AddRange(weapons); - combined.AddRange(food); - combined.AddRange(apparel); - combined.AddRange(animals); - combined.AddRange(other); - - int pod = 0; - foreach (Thing thing in combined) { - pods[pod].Add(thing); - if (++pod >= pawnCount) { - pod = 0; - } - } - - // EdB: Deploy the drop pods. - bool canInstaDropDuringInit = startedDirectInEditor; - DropPodUtility.DropThingGroupsNear(MapGenerator.PlayerStartSpot, pods, 110, canInstaDropDuringInit, true, false); - } - - private static Thing CreateAnimal(EquipmentDatabaseEntry entry) - { - ThingDef def = entry.def; - PawnKindDef kindDef = (from td in DefDatabase.AllDefs - where td.race == def - select td).FirstOrDefault(); - if (kindDef != null) { - Pawn pawn = PawnGenerator.GeneratePawn(kindDef, Faction.OfPlayer); - pawn.gender = entry.gender; - if (kindDef.RaceProps.petness > 0) { - if (pawn.Name == null || pawn.Name.Numerical) { - pawn.Name = NameGenerator.GeneratePawnName(pawn, NameStyle.Full, null); - Pawn pawn2 = PrepareCarefully.Instance.Colonists.RandomElement(); - pawn2.relations.AddDirectRelation(PawnRelationDefOf.Bond, pawn); - } - } - else { - pawn.Name = null; - } - return pawn; - } - else { - return null; - } - } - - public void AddImplantToPawn(Pawn pawn, Implant implant) - { - pawn.health.AddHediff(implant.recipe.addsHediff, implant.BodyPartRecord, new DamageInfo?()); - } - - // EdB: Copy of RimWold.Genstep_GenerateColonists.CreateInitialWorkSettings(), but with error - // messages removed. - private static void CreateInitialWorkSettings() - { - foreach (Pawn current in Find.GameInitData.startingPawns) { - current.workSettings.DisableAll(); - } - foreach (WorkTypeDef w in DefDatabase.AllDefs) { - if (w.alwaysStartActive) { - foreach (Pawn current2 in from col in Find.GameInitData.startingPawns - where !col.story.WorkTypeIsDisabled(w) - select col) { - current2.workSettings.SetPriority(w, 3); - } - } - else { - bool flag = false; - foreach (Pawn current3 in Find.GameInitData.startingPawns) { - if (!current3.story.WorkTypeIsDisabled(w) && current3.skills.AverageOfRelevantSkillsFor(w) >= 6) { - current3.workSettings.SetPriority(w, 3); - flag = true; - } - } - if (!flag) { - IEnumerable source = from col in Find.GameInitData.startingPawns - where !col.story.WorkTypeIsDisabled(w) - select col; - if (source.Any()) { - Pawn pawn = source.InRandomOrder(null).MaxBy((Pawn c) => c.skills.AverageOfRelevantSkillsFor(w)); - pawn.workSettings.SetPriority(w, 3); - } - else if (w.requireCapableColonist) { - // EdB: Show warning instead of an error. - //Log.Error("No colonist could do requireCapableColonist work type " + w); - Log.Warning("No colonist can do what is thought to be a required work type " + w.gerundLabel); - } - } - } - } - } - } -} - diff --git a/Source/Genstep_SpawnStartingResources.cs b/Source/Genstep_SpawnStartingResources.cs deleted file mode 100644 index 00bed6e..0000000 --- a/Source/Genstep_SpawnStartingResources.cs +++ /dev/null @@ -1,144 +0,0 @@ -using RimWorld; -using System; -using Verse; - -namespace EdB.PrepareCarefully -{ - // TODO: Alpha 14 - // This is the Alpha 13 map generation step that scatters custom resources on the map, near the - // starting location of the colonists. It is no longer needed but is left for reference. - public class Genstep_SpawnStartingResources : Genstep - { - public Genstep_SpawnStartingResources() - { - } - - public override void Generate() - { - if (PrepareCarefully.Instance.Active) { - GeneratePrepareCarefullyResources(); - } - else { - GenerateStandardResources(); - } - - } - - public void GeneratePrepareCarefullyResources() - { - foreach (var e in PrepareCarefully.Instance.Equipment) { - EquipmentDatabaseEntry entry = PrepareCarefully.Instance.EquipmentEntries[e.EquipmentKey]; - if (entry == null) { - string thing = e.def != null ? e.def.defName : "null"; - string stuff = e.stuffDef != null ? e.stuffDef.defName : "null"; - Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); - continue; - } - if (!entry.gear && !entry.animal && !entry.Minifiable) { - int stackSize = entry.def.stackLimit; - if (stackSize > 75) { - stackSize = 75; - } - if (entry.def == ThingDefOf.Component && e.Count <= 100) { - stackSize = 10; - } - int stacks = e.count / stackSize; - int remainder = e.count % stackSize; - //Log.Message("Scatter " + e.def.defName + ": " + stacks + " stacks of " + stackSize + " + " + remainder); - - // TODO: Alpha 14 - /* - new Genstep_ScatterThingGroups { - thingDefs = { - e.def - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(stacks, stacks), - stackCountRange = new IntRange(stackSize, stackSize), - countAtPlayerStart = 1 - }.Generate(); - if (remainder > 0) { - new Genstep_ScatterThingGroups { - thingDefs = { - e.def - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(1, 1), - stackCountRange = new IntRange(remainder, remainder), - countAtPlayerStart = 1 - }.Generate(); - } - */ - } - if (entry.Minifiable) { - for (int i = 0; i < e.Count; i++) { - /* - new Genstep_ScatterBuildings { - thingDefs = { - e.def - }, - stuffDef = e.stuffDef, - spotMustBeStandable = true, - // TODO: Alpha 14 - //countAtPlayerStart = 1 - }.Generate(); - */ - } - } - } - } - - public void GenerateStandardResources() - { - // TODO: Alpha 14 - /* - new Genstep_ScatterThingGroups { - thingDefs = { - ThingDefOf.Steel - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(6, 6), - stackCountRange = new IntRange(75, 75), - countAtPlayerStart = 1 - }.Generate(); - new Genstep_ScatterThingGroups { - thingDefs = { - ThingDefOf.WoodLog - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(6, 6), - stackCountRange = new IntRange(40, 60), - countAtPlayerStart = 1 - }.Generate(); - new Genstep_ScatterThingGroups { - thingDefs = { - ThingDefOf.Silver - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(5, 5), - stackCountRange = new IntRange(40, 60), - countAtPlayerStart = 2 - }.Generate(); - new Genstep_ScatterThingGroups { - thingDefs = { - ThingDefOf.Silver - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(3, 3), - stackCountRange = new IntRange(40, 60), - countAtPlayerStart = 2 - }.Generate(); - new Genstep_ScatterThingGroups { - thingDefs = { - ThingDefOf.Component - }, - spotMustBeStandable = true, - groupSizeRange = new IntRange(3, 3), - stackCountRange = new IntRange(10, 10), - countAtPlayerStart = 1 - }.Generate(); - */ - } - } -} - diff --git a/Source/RelationshipManager.cs b/Source/RelationshipManager.cs index c764937..e4896e1 100644 --- a/Source/RelationshipManager.cs +++ b/Source/RelationshipManager.cs @@ -61,7 +61,6 @@ protected void PopulateInverseRelationships() } if (inverse != null) { inverseRelationships[def] = inverse; - //Log.Message(def.defName + " is inverse of " + inverse.defName); } } } diff --git a/Source/Version3/PresetLoaderVersion3.cs b/Source/Version3/PresetLoaderVersion3.cs index e5dc126..f81dd01 100644 --- a/Source/Version3/PresetLoaderVersion3.cs +++ b/Source/Version3/PresetLoaderVersion3.cs @@ -379,9 +379,6 @@ public CustomPawn LoadPawn(SaveRecordPawnV3 record) pawn.ClearCachedAbilities(); pawn.ClearCachedLifeStage(); - //Log.Message("Loaded pawn: " + pawn.Name); - //Log.Message(" Market Value: \n " + StatDefOf.MarketValue.Worker.GetExplanation(StatRequest.For(pawn.Pawn), ToStringNumberSense.Absolute)); - return pawn; } From 6b03c3275ee73f5714b9f82f0cc095305bb60763 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 19:45:07 -0700 Subject: [PATCH 22/29] Cleaned up unneeded code for determining stack size and number of stacks. --- Source/Genstep_ScenParts.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Source/Genstep_ScenParts.cs b/Source/Genstep_ScenParts.cs index a6d98a5..df58ea9 100644 --- a/Source/Genstep_ScenParts.cs +++ b/Source/Genstep_ScenParts.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using UnityEngine; using Verse; namespace EdB.PrepareCarefully @@ -300,22 +301,11 @@ public void SpawnStartingResources() continue; } if (!entry.gear && !entry.animal) { - int stackSize = entry.def.stackLimit; - if (stackSize > 75) { - stackSize = 75; - } - if (entry.def == ThingDefOf.Component && e.Count <= 100) { - stackSize = 10; - } - int stacks = e.count / stackSize; - int remainder = e.count % stackSize; - //Log.Message("Scatter " + e.def.defName + ": " + stacks + " stacks of " + stackSize + " + " + remainder); - new Genstep_ScatterThings { nearPlayerStart = true, thingDef = e.def, stuff = e.stuffDef, - clusterSize = stackSize, + clusterSize = 4, count = e.Count, spotMustBeStandable = true, minSpacing = 5f From caad6ca53b56938b693e5c6125756b090d2cf754 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 19:49:54 -0700 Subject: [PATCH 23/29] Added a TODO comment to look more closely at the scatterer parameters. --- Source/Genstep_ScenParts.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Genstep_ScenParts.cs b/Source/Genstep_ScenParts.cs index df58ea9..ba17413 100644 --- a/Source/Genstep_ScenParts.cs +++ b/Source/Genstep_ScenParts.cs @@ -300,6 +300,10 @@ public void SpawnStartingResources() Log.Warning(string.Format("Unrecognized resource/equipment. This may be caused by an invalid thing/stuff combination. (thing = {0}, stuff={1})", thing, stuff)); continue; } + + // TODO: Look into what the clusterSize and minSpacing parameters do. If we are spawning an + // exceptionally large number of a given resource, would the numbers for those parameters + // need to be increased to allow the scatterer to find a place on the map? if (!entry.gear && !entry.animal) { new Genstep_ScatterThings { nearPlayerStart = true, From 5721785bc464197fd362b7535b52cddf95c1d932 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 20:05:26 -0700 Subject: [PATCH 24/29] Sort the apparel and hair lists by label --- .../Page_ConfigureStartingPawnsCarefully.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Source/Page_ConfigureStartingPawnsCarefully.cs b/Source/Page_ConfigureStartingPawnsCarefully.cs index 9328abb..2fb6383 100644 --- a/Source/Page_ConfigureStartingPawnsCarefully.cs +++ b/Source/Page_ConfigureStartingPawnsCarefully.cs @@ -156,6 +156,7 @@ public Page_ConfigureStartingPawnsCarefully() delegate { this.ChangePawnLayer(PawnLayers.Hat); }, }); + // Initialize and sort hair lists. foreach (HairDef hairDef in DefDatabase.AllDefs) { if (hairDef.hairGender != HairGender.Male) { femaleHairDefs.Add(hairDef); @@ -164,6 +165,28 @@ public Page_ConfigureStartingPawnsCarefully() maleHairDefs.Add(hairDef); } } + femaleHairDefs.Sort((HairDef x, HairDef y) => { + if (x.label == null) { + return -1; + } + else if (y.label == null) { + return 1; + } + else { + return x.label.CompareTo(y.label); + } + }); + femaleHairDefs.Sort((HairDef x, HairDef y) => { + if (x.label == null) { + return -1; + } + else if (y.label == null) { + return 1; + } + else { + return x.label.CompareTo(y.label); + } + }); for (int i = 0; i < PawnLayers.Count; i++) { if (PawnLayers.IsApparelLayer(i)) { @@ -221,6 +244,23 @@ public Page_ConfigureStartingPawnsCarefully() } } + // Sort the apparel lists + foreach (var list in apparelLists) { + if (list != null) { + list.Sort((ThingDef x, ThingDef y) => { + if (x.label == null) { + return -1; + } + else if (y.label == null) { + return 1; + } + else { + return x.label.CompareTo(y.label); + } + }); + } + } + // Iterate through each solid bio. Find the corresponding backstories. If the backstory description contains the bio's name // the mark the backstory as name-specific. If the bio does not has HE/HIS/etc, mark the backstory as gender-specific. From 2e6e39fd6289d42ac9ed48cfbc7304a978420085 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 21:51:09 -0700 Subject: [PATCH 25/29] Added support for the Accessory layer. Fixed apparel conflict logic. Fixed the way that apparel is added to the pawn. --- .../English/Keyed/EdBPrepareCarefully.xml | 3 +- Source/CustomPawn.cs | 48 ++++++++++++++----- .../Page_ConfigureStartingPawnsCarefully.cs | 14 +++++- Source/PawnLayers.cs | 17 +++++-- 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml index f46dc14..df3869e 100644 --- a/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml +++ b/Resources/Languages/English/Keyed/EdBPrepareCarefully.xml @@ -137,5 +137,6 @@ Prepare Carefully is a map generation mod. It appears that you have another mod enabled that also modifies the game's map generation and is overriding Prepare Carefully. Your colonist customizations will be ignored. - + Accessory + \ No newline at end of file diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index d38d0d7..6b4d6c4 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -144,6 +144,8 @@ public void InitializeWithPawn(Pawn pawn) colors.Add(Color.white); graphics.Add(null); colors.Add(Color.white); + graphics.Add(null); + colors.Add(Color.white); graphics.Add(GraphicDatabaseHeadRecords.GetHeadNamed(pawn.story.HeadGraphicPath, pawn.story.SkinColor)); colors.Add(pawn.story.SkinColor); @@ -671,28 +673,45 @@ public void SetSelectedStuff(int layer, ThingDef stuffDef) { } protected void ApparelAcceptanceTest() { + // Clear out any conflicts from a previous check. apparelConflicts.Clear(); + + // Assume that each peice of apparel will be accepted. for (int i = PawnLayers.TopClothingLayer; i >= PawnLayers.BottomClothingLayer; i--) { this.acceptedApparel[i] = this.selectedApparel[i]; } + // Go through each layer. for (int i = PawnLayers.TopClothingLayer; i >= PawnLayers.BottomClothingLayer; i--) { + + // If no apparel was selected for this layer, go to the next layer. if (selectedApparel[i] == null) { continue; } + ThingDef apparel = selectedApparel[i]; if (apparel.apparel != null && apparel.apparel.layers != null && apparel.apparel.layers.Count > 1) { foreach (ApparelLayer layer in apparel.apparel.layers) { + // If the apparel's layer matches the current layer, go to the apparel's next layer. if (layer == PawnLayers.ToApparelLayer(i)) { continue; } + + // If the apparel covers another layer as well as the current one, check to see + // if the user has selected another piece of apparel for that layer. If so, check + // to see if it covers any of the same body parts. If it does, it's a conflict. int disallowedLayer = PawnLayers.ToPawnLayerIndex(layer); if (this.selectedApparel[disallowedLayer] != null) { - ApparelConflict conflict = new ApparelConflict(); - conflict.def = selectedApparel[i]; - conflict.conflict = selectedApparel[disallowedLayer]; - apparelConflicts.Add(conflict); - this.acceptedApparel[disallowedLayer] = null; + foreach (var group in this.selectedApparel[disallowedLayer].apparel.bodyPartGroups) { + if (apparel.apparel.bodyPartGroups.Contains(group)) { + ApparelConflict conflict = new ApparelConflict(); + conflict.def = selectedApparel[i]; + conflict.conflict = selectedApparel[disallowedLayer]; + apparelConflicts.Add(conflict); + this.acceptedApparel[disallowedLayer] = null; + break; + } + } } } } @@ -1071,11 +1090,12 @@ public Pawn ConvertToPawn(bool resolveGraphics) { List apparel = (List)wornApparelField.GetValue(result.apparel); apparel.Clear(); - AddApparel(PawnLayers.Pants, apparel); - AddApparel(PawnLayers.BottomClothingLayer, apparel); - AddApparel(PawnLayers.MiddleClothingLayer, apparel); - AddApparel(PawnLayers.TopClothingLayer, apparel); - AddApparel(PawnLayers.Hat, apparel); + AddApparel(result, PawnLayers.Pants); + AddApparel(result, PawnLayers.BottomClothingLayer); + AddApparel(result, PawnLayers.MiddleClothingLayer); + AddApparel(result, PawnLayers.TopClothingLayer); + AddApparel(result, PawnLayers.Accessory); + AddApparel(result, PawnLayers.Hat); foreach (SkillRecord skill in result.skills.skills) { int value = this.GetSkillLevel(skill.def); @@ -1111,7 +1131,7 @@ public Pawn ConvertToPawn() return ConvertToPawn(true); } - public void AddApparel(int layer, List list) + public void AddApparel(Pawn targetPawn, int layer) { if (acceptedApparel[layer] != null) { Apparel a; @@ -1123,7 +1143,11 @@ public void AddApparel(int layer, List list) a = (Apparel)ThingMaker.MakeThing(selectedApparel[layer], null); a.DrawColor = colors[layer]; } - list.Add(a); + + PawnGenerator.PostProcessGeneratedGear(a, targetPawn); + if (ApparelUtility.HasPartsToWear(targetPawn, a.def)) { + targetPawn.apparel.Wear(a, false); + } } } diff --git a/Source/Page_ConfigureStartingPawnsCarefully.cs b/Source/Page_ConfigureStartingPawnsCarefully.cs index 2fb6383..dba0333 100644 --- a/Source/Page_ConfigureStartingPawnsCarefully.cs +++ b/Source/Page_ConfigureStartingPawnsCarefully.cs @@ -144,6 +144,7 @@ public Page_ConfigureStartingPawnsCarefully() PawnLayers.BottomClothingLayer, PawnLayers.MiddleClothingLayer, PawnLayers.TopClothingLayer, + PawnLayers.Accessory, PawnLayers.Hat }); pawnLayerActions = new List(new Action[] { @@ -153,7 +154,8 @@ public Page_ConfigureStartingPawnsCarefully() delegate { this.ChangePawnLayer(PawnLayers.BottomClothingLayer); }, delegate { this.ChangePawnLayer(PawnLayers.MiddleClothingLayer); }, delegate { this.ChangePawnLayer(PawnLayers.TopClothingLayer); }, - delegate { this.ChangePawnLayer(PawnLayers.Hat); }, + delegate { this.ChangePawnLayer(PawnLayers.Accessory); }, + delegate { this.ChangePawnLayer(PawnLayers.Hat); } }); // Initialize and sort hair lists. @@ -817,6 +819,15 @@ protected void DrawAppearance(CustomPawn customPawn) List list = new List(); int layerCount = this.pawnLayerActions.Count; for (int i = 0; i < layerCount; i++) { + int pawnLayer = pawnLayers[i]; + // Only add apparel layers that have items. + if (PawnLayers.IsApparelLayer(pawnLayer)) { + if (pawnLayer == PawnLayers.Accessory) { + if (apparelLists[pawnLayer] == null || apparelLists[pawnLayer].Count == 0) { + continue; + } + } + } label = PawnLayers.Label(pawnLayers[i]); list.Add(new FloatMenuOption(label, this.pawnLayerActions[i], MenuOptionPriority.Medium, null, null, 0, null)); } @@ -1268,6 +1279,7 @@ protected void DrawPawn(Rect rect) Rect headRect = new Rect(bodyRect.x, bodyRect.y - 30, 128, 128); List graphics = customPawn.graphics; DrawGraphics(bodyRect, PawnLayers.BodyType, PawnLayers.TopClothingLayer); + DrawGraphic(bodyRect, PawnLayers.Accessory); DrawGraphic(headRect, PawnLayers.HeadType); DrawOneGraphic(headRect, PawnLayers.Hat, PawnLayers.Hair); diff --git a/Source/PawnLayers.cs b/Source/PawnLayers.cs index 7d01750..c19b646 100644 --- a/Source/PawnLayers.cs +++ b/Source/PawnLayers.cs @@ -11,9 +11,10 @@ public class PawnLayers public const int Pants = 2; public const int MiddleClothingLayer = 3; public const int TopClothingLayer = 4; - public const int HeadType = 5; - public const int Hair = 6; - public const int Hat = 7; + public const int Accessory = 5; + public const int HeadType = 6; + public const int Hair = 7; + public const int Hat = 8; public const int Count = Hat + 1; @@ -35,6 +36,8 @@ public static String Label(int layer) { return "EdB.PawnLayer.Hair".Translate(); case Hat: return "EdB.PawnLayer.Hat".Translate(); + case Accessory: + return "EdB.PawnLayer.Accessory".Translate(); default: return ""; } @@ -50,6 +53,8 @@ public static int ToPawnLayerIndex(ApparelLayer layer) { return TopClothingLayer; case ApparelLayer.Overhead: return Hat; + case ApparelLayer.Accessory: + return Accessory; default: return -1; } @@ -68,6 +73,8 @@ public static int ToPawnLayerIndex(ApparelProperties apparelProperties) { return MiddleClothingLayer; case ApparelLayer.Shell: return TopClothingLayer; + case ApparelLayer.Accessory: + return Accessory; case ApparelLayer.Overhead: return Hat; default: @@ -91,6 +98,8 @@ public static ApparelLayer ToApparelLayer(int layer) { return ApparelLayer.Shell; case Hat: return ApparelLayer.Overhead; + case Accessory: + return ApparelLayer.Accessory; default: return ApparelLayer.OnSkin; } @@ -114,6 +123,8 @@ public static bool IsApparelLayer(int layer) { return false; case Hat: return true; + case Accessory: + return true; default: return false; } From 3698acfdf610aa1fb97d1d08feae0dedfcdb7c12 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 21:57:53 -0700 Subject: [PATCH 26/29] Removed special-case exception for the personal shield item --- Source/Page_ConfigureStartingPawnsCarefully.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Page_ConfigureStartingPawnsCarefully.cs b/Source/Page_ConfigureStartingPawnsCarefully.cs index dba0333..54c609c 100644 --- a/Source/Page_ConfigureStartingPawnsCarefully.cs +++ b/Source/Page_ConfigureStartingPawnsCarefully.cs @@ -201,7 +201,7 @@ public Page_ConfigureStartingPawnsCarefully() // Get all apparel options foreach (ThingDef apparelDef in DefDatabase.AllDefs) { - if (apparelDef.apparel == null || apparelDef.defName == "Apparel_PersonalShield") { + if (apparelDef.apparel == null) { continue; } int layer = PawnLayers.ToPawnLayerIndex(apparelDef.apparel); From 9cbd20480f3a9f92c345c0492bdc0b718d515324 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 22:07:50 -0700 Subject: [PATCH 27/29] Moved the accessory index to be the last pawn layer to avoid breaking preset backwards compatibility --- Source/CustomPawn.cs | 4 ++-- Source/PawnLayers.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/CustomPawn.cs b/Source/CustomPawn.cs index 6b4d6c4..5029b89 100644 --- a/Source/CustomPawn.cs +++ b/Source/CustomPawn.cs @@ -144,8 +144,6 @@ public void InitializeWithPawn(Pawn pawn) colors.Add(Color.white); graphics.Add(null); colors.Add(Color.white); - graphics.Add(null); - colors.Add(Color.white); graphics.Add(GraphicDatabaseHeadRecords.GetHeadNamed(pawn.story.HeadGraphicPath, pawn.story.SkinColor)); colors.Add(pawn.story.SkinColor); @@ -154,6 +152,8 @@ public void InitializeWithPawn(Pawn pawn) graphics.Add(GraphicsCache.Instance.GetHair(pawn.story.hairDef)); colors.Add(pawn.story.hairColor); + graphics.Add(null); + colors.Add(Color.white); graphics.Add(null); colors.Add(Color.white); diff --git a/Source/PawnLayers.cs b/Source/PawnLayers.cs index c19b646..33b6751 100644 --- a/Source/PawnLayers.cs +++ b/Source/PawnLayers.cs @@ -11,12 +11,12 @@ public class PawnLayers public const int Pants = 2; public const int MiddleClothingLayer = 3; public const int TopClothingLayer = 4; - public const int Accessory = 5; - public const int HeadType = 6; - public const int Hair = 7; - public const int Hat = 8; + public const int HeadType = 5; + public const int Hair = 6; + public const int Hat = 7; + public const int Accessory = 8; - public const int Count = Hat + 1; + public const int Count = Accessory + 1; public static String Label(int layer) { switch (layer) { From a6c97203ada269e2908754b7e24dea4f71eae407 Mon Sep 17 00:00:00 2001 From: edbmods Date: Sat, 30 Jul 2016 22:17:05 -0700 Subject: [PATCH 28/29] Updated version number for 0.14.1.1. Also removed a couple of deleted files from the project that we forgot to remove in a previous commit. --- EdBPrepareCarefully.csproj | 4 +--- EdBPrepareCarefully.sln | 6 +++--- Properties/AssemblyInfo.cs | 2 +- Resources/About/About.xml | 2 +- Resources/CHANGELOG.txt | 5 +++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/EdBPrepareCarefully.csproj b/EdBPrepareCarefully.csproj index 013b65a..a3c7d59 100644 --- a/EdBPrepareCarefully.csproj +++ b/EdBPrepareCarefully.csproj @@ -7,7 +7,7 @@ Library EdB.PrepareCarefully EdBPrepareCarefully - 0.14.0.5 + 0.14.1.1 False v3.5 ScenPart_PlayerPawnsArriveMethodCarefully.manifest @@ -76,10 +76,8 @@ - - diff --git a/EdBPrepareCarefully.sln b/EdBPrepareCarefully.sln index 5a1ef6a..185eeec 100644 --- a/EdBPrepareCarefully.sln +++ b/EdBPrepareCarefully.sln @@ -23,9 +23,9 @@ Global $2.FileWidth = 120 $2.TabsToSpaces = False $2.EolMarker = Unix - $2.inheritsSet = VisualStudio + $2.inheritsSet = null $2.inheritsScope = text/plain - $2.scope = text/plain + $2.scope = application/xml $0.CSharpFormattingPolicy = $3 $3.IndentSwitchBody = True $3.ElseNewLinePlacement = NewLine @@ -195,6 +195,6 @@ Global $29.IncludeStaticEntities = True $0.VersionControlPolicy = $31 $31.inheritsSet = Mono - version = 0.14.0.5 + version = 0.14.1.1 EndGlobalSection EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 836ceeb..e3fffe5 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("0.14.0.5")] +[assembly: AssemblyVersion("0.14.1.1")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Resources/About/About.xml b/Resources/About/About.xml index 82b870d..2bfa7a6 100644 --- a/Resources/About/About.xml +++ b/Resources/About/About.xml @@ -8,6 +8,6 @@ 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 0.14.0.5] +[Version 0.14.1.1] \ No newline at end of file diff --git a/Resources/CHANGELOG.txt b/Resources/CHANGELOG.txt index b754df7..1f3cd5d 100644 --- a/Resources/CHANGELOG.txt +++ b/Resources/CHANGELOG.txt @@ -1,10 +1,11 @@ _____________________________________________________________________________ - Version 0.14.0.5 (2016-07-28) + Version 0.14.1.1 (2016-07-31) _____________________________________________________________________________ - Added support for Alpha 14. - - Removed point system. + - Removed the point system. + - Added support for the Accessory layer in the apparel selection UI. _____________________________________________________________________________ From 76e2d62148ea2ed0da367c207e033fd1a675c25e Mon Sep 17 00:00:00 2001 From: edbmods Date: Sun, 31 Jul 2016 08:16:17 -0700 Subject: [PATCH 29/29] Prevented presets from re-enabling points. Removed inadvertent color tint from preset buttons --- Source/Page_PrepareCarefully.cs | 1 + Source/Version3/PresetLoaderVersion3.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Page_PrepareCarefully.cs b/Source/Page_PrepareCarefully.cs index d08a9fb..ec6eecc 100644 --- a/Source/Page_PrepareCarefully.cs +++ b/Source/Page_PrepareCarefully.cs @@ -109,6 +109,7 @@ protected void DrawCost(Rect parentRect) protected void DrawPresetButtons() { + GUI.color = Color.white; float middle = 982f / 2f; float buttonWidth = 150; float buttonSpacing = 24; diff --git a/Source/Version3/PresetLoaderVersion3.cs b/Source/Version3/PresetLoaderVersion3.cs index f81dd01..ad407d5 100644 --- a/Source/Version3/PresetLoaderVersion3.cs +++ b/Source/Version3/PresetLoaderVersion3.cs @@ -115,7 +115,7 @@ public bool Load(PrepareCarefully loadout, string presetName) HashSet saveables = (HashSet) (typeof(PostLoadInitter).GetField("saveablesToPostLoad", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null)); saveables.Clear(); - PrepareCarefully.Instance.Config.pointsEnabled = usePoints; + //PrepareCarefully.Instance.Config.pointsEnabled = usePoints; } catch (Exception e) { Log.Error("Failed to load preset file");