private void SetRelationshipMatchingGender(CharacterNote partner, CharacterRelationshipOption relationship) { // If the relationship is orientation-specific, pick an appropriate gender. bool choseGender = false; if (relationship.RequiresOrientationMatch) { NoteOption orientationOptions = partner.Traits.FindOption("Personality\\Orientation"); CharacterOrientationOption partnerOrientation = orientationOptions.LowestCheckedChild as CharacterOrientationOption; choseGender = SetOrientationMatchedGender(partner, partnerOrientation); // A choice must only be forced if the partner has an orientation, and it is gender-specific. if (partnerOrientation != null && !partnerOrientation.IncludesAny) { // Ensure gender options are up to date. if (RootSaveFile?.Template?.CharacterTemplate?.Genders != null) Genders.MergeWithOption(RootSaveFile.Template.CharacterTemplate.Genders, false); List<CharacterGenderOption> genders = new List<CharacterGenderOption>(); if (partnerOrientation.IncludesSimilarToOpposite) genders.AddRange(Genders.GetArchetypeClone(partner.EffectiveGender.Opposite).ChildOptions.Cast<CharacterGenderOption>()); else if (partnerOrientation.IncludesOpposite) genders.AddRange(Genders.FindOptionsByName(new List<NoteOption>(), partner.EffectiveGender.Opposite).Cast<CharacterGenderOption>()); if (partnerOrientation.IncludesSimilar) genders.AddRange(Genders.GetArchetypeClone(partner.EffectiveGender.Archetype).ChildOptions.Cast<CharacterGenderOption>()); else if (partnerOrientation.IncludesSame) genders.AddRange(Genders.FindOptionsByName(new List<NoteOption>(), partner.EffectiveGender.Archetype).Cast<CharacterGenderOption>()); if (genders.Count > 0) { // Use a dummy NoteOption so that the weighted choice algorithms may be leveraged. NoteOption genderOptions = new NoteOption() { RootIdea = this, ChildOptions = new ObservableCollection<NoteOption>(genders), IsChoice = true }; genderOptions.Choose(); Genders.FindOption(genderOptions.LowestCheckedChild.Path).IsChecked = true; choseGender = true; } } } // Otherwise, choose one at random. if (!choseGender) ChooseGender(); foreach (CharacterRelationshipOption childRelationship in relationship.ChildOptions) { if (childRelationship.Genders.Any(g => g == EffectiveGender.Archetype)) { Relationship = childRelationship; return; } } return; }
private bool SetOrientationMatchedGender(CharacterNote partner, CharacterOrientationOption partnerOrientation) { CharacterGenderOption partnerGender = partner.EffectiveGender; if (partnerGender == null) return false; List<CharacterGenderOption> candidateGenders = new List<CharacterGenderOption>(); if (partnerOrientation.IncludesSame) candidateGenders.AddRange(Genders.FindOptionsByName(new List<NoteOption>(), partnerGender.Archetype).Cast<CharacterGenderOption>()); if (partnerOrientation.IncludesSimilar) candidateGenders.AddRange(Genders.ChildOptions.Cast<CharacterGenderOption>().Where(g => g.Archetype == partnerGender.Archetype)); if (partnerOrientation.IncludesOpposite && partnerGender.Opposite != null) candidateGenders.AddRange(Genders.FindOptionsByName(new List<NoteOption>(), partnerGender.Opposite).Cast<CharacterGenderOption>()); if (partnerOrientation.IncludesSimilarToOpposite && partnerGender.Opposite != null) candidateGenders.AddRange(Genders.ChildOptions.Cast<CharacterGenderOption>().Where(g => g.Archetype == partnerGender.Opposite)); if (candidateGenders.Count == 0) return false; // Use a dummy NoteOption so that the weighted choice algorithms may be leveraged. NoteOption genderOptions = new NoteOption() { RootIdea = this, ChildOptions = new ObservableCollection<NoteOption>(candidateGenders), IsChoice = true }; genderOptions.Choose(); return true; }
/// <summary> /// Allows generating a parent for this character as if generating a spouse for one of their existing parents. /// This allows generating correct gender and orientation matching for between the 'spouses.' /// </summary> /// <param name="spouse">The character to be used as the hypothetical spouse.</param> private void GenerateParentAsSpouse(CharacterNote spouse) { if (RootSaveFile?.Template?.CharacterTemplate?.Relationships == null) return; CharacterRelationshipOption relationship = RootSaveFile.Template.CharacterTemplate.Relationships.FindOption("Parent") as CharacterRelationshipOption; CharacterRelationshipOption spouseRelationship = RootSaveFile.Template.CharacterTemplate.Relationships.FindOption("Significant Other\\Spouse") as CharacterRelationshipOption; if (relationship == null || spouseRelationship == null) return; CharacterNote newNote = new CharacterNote(); AddChild(newNote); newNote.Relationship = relationship; int? spouseMinAge = spouseRelationship.MinAge; if (spouseRelationship.MinAgeOffset.HasValue) spouseMinAge = Math.Max(spouseMinAge ?? int.MinValue, spouse.AgeYears + spouseRelationship.MinAgeOffset.Value); int? spouseMaxAge = spouseRelationship.MaxAge; if (spouseRelationship.MaxAgeOffset.HasValue) spouseMaxAge = Math.Min(spouseMaxAge ?? int.MaxValue, spouse.AgeYears + spouseRelationship.MaxAgeOffset.Value); if (!newNote.ChooseAge(false, spouseMinAge, spouseMaxAge)) // False return means no valid age range exists for the relationship (min > max). { Ideas.Remove(newNote); return; } if (!spouseRelationship.RequiresOrientationMatch || !newNote.SetSignificantOtherGender(spouse, spouseRelationship)) newNote.ChooseGender(); newNote.SetInheritedRaces(); newNote.AssignFamilySurname(); newNote.AssignFamilyFirstName(); if (RootSaveFile?.Template?.CharacterTemplate?.Traits != null) newNote.Traits.MergeWithOption(RootSaveFile.Template.CharacterTemplate.Traits, true); newNote.Traits.Choose(); // There is a chance that a character of any orientation will be in a // heterosexual marriage (or ex-marriage), due to societal pressure. // Otherwise, the new character's orientation will be adjusted, if necessary, to // fit the relationship. if (spouseRelationship.RequiresOrientationMatch && (random.Next(101) > chanceOfForcedHeteroMarriage || spouse.EffectiveGender?.Archetype != newNote.EffectiveGender?.Opposite)) newNote.ReconcileOrientation(spouse, spouseRelationship); }
private void ReconcileOrientation(CharacterNote partner, CharacterRelationshipOption relationship) { NoteOption orientationOptions = Traits.FindOption("Personality\\Orientation"); if (orientationOptions == null) return; // No adjustment necessary/possible without any orientation traits. CharacterOrientationOption orientation = orientationOptions.LowestCheckedChild as CharacterOrientationOption; if (orientation == null || orientation.IncludesAny == true) return; // No adjustment necessary; any gender is accepted. CharacterGenderOption gender = EffectiveGender; CharacterGenderOption partnerGender = partner.EffectiveGender; if (gender == null || partnerGender == null) return; // No adjustment possible without gender information. List<NoteOption> candidateOrientations = new List<NoteOption>(); if (gender.Opposite == partnerGender.Archetype && !orientation.IncludesOpposite && !orientation.IncludesSimilarToOpposite) candidateOrientations.AddRange(orientationOptions.ChildOptions.Cast<CharacterOrientationOption>().Where(o => o.IncludesOpposite || o.IncludesSimilarToOpposite || o.IncludesAny)); else if (gender.Archetype == partnerGender.Archetype && !orientation.IncludesSame && !orientation.IncludesSimilar) candidateOrientations.AddRange(orientationOptions.ChildOptions.Cast<CharacterOrientationOption>().Where(o => o.IncludesSame || o.IncludesSimilar || o.IncludesAny)); else if (gender.Opposite == partnerGender.Name && !orientation.IncludesOpposite) candidateOrientations.AddRange(orientationOptions.ChildOptions.Cast<CharacterOrientationOption>().Where(o => o.IncludesOpposite || o.IncludesAny)); else if (gender.Archetype == partnerGender.Name && !orientation.IncludesSame) candidateOrientations.AddRange(orientationOptions.ChildOptions.Cast<CharacterOrientationOption>().Where(o => o.IncludesSame || o.IncludesAny)); if (candidateOrientations.Count > 0) { // Use a dummy NoteOption so that the weighted choice algorithms may be leveraged. NoteOption options = new NoteOption() { RootIdea = this, ChildOptions = new ObservableCollection<NoteOption>(candidateOrientations), IsChoice = true }; orientationOptions.DeselectAllChildren(); options.Choose(); } }
private List<CharacterNote> GenerateChildren(bool woman, int minChildAge, int maxChildAge, bool married, bool divorced, bool siblings, int numAlready, bool manual) { List<CharacterNote> children = new List<CharacterNote>(); CharacterRelationshipOption relationship = RootSaveFile.Template.CharacterTemplate.Relationships.FindOption(siblings ? "Sibling" : "Child") as CharacterRelationshipOption; if (relationship == null) return children; // Constrain the age bounds. If there are no valid ages, stop now. minChildAge = Math.Max(0, minChildAge); maxChildAge = Math.Min(maxAge, maxChildAge); if (minChildAge > maxChildAge) return children; // If there are already some children, and this isn't manual selection of more, decide whether to generate more before proceeding. if (!manual) { if (numAlready >= relationship.Max) return children; else if (numAlready == 1) { if (random.Next(4) == 0) return children; } else if (numAlready > 1 && random.Next(1, (2 * (numAlready - 1)) + 1) != 1) return children; } // Decide whether to stop now based on the character's situation. // If the user is manually adding children, don't make these determinations. // If determining siblings there's at least 1 child already (the character), so these checks can be skipped. if (!manual && !siblings && !ShouldHaveChildren(relationship, woman, married, divorced)) return children; int childAge; bool first = true; int numTwins = 0; int numChildren = numAlready; while (maxChildAge >= 0 && maxChildAge >= minChildAge) { childAge = random.Next(minChildAge, maxChildAge + 1); if (!first) { if (childAge == maxChildAge) { // First time, 1/4 chance of allowing twins; second, 1/8; third, 1/16; &c. // Triplets, quadruplets, &c have the same increasingly small chance of being allowed. if (random.Next(4 * (numTwins + 1)) != 0) { maxChildAge--; continue; } else numTwins++; } // There's a 50% chance of adjusting a 1-year separation to 2, to simulate the relative // rarity of such closely-spaced children. else if (childAge == maxChildAge - 1 && random.Next(2) == 0) { childAge--; if (childAge < 0) return children; } } CharacterNote newNote = new CharacterNote(); AddChild(newNote); // Pick a child relationship (gender-specific variant) relationship.DeselectAllChildren(); relationship.Choose(); newNote.Relationship = relationship.LowestCheckedChild as CharacterRelationshipOption; newNote.AgeYears = childAge; if (newNote.AgeYears == 0) newNote.AgeMonths = (byte)random.Next(13); newNote.ChooseGender(); newNote.SetInheritedRaces(); newNote.AssignFamilySurname(); newNote.AssignFamilyFirstName(); if (RootSaveFile?.Template?.CharacterTemplate?.Traits != null) newNote.Traits.MergeWithOption(RootSaveFile.Template.CharacterTemplate.Traits, true); newNote.Traits.Choose(); children.Add(newNote); first = false; maxChildAge = childAge; numChildren++; if (numChildren >= relationship.Max) return children; // The chances of having multiple kids follows a different progression than the standard for other relationships: // Once a couple has a single child, there is a 3 in 4 chance that they will have a second. else if (numChildren == 1) { if (random.Next(1, 101) <= 25) return children; } // After two, the chances of having additional children drop off rapidly, // starting at 1 in 2 for a third, then 1 in 4 for a fourth, 1 in 6 for a fifth, etc. else if (random.Next(1, (2 * (numChildren - 1)) + 1) != 1) return children; } return children; }
public CharacterNote AddNewFamilyMember(CharacterRelationshipOption relationship, bool manual) { if (relationship == null) return null; CharacterNote newNote = new CharacterNote(); AddChild(newNote); // First see if the relationship specified is a gender-neutral option with gender-specific child options. // If so, choose a gender first, then select the appropriate child relationship. bool genderChosen = false; if (relationship.Genders?.Count == 0 && relationship.ChildOptions.Cast<CharacterRelationshipOption>().Count(c => c.Genders?.Count > 0) > 1) { newNote.SetRelationshipMatchingGender(this, relationship); genderChosen = true; } // If no gender-specific options were selected, the choice will // fall back on the gender-neutral original below. if (newNote.Relationship == null) newNote.Relationship = relationship; // False return means no valid age range exists for the relationship (min > max). // Ignored when adding manually. if (!newNote.ChooseAge() && !manual) { Ideas.Remove(newNote); return null; } if (!genderChosen) newNote.ChooseGender(); newNote.SetInheritedRaces(); newNote.AssignFamilySurname(); newNote.AssignFamilyFirstName(); if (RootSaveFile?.Template?.CharacterTemplate?.Traits != null) newNote.Traits.MergeWithOption(RootSaveFile.Template.CharacterTemplate.Traits, true); newNote.Traits.Choose(); if (newNote.Relationship.RequiresOrientationMatch) { newNote.ReconcileOrientation(this, newNote.Relationship); CharacterRelationshipOption reciprocalRelationship = RootSaveFile.Template.CharacterTemplate.Relationships.FindOption(newNote.Relationship.ReciprocalRelationship) as CharacterRelationshipOption; ReconcileOrientation(newNote, reciprocalRelationship); } return newNote; }
private bool GenerateSignificantOther(CharacterRelationshipOption relationship, int numAlready) { if (relationship.IsInPath("Spouse")) { if (!CheckForSpouse(relationship, numAlready)) return false; } else if (relationship.IsInPath("Ex-Spouse")) { if (!CheckForExSpouse(relationship, numAlready)) return false; } else if (relationship.IsInPath("Lover")) { if (!CheckForLover(relationship, numAlready)) return false; } else if (!CheckForRelationship(relationship, numAlready)) return false; CharacterNote newNote = new CharacterNote(); AddChild(newNote); newNote.Relationship = relationship; if (!relationship.RequiresOrientationMatch || !newNote.SetSignificantOtherGender(this, relationship)) newNote.ChooseGender(); int? minimum, maximum; GetSignificantOtherAgeRange(newNote.EffectiveGender, out minimum, out maximum); if (!newNote.ChooseAge(false, minimum, maximum)) // False return means no valid age range exists for the relationship (min > max). { Ideas.Remove(newNote); return false; } newNote.SetInheritedRaces(); newNote.AssignFamilySurname(); newNote.AssignFamilyFirstName(); if (RootSaveFile?.Template?.CharacterTemplate?.Traits != null) newNote.Traits.MergeWithOption(RootSaveFile.Template.CharacterTemplate.Traits, true); newNote.Traits.Choose(); // There is a chance that a character of any orientation will be in a // heterosexual marriage (or ex-marriage), due to societal pressure. // Otherwise, the new character's orientation will be adjusted, if necessary, to // fit the relationship. if (relationship.RequiresOrientationMatch && (!relationship.Path.Contains("Spouse") || random.Next(101) > chanceOfForcedHeteroMarriage || EffectiveGender?.Archetype != newNote.EffectiveGender?.Opposite)) newNote.ReconcileOrientation(this, newNote.Relationship); return true; }
private bool SetSignificantOtherGender(CharacterNote partner, CharacterRelationshipOption relationship) { // Ensure gender options are up to date. if (RootSaveFile?.Template?.CharacterTemplate?.Genders != null) Genders.MergeWithOption(RootSaveFile.Template.CharacterTemplate.Genders, false); // There is a chance that any character, regardless of actual orientation, will be in // a hetero marriage (or ex-marriage), due to the pressure of social conventions. if (relationship.Path.Contains("Spouse") && random.Next(101) <= chanceOfForcedHeteroMarriage) { CharacterGenderOption gender = Genders.FindOptionsByName(new List<NoteOption>(), partner.EffectiveGender?.Opposite).FirstOrDefault() as CharacterGenderOption; if (gender != null) { gender.IsChecked = true; return true; } } // Otherwise, select a gender that corresponds to the character's orientation. NoteOption orientationOption = partner.Traits.FindOption("Personality\\Orientation"); CharacterOrientationOption partnerOrientation = orientationOption.LowestCheckedChild as CharacterOrientationOption; if (partnerOrientation != null) { // When selecting a lover, a character whose orientation includes multiple genders // has a high chance that their extra-marital relationship will be with someone of // a gender not represented in their marriage. if (relationship.IsInPath("Lover") && partnerOrientation.IncludesMultiple && random.Next(101) <= chanceOfAlternateGenderLoverForBisexual) { IEnumerable<CharacterGenderOption> spouseGenders = partner.GetSpouses().Select(s => s.EffectiveGender); List<CharacterGenderOption> candidateGenders = Genders.ChildOptions.Cast<CharacterGenderOption>().Where(g => !spouseGenders.Any(s => s.Archetype == g.Archetype)).ToList(); if (candidateGenders.Count > 0) { // Use a dummy NoteOption so that the weighted choice algorithms may be leveraged. NoteOption genderOptions = new NoteOption() { RootIdea = this, ChildOptions = new ObservableCollection<NoteOption>(candidateGenders), IsChoice = true }; genderOptions.Choose(); return true; } } return SetOrientationMatchedGender(partner, partnerOrientation); } return false; }
private void AddOrChangeRelationship(CharacterNote note) { RelationshipDialog relationshipDialog = new RelationshipDialog(typeof(CharacterRelationshipOption)); relationshipDialog.ItemsSource = Template.CharacterTemplate.Relationships.ChildOptions; relationshipDialog.IsReadOnly = true; relationshipDialog.CollectionControl.PropertyGrid.AutoGenerateProperties = false; relationshipDialog.CollectionControl.PropertyGrid.PropertyDefinitions = new Xceed.Wpf.Toolkit.PropertyGrid.PropertyDefinitionCollection() { new Xceed.Wpf.Toolkit.PropertyGrid.PropertyDefinition() { TargetProperties = new List<string>() { nameof(NoteOption.Name) } }, new Xceed.Wpf.Toolkit.PropertyGrid.PropertyDefinition() { TargetProperties = new List<string>() { nameof(NoteOption.IsChoice) } } }; if (relationshipDialog.ShowDialog() == true) { if (relationshipDialog.UseCustom && !string.IsNullOrWhiteSpace(relationshipDialog.CustomRelationshipName)) note.Relationship = new CharacterRelationshipOption(relationshipDialog.CustomRelationshipName); else note.Relationship = relationshipDialog.CollectionControl.SelectedItem as CharacterRelationshipOption; } }