Esempio n. 1
0
        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;
        }
Esempio n. 2
0
        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;
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        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();
            }
        }
Esempio n. 5
0
        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;
        }
Esempio n. 6
0
        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;
        }
Esempio n. 7
0
        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;
        }
Esempio n. 8
0
        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;
        }
Esempio n. 9
0
 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;
     }
 }