diff --git a/CHANGELOG.md b/CHANGELOG.md index 71cafbf..800221c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v1.6 — 2024-06-10 +Tested to work on game version 207.67 + +* Fix off-by-one error in pronoun selection + ## v1.5 — 2023-08-15 Tested to work on game versions 204.100 / 205.54 (beta) diff --git a/NamePronoun.cs b/NamePronoun.cs deleted file mode 100644 index efc03f8..0000000 --- a/NamePronoun.cs +++ /dev/null @@ -1,163 +0,0 @@ -using HarmonyLib; -using XRL.World; -using XRL.Language; -using ConsoleLib.Console; - -namespace QudGendersUnleashed.NamePronoun -{ - /// - /// Wraps the returned by with a . - /// - [HarmonyPatch(typeof(GameObject))] - [HarmonyPatch(nameof(GameObject.GetPronounProvider))] - public static class NameOnlyPronounPatch - { - static IPronounProvider Postfix(IPronounProvider pronouns, GameObject __instance) - => NamePronounWrapper.Wrap(pronouns, __instance); - } - - /// - /// Wraps a , replacing =name=/=name's= with the name of the pronouns' referent. - /// - public class NamePronounWrapper : IPronounProvider - { - private IPronounProvider BasePronouns; - private GameObject Referent; - - private static bool CouldHaveName(string s) => s.Contains("=name"); - - public static bool CouldBeNamePronouns(IPronounProvider pronouns) => - CouldHaveName(pronouns.Name) - || CouldHaveName(pronouns.CapitalizedName) - || CouldHaveName(pronouns.Subjective) - || CouldHaveName(pronouns.CapitalizedSubjective) - || CouldHaveName(pronouns.Objective) - || CouldHaveName(pronouns.CapitalizedObjective) - || CouldHaveName(pronouns.PossessiveAdjective) - || CouldHaveName(pronouns.CapitalizedPossessiveAdjective) - || CouldHaveName(pronouns.SubstantivePossessive) - || CouldHaveName(pronouns.CapitalizedSubstantivePossessive) - || CouldHaveName(pronouns.Reflexive) - || CouldHaveName(pronouns.CapitalizedReflexive) - || CouldHaveName(pronouns.PersonTerm) - || CouldHaveName(pronouns.CapitalizedPersonTerm) - || CouldHaveName(pronouns.ImmaturePersonTerm) - || CouldHaveName(pronouns.CapitalizedImmaturePersonTerm) - || CouldHaveName(pronouns.FormalAddressTerm) - || CouldHaveName(pronouns.CapitalizedFormalAddressTerm) - || CouldHaveName(pronouns.OffspringTerm) - || CouldHaveName(pronouns.CapitalizedOffspringTerm) - || CouldHaveName(pronouns.SiblingTerm) - || CouldHaveName(pronouns.CapitalizedSiblingTerm) - || CouldHaveName(pronouns.ParentTerm) - || CouldHaveName(pronouns.CapitalizedParentTerm) - || CouldHaveName(pronouns.IndicativeProximal) - || CouldHaveName(pronouns.CapitalizedIndicativeProximal) - || CouldHaveName(pronouns.IndicativeDistal) - || CouldHaveName(pronouns.CapitalizedIndicativeDistal); - - public static IPronounProvider Wrap(IPronounProvider basePronouns, GameObject referent) - { - if (basePronouns is NamePronounWrapper p && p.Referent != referent) - { - p.Referent = referent; - } - else if (CouldBeNamePronouns(basePronouns)) - { - return new NamePronounWrapper(basePronouns, referent); - } - - return basePronouns; - } - - public NamePronounWrapper(IPronounProvider basePronouns, GameObject referent) - { - this.BasePronouns = basePronouns; - this.Referent = referent; - } - - public string ReplaceWithName(string pronoun, bool capitalize = false) - { - if (pronoun.Contains("=name")) - { - string displayName = Referent.BaseDisplayNameStripped; - if (capitalize) { - displayName = ColorUtility.CapitalizeExceptFormatting(displayName); - } - string displayNamePossessive = Grammar.MakePossessive(displayName); - return pronoun.Replace("=name=", displayName) - .Replace("=name's=", displayNamePossessive); - } - else - { - return pronoun; - } - } - - public string Name => ReplaceWithName(BasePronouns.Name); - - public string CapitalizedName => ReplaceWithName(BasePronouns.CapitalizedName, true); - - public bool Generic => BasePronouns.Generic; - - public bool Generated => BasePronouns.Generated; - - public bool Plural => BasePronouns.Plural; - - public bool PseudoPlural => BasePronouns.PseudoPlural; - - public string Subjective => ReplaceWithName(BasePronouns.Subjective); - - public string CapitalizedSubjective => ReplaceWithName(BasePronouns.CapitalizedSubjective, true); - - public string Objective => ReplaceWithName(BasePronouns.Objective); - - public string CapitalizedObjective => ReplaceWithName(BasePronouns.CapitalizedObjective, true); - - public string PossessiveAdjective => ReplaceWithName(BasePronouns.PossessiveAdjective); - - public string CapitalizedPossessiveAdjective => ReplaceWithName(BasePronouns.CapitalizedPossessiveAdjective, true); - - public string SubstantivePossessive => ReplaceWithName(BasePronouns.SubstantivePossessive); - - public string CapitalizedSubstantivePossessive => ReplaceWithName(BasePronouns.CapitalizedSubstantivePossessive); - - public string Reflexive => ReplaceWithName(BasePronouns.Reflexive); - - public string CapitalizedReflexive => ReplaceWithName(BasePronouns.CapitalizedReflexive, true); - - public string PersonTerm => ReplaceWithName(BasePronouns.PersonTerm); - - public string CapitalizedPersonTerm => ReplaceWithName(BasePronouns.CapitalizedPersonTerm, true); - - public string ImmaturePersonTerm => ReplaceWithName(BasePronouns.ImmaturePersonTerm); - - public string CapitalizedImmaturePersonTerm => ReplaceWithName(BasePronouns.CapitalizedImmaturePersonTerm, true); - - public string FormalAddressTerm => ReplaceWithName(BasePronouns.FormalAddressTerm); - - public string CapitalizedFormalAddressTerm => ReplaceWithName(BasePronouns.CapitalizedFormalAddressTerm, true); - - public string OffspringTerm => ReplaceWithName(BasePronouns.OffspringTerm); - - public string CapitalizedOffspringTerm => ReplaceWithName(BasePronouns.CapitalizedOffspringTerm, true); - - public string SiblingTerm => ReplaceWithName(BasePronouns.SiblingTerm); - - public string CapitalizedSiblingTerm => ReplaceWithName(BasePronouns.CapitalizedSiblingTerm, true); - - public string ParentTerm => ReplaceWithName(BasePronouns.ParentTerm); - - public string CapitalizedParentTerm => ReplaceWithName(BasePronouns.CapitalizedParentTerm, true); - - public string IndicativeProximal => ReplaceWithName(BasePronouns.IndicativeProximal); - - public string CapitalizedIndicativeProximal => ReplaceWithName(BasePronouns.CapitalizedIndicativeProximal, true); - - public string IndicativeDistal => ReplaceWithName(BasePronouns.IndicativeDistal); - - public string CapitalizedIndicativeDistal => ReplaceWithName(BasePronouns.CapitalizedIndicativeDistal, true); - - public bool UseBareIndicative => BasePronouns.UseBareIndicative; - } -} diff --git a/README.md b/README.md index 9133be1..6a5fe95 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ A mod that works in conjunction with the [Gender and Pronoun Sets](https://steam Specifically, this mod allows you to pick from all personal genders and pronouns defined in game (including pseudo-plural ones), with a number of additions. -Additionally, all gender and pronoun prompts have been redesigned with inspirations from kernelmethod's [Better Pet Selector](https://steamcommunity.com/sharedfiles/filedetails/?id=3006503292) mod. +Additionally, all gender and pronoun prompts have been redesigned with inspiration from kernelmethod's [Better Pet Selector](https://steamcommunity.com/sharedfiles/filedetails/?id=3006503292). -Tested to work on game versions 204.100 / 205.54 (beta). +Tested to work on game version 207.67. [Source Code (GitHub)](https://github.com/librarianmage/QudGendersUnleashed) \| [Workshop Page](https://steamcommunity.com/sharedfiles/filedetails/?id=2815078000) \| [My Caves of Qud Mods (Steam Workshop)](https://steamcommunity.com/profiles/76561198836298826/myworkshopfiles/?appid=333640) diff --git a/Scripts/Formatting.cs b/Scripts/Formatting.cs new file mode 100644 index 0000000..4ce56b2 --- /dev/null +++ b/Scripts/Formatting.cs @@ -0,0 +1,56 @@ +using System.Linq; +using ConsoleLib.Console; +using XRL.World; + +namespace QudGendersUnleashed +{ + public static class Formatting + { + public static readonly string FromGenderText = Markup.Color("W", ""); + public static readonly string CreateNewText = Markup.Color("W", ""); + + public static string FormatGender(Gender G) + { + var name = Markup.Color("M", G.Name); + var summary = Markup.Color("c", G.GetBasicSummary()); + + return $"{name}\n{summary}"; + } + + public static string FormatPronounSet(PronounSet P) + { + var name = Markup.Color("M", P.GetShortName()); + var summary = Markup.Color("c", P.GetBasicSummary()); + + if (P.FromGender) + { + var suspects = Gender + .Find(G => !G.DoNotReplicateAsPronounSet) + .Where(G => new PronounSet(G).Name == P.Name) + .Select(G => Markup.Color("m", G.Name)); + + var from = suspects.Any() ? string.Join(" / ", suspects) : "unknown"; + var extra = Markup.Color("m", $"(from {from})"); + + return $"{name} {extra}\n{summary}"; + } + + return $"{name}\n{summary}"; + } + + public static string FormatFromGenderPronounOption(Gender G) + { + var g1 = G ?? Gender.Get("nonspecific"); + if (g1 is null) + { + return FromGenderText; + } + + var name = Markup.Color("m", g1.Name); + var extra = Markup.Color("m", $"({name})"); + var summary = Markup.Color("c", g1.GetBasicSummary()); + + return $"{FromGenderText} {extra}\n{summary}"; + } + } +} diff --git a/Scripts/NamePronoun.cs b/Scripts/NamePronoun.cs new file mode 100644 index 0000000..aee26a1 --- /dev/null +++ b/Scripts/NamePronoun.cs @@ -0,0 +1,166 @@ +using ConsoleLib.Console; +using XRL.Language; +using XRL.World; + +namespace QudGendersUnleashed.NamePronoun +{ + /// + /// Wraps a , replacing =name=/=name's= with the name of the pronouns' referent. + /// + public class NamePronounWrapper : IPronounProvider + { + private readonly IPronounProvider BasePronouns; + private readonly GameObject Referent; + + private static bool HasName(string S) => S.Contains("=name=") || S.Contains("=name's="); + + public static bool CouldBeNamePronouns(IPronounProvider Pronouns) => + HasName(Pronouns.Name) + || HasName(Pronouns.CapitalizedName) + || HasName(Pronouns.Subjective) + || HasName(Pronouns.CapitalizedSubjective) + || HasName(Pronouns.Objective) + || HasName(Pronouns.CapitalizedObjective) + || HasName(Pronouns.PossessiveAdjective) + || HasName(Pronouns.CapitalizedPossessiveAdjective) + || HasName(Pronouns.SubstantivePossessive) + || HasName(Pronouns.CapitalizedSubstantivePossessive) + || HasName(Pronouns.Reflexive) + || HasName(Pronouns.CapitalizedReflexive) + || HasName(Pronouns.PersonTerm) + || HasName(Pronouns.CapitalizedPersonTerm) + || HasName(Pronouns.ImmaturePersonTerm) + || HasName(Pronouns.CapitalizedImmaturePersonTerm) + || HasName(Pronouns.FormalAddressTerm) + || HasName(Pronouns.CapitalizedFormalAddressTerm) + || HasName(Pronouns.OffspringTerm) + || HasName(Pronouns.CapitalizedOffspringTerm) + || HasName(Pronouns.SiblingTerm) + || HasName(Pronouns.CapitalizedSiblingTerm) + || HasName(Pronouns.ParentTerm) + || HasName(Pronouns.CapitalizedParentTerm) + || HasName(Pronouns.IndicativeProximal) + || HasName(Pronouns.CapitalizedIndicativeProximal) + || HasName(Pronouns.IndicativeDistal) + || HasName(Pronouns.CapitalizedIndicativeDistal); + + public static IPronounProvider Wrap(IPronounProvider BasePronouns, GameObject Referent) + { + if (BasePronouns is NamePronounWrapper p && p.Referent != Referent) + { + return new NamePronounWrapper(p.BasePronouns, Referent); + } + else if (CouldBeNamePronouns(BasePronouns)) + { + return new NamePronounWrapper(BasePronouns, Referent); + } + + return BasePronouns; + } + + public NamePronounWrapper(IPronounProvider BasePronouns, GameObject Referent) + { + this.BasePronouns = BasePronouns; + this.Referent = Referent; + } + + public string ReplaceWithName(string Pronoun, bool Capitalize = false) + { + if (Pronoun.Contains("=name")) + { + var displayName = Referent.BaseDisplayName; + if (Capitalize) + { + displayName = ColorUtility.CapitalizeExceptFormatting(displayName); + } + var displayNamePossessive = Grammar.MakePossessive(displayName); + return Pronoun + .Replace("=name=", displayName) + .Replace("=name's=", displayNamePossessive); + } + else + { + return Pronoun; + } + } + + public string Name => ReplaceWithName(BasePronouns.Name); + + public string CapitalizedName => ReplaceWithName(BasePronouns.CapitalizedName, true); + + public bool Generic => BasePronouns.Generic; + + public bool Generated => BasePronouns.Generated; + + public bool Plural => BasePronouns.Plural; + + public bool PseudoPlural => BasePronouns.PseudoPlural; + + public string Subjective => ReplaceWithName(BasePronouns.Subjective); + + public string CapitalizedSubjective => + ReplaceWithName(BasePronouns.CapitalizedSubjective, true); + + public string Objective => ReplaceWithName(BasePronouns.Objective); + + public string CapitalizedObjective => + ReplaceWithName(BasePronouns.CapitalizedObjective, true); + + public string PossessiveAdjective => ReplaceWithName(BasePronouns.PossessiveAdjective); + + public string CapitalizedPossessiveAdjective => + ReplaceWithName(BasePronouns.CapitalizedPossessiveAdjective, true); + + public string SubstantivePossessive => ReplaceWithName(BasePronouns.SubstantivePossessive); + + public string CapitalizedSubstantivePossessive => + ReplaceWithName(BasePronouns.CapitalizedSubstantivePossessive); + + public string Reflexive => ReplaceWithName(BasePronouns.Reflexive); + + public string CapitalizedReflexive => + ReplaceWithName(BasePronouns.CapitalizedReflexive, true); + + public string PersonTerm => ReplaceWithName(BasePronouns.PersonTerm); + + public string CapitalizedPersonTerm => + ReplaceWithName(BasePronouns.CapitalizedPersonTerm, true); + + public string ImmaturePersonTerm => ReplaceWithName(BasePronouns.ImmaturePersonTerm); + + public string CapitalizedImmaturePersonTerm => + ReplaceWithName(BasePronouns.CapitalizedImmaturePersonTerm, true); + + public string FormalAddressTerm => ReplaceWithName(BasePronouns.FormalAddressTerm); + + public string CapitalizedFormalAddressTerm => + ReplaceWithName(BasePronouns.CapitalizedFormalAddressTerm, true); + + public string OffspringTerm => ReplaceWithName(BasePronouns.OffspringTerm); + + public string CapitalizedOffspringTerm => + ReplaceWithName(BasePronouns.CapitalizedOffspringTerm, true); + + public string SiblingTerm => ReplaceWithName(BasePronouns.SiblingTerm); + + public string CapitalizedSiblingTerm => + ReplaceWithName(BasePronouns.CapitalizedSiblingTerm, true); + + public string ParentTerm => ReplaceWithName(BasePronouns.ParentTerm); + + public string CapitalizedParentTerm => + ReplaceWithName(BasePronouns.CapitalizedParentTerm, true); + + public string IndicativeProximal => ReplaceWithName(BasePronouns.IndicativeProximal); + + public string CapitalizedIndicativeProximal => + ReplaceWithName(BasePronouns.CapitalizedIndicativeProximal, true); + + public string IndicativeDistal => ReplaceWithName(BasePronouns.IndicativeDistal); + + public string CapitalizedIndicativeDistal => + ReplaceWithName(BasePronouns.CapitalizedIndicativeDistal, true); + + public bool UseBareIndicative => BasePronouns.UseBareIndicative; + } +} diff --git a/Scripts/Patches.cs b/Scripts/Patches.cs new file mode 100644 index 0000000..2509a9e --- /dev/null +++ b/Scripts/Patches.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using HarmonyLib; +using QudGendersUnleashed.NamePronoun; +using XRL; +using XRL.CharacterBuilds.Qud.UI; +using XRL.World; + +namespace QudGendersUnleashed.Patches +{ + /// Patch the gender selector for during character creation. + [HarmonyPatch(typeof(QudCustomizeCharacterModuleWindow))] + [HarmonyPatch(nameof(QudCustomizeCharacterModuleWindow.OnChooseGenderAsync))] + public static class GenderPatch + { + private static bool Prefix( + ref Task __result, + QudCustomizeCharacterModuleWindow __instance + ) + { + __result = Selectors.SelectGenderAsync(__instance.module?.data?.gender); + return false; + } + } + + /// Patch the pronoun set selector for during character creation. + [HarmonyPatch(typeof(QudCustomizeCharacterModuleWindow))] + [HarmonyPatch(nameof(QudCustomizeCharacterModuleWindow.OnChoosePronounSetAsync))] + public static class PronounPatch + { + private static bool Prefix( + ref Task __result, + QudCustomizeCharacterModuleWindow __instance + ) + { + var fromGenderPlaceholder = Traverse + .Create(__instance) + .Field("fromGenderPlaceholder") + .Value; + + var data = __instance.module?.data; + + __result = Selectors.ChoosePronounSetAsync( + data?.gender, + data?.pronounSet, + fromGenderPlaceholder + ); + return false; + } + } + + /// Patch the pronoun set selector for the (old-style) character sheet. + [HarmonyPatch(typeof(PronounAndGenderSets))] + [HarmonyPatch(nameof(PronounAndGenderSets.ShowChangePronounSet))] + public static class PlaytimePronounPatch + { + private static bool Prefix() + { + Selectors.ChoosePronounSet(); + return false; + } + } + + /// + /// Wraps the returned by with a . + /// + [HarmonyPatch(typeof(GameObject))] + [HarmonyPatch(nameof(GameObject.GetPronounProvider))] + public static class NameOnlyPronounPatch + { + private static IPronounProvider Postfix(IPronounProvider Pronouns, GameObject __instance) => + NamePronounWrapper.Wrap(Pronouns, __instance); + } +} diff --git a/Scripts/Selectors.cs b/Scripts/Selectors.cs new file mode 100644 index 0000000..1a63400 --- /dev/null +++ b/Scripts/Selectors.cs @@ -0,0 +1,195 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using XRL; +using XRL.UI; +using XRL.World; + +namespace QudGendersUnleashed +{ + /// Selectors for gender and pronoun sets. + public static class Selectors + { + #region Genders + public static async Task SelectGenderAsync(Gender CurrentGender) + { + var genders = Gender.GetAllPersonal(); + var options = genders.Select(G => Formatting.FormatGender(G)).ToList(); + options.Add(Formatting.CreateNewText); + + var initialSelection = genders.IndexOf(CurrentGender); + if (initialSelection < 0) + { + initialSelection = 0; + } + + var idx = await Popup.ShowOptionListAsync( + Title: "Choose Gender", + Options: options.ToArray(), + AllowEscape: true, + DefaultSelected: initialSelection + ); + + if (idx <= -1) + { + return null; + } + else if (0 <= idx && idx < options.Count - 1) + { + return genders[idx]; + } + else + { + var baseIdx = await Popup.ShowOptionListAsync( + Title: "Select Base Gender", + Options: genders.Select(G => G.Name).ToArray(), + AllowEscape: true, + DefaultSelected: initialSelection + ); + + if (baseIdx <= -1) + { + return null; + } + + var baseGender = genders[baseIdx]; + var newGender = new Gender(baseGender); + + if (await newGender.CustomizeAsync()) + { + return newGender; + } + else + { + return null; + } + } + } + + public static void ChooseGender(bool Message = false) + { + var newGender = SelectGenderAsync(The.Player.GetGender()).Result; + if (newGender is not null) + { + The.Player.SetGender(newGender.Register()); + if (Message) + { + Popup.Show($"Set gender to {newGender.Name}"); + } + } + } + #endregion + + #region Pronouns + public static async Task ChoosePronounSetAsync( + Gender CurrentGender, + PronounSet CurrentPronounSet, + PronounSet Placeholder + ) + { + var pronounSets = PronounSet.GetAllPersonal(); + var options = new List + { + Formatting.FormatFromGenderPronounOption(CurrentGender) + }; + options.AddRange(pronounSets.Select(P => Formatting.FormatPronounSet(P))); + options.Add(Formatting.CreateNewText); + + int initialSelection, + newSelection; + if (CurrentPronounSet is null) + { + initialSelection = 0; + newSelection = 0; + } + else + { + var setIdx = pronounSets.IndexOf(CurrentPronounSet); + + if (setIdx < 0) + { + initialSelection = 0; + newSelection = 0; + } + else + { + initialSelection = setIdx + 1; + newSelection = setIdx; + } + } + + var n = await Popup.ShowOptionListAsync( + "Choose Pronoun Set", + options.ToArray(), + AllowEscape: true, + DefaultSelected: initialSelection + ); + + if (n <= -1) + { + return null; + } + else if (n == 0) + { + return Placeholder; + } + else if (1 <= n && n < options.Count - 1) + { + return pronounSets[n - 1]; + } + else + { + var b = await Popup.ShowOptionListAsync( + "Select Base Set", + pronounSets.Select(P => P.Name).ToArray(), + AllowEscape: true, + DefaultSelected: newSelection + ); + if (b <= -1) + { + return null; + } + + var basePronounSet = pronounSets[b]; + var newPronounSet = new PronounSet(basePronounSet); + + if (await newPronounSet.CustomizeAsync()) + { + return newPronounSet; + } + else + { + return null; + } + } + } + + public static void ChoosePronounSet(bool Message = false) + { + var sentinel = new PronounSet(); + var newPronounSet = ChoosePronounSetAsync( + The.Player.GetGender(), + The.Player.GetPronounSet(), + sentinel + ).Result; + if (newPronounSet == sentinel) + { + var set = new PronounSet(The.Player.GetGender()); + The.Player.SetPronounSet(set); + if (Message) + { + Popup.Show($"set pronoun set from gender ({The.Player.GetGender().Name})", LogMessage: false); + } + } + else if (newPronounSet is not null) + { + The.Player.SetPronounSet(newPronounSet.Register()); + if (Message) + { + Popup.Show($"set pronoun set to {newPronounSet.Name}", LogMessage: false); + } + } + } + #endregion + } +} diff --git a/Wishes.cs b/Scripts/Wishes.cs similarity index 61% rename from Wishes.cs rename to Scripts/Wishes.cs index 731d1b1..db02713 100644 --- a/Wishes.cs +++ b/Scripts/Wishes.cs @@ -10,21 +10,18 @@ public static class Wishes /// Changes the user's gender. /// Will not change the user's pronouns. [WishCommand(Command = "changegender")] - static public void ChangeGender() => Selectors.ChooseGender(); + public static void ChangeGender() => Selectors.ChooseGender(true); /// Changes the user's pronouns. [WishCommand(Command = "changepronouns")] - static public void ChangePronounSet() => Selectors.ChoosePronounSet(); + public static void ChangePronounSet() => Selectors.ChoosePronounSet(true); /// Useless wish used to generate the header image. + /// [WishCommand(Command = "gendermeme")] - static public void GenderUploadForm() + public static void GenderUploadForm() { - string[] opts = { - "Male", - "Female", - "Custom [Upload custom gender (max 10MB)]" - }; + string[] opts = { "Male", "Female", "Custom [Upload custom gender (max 10MB)]" }; Popup.ShowOptionList("Gender", opts); } diff --git a/SelectorPatches.cs b/SelectorPatches.cs deleted file mode 100644 index 98fa293..0000000 --- a/SelectorPatches.cs +++ /dev/null @@ -1,38 +0,0 @@ -using HarmonyLib; -using XRL; -using XRL.World; -using XRL.CharacterBuilds.Qud.UI; -using System.Threading.Tasks; - -namespace QudGendersUnleashed.Patches -{ - /// Patch the gender selector during character creation. - [HarmonyPatch(typeof(QudCustomizeCharacterModuleWindow))] - [HarmonyPatch(nameof(QudCustomizeCharacterModuleWindow.OnChooseGenderAsync))] - public static class GenderPatch - { - static bool Prefix() => false; - - static Task Postfix(Task _, QudCustomizeCharacterModuleWindow __instance) => Selectors.OnChooseGenderAsync(__instance); - } - - /// Patch the pronoun set selector during character creation. - [HarmonyPatch(typeof(QudCustomizeCharacterModuleWindow))] - [HarmonyPatch(nameof(QudCustomizeCharacterModuleWindow.OnChoosePronounSetAsync))] - public static class PronounPatch - { - static bool Prefix() => false; - - static Task Postfix(Task _, PronounSet ___fromGenderPlaceholder, QudCustomizeCharacterModuleWindow __instance) - => Selectors.OnChoosePronounSetAsync(__instance, ___fromGenderPlaceholder); - } - - /// Patch the pronoun set selector from the character sheet. - [HarmonyPatch(typeof(PronounAndGenderSets))] - [HarmonyPatch(nameof(PronounAndGenderSets.ShowChangePronounSet))] - public static class PlaytimePronounPatch - { - static bool Prefix() => false; - static void Postfix() => Selectors.ChoosePronounSet(); - } -} diff --git a/Selectors.cs b/Selectors.cs deleted file mode 100644 index ea51751..0000000 --- a/Selectors.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using ConsoleLib.Console; -using XRL; -using XRL.CharacterBuilds.Qud.UI; -using XRL.UI; -using XRL.World; - -// TODO: refactor - -namespace QudGendersUnleashed -{ - /// Selectors for gender and pronoun sets. - [HasModSensitiveStaticCache] - public static class Selectors - { - private static string FromGenderText = Markup.Color("W", ""); - private static string CreateNewText = Markup.Color("W", ""); - - [ModSensitiveStaticCache] - private static Dictionary _PronounGenderMapping; - - public static Dictionary PronounGenderMapping { - get { - if (_PronounGenderMapping == null) - { - _PronounGenderMapping = new Dictionary(); - - var suspects = Gender.Find(g => !g.DoNotReplicateAsPronounSet); - foreach (Gender g in suspects) - { - var setName = new PronounSet(g).Name; - var set = PronounSet.GetIfExists(setName); - if (set != null) _PronounGenderMapping.Add(set, g); - } - - } - - return _PronounGenderMapping; - } - } - - private static string FormatName(string name, string color = "M") - { - if (ColorUtility.HasFormatting(name)) - { - return Markup.Color("y", name); - } - else - { - return Markup.Color(color, name); - } - } - - private static string FormatGender(Gender g) - { - string name = FormatName(g.Name); - string summary = Markup.Color("c", g.GetBasicSummary()); - - return $"{name}\n{summary}"; - } - - private static string FormatPronounSet(PronounSet p) - { - string name = FormatName(p.GetShortName()); - - string extra = ""; - - Gender g; - if (PronounGenderMapping.TryGetValue(p, out g)) - { - extra = Markup.Color("m", $" (from {FormatName(g.Name, "m")})"); - } - - string summary = Markup.Color("c", p.GetBasicSummary()); - - return $"{name}{extra}\n{summary}"; - } - - private static string FormatFromGenderPronounOption(Gender g1) - { - Gender g = g1 ?? Gender.Get("nonspecific"); - if (g == null) return FromGenderText; - - string gName = FormatName(g.Name, "m"); - - string extra = Markup.Color("m", $"({gName})"); - - string summary = Markup.Color("c", g.GetBasicSummary()); - - return $"{FromGenderText} {extra}\n{summary}"; - } - - public static async Task ChooseGenderAsync(Gender current) - { - var availableGenders = Gender.GetAllPersonal(); - var options = availableGenders.Select(gender=>FormatGender(gender)).ToList(); - options.Add(CreateNewText); - - var initial = availableGenders.IndexOf(current); - if (initial < 0) initial = 0; - - int index = await Popup.ShowOptionListAsync( - Title: "Choose Gender", - Options: options.ToArray(), - AllowEscape: true, - DefaultSelected: initial - ); - - if (index <= -1) - return null; - else if (0 <= index && index < options.Count - 1) - return availableGenders[index]; - else - { - int baseIndex = await Popup.ShowOptionListAsync( - Title: "Select Base Gender", - Options: availableGenders.Select(gender => gender.Name).ToArray(), - AllowEscape: true, - DefaultSelected: initial - ); - - if (baseIndex <= -1) return null; - - var baseGender = availableGenders[baseIndex]; - var newGender = new Gender(baseGender); - - if(await newGender.CustomizeAsync()) - return newGender; - else - return null; - } - } - - public static async Task OnChooseGenderAsync(QudCustomizeCharacterModuleWindow window) - => await ChooseGenderAsync(window?.module?.data?.gender); - - public static void ChooseGender() - { - var newGender = ChooseGenderAsync(The.Player.GetGender()).Result; - The.Player.SetGender(newGender.Register()); - } - - public static async Task ChoosePronounSetAsync(Gender currentGender, PronounSet currentPronounSet, PronounSet placeholder) - { - var availablePronounSets = PronounSet.GetAllPersonal(); - var options = new List(); - options.Add(FormatFromGenderPronounOption(currentGender)); - options.AddRange(availablePronounSets.Select(pronounSet => FormatPronounSet(pronounSet))); - options.Add(CreateNewText); - - int initialPos, newPos; - if (currentPronounSet == null) - { - initialPos = 0; - newPos = 0; - } - else - { - int setIndex = availablePronounSets.IndexOf(currentPronounSet); - - initialPos = setIndex + 1; - newPos = setIndex; - if (newPos < 0) newPos = 0; - } - - int n = await Popup.ShowOptionListAsync("Choose Pronoun Set", options.ToArray(), AllowEscape: true, DefaultSelected: initialPos); - - if (n <= -1) - return null; - else if (n == 0) - return placeholder; - else if (1 <= n && n < options.Count - 1) - return availablePronounSets[n]; - else - { - int b = await Popup.ShowOptionListAsync("Select Base Set", availablePronounSets.Select(PronounSet => PronounSet.Name).ToArray(), AllowEscape: true, DefaultSelected: newPos); - if (b <= -1) return null; - - var basePronounSet = availablePronounSets[b]; - var newPronounSet = new PronounSet(basePronounSet); - - if( await newPronounSet.CustomizeAsync() ) return newPronounSet; - else return null; - } - } - - public static async Task OnChoosePronounSetAsync(QudCustomizeCharacterModuleWindow window, PronounSet fromGenderPlaceholder) - { - var data = window?.module?.data; - return await ChoosePronounSetAsync(data?.gender, data?.pronounSet, fromGenderPlaceholder); - } - - public static void ChoosePronounSet() - { - var sentinel = new PronounSet(); - var newPronounSet = ChoosePronounSetAsync(The.Player.GetGender(), The.Player.GetPronounSet(), sentinel).Result; - if (newPronounSet == sentinel) - { - var n = new PronounSet(The.Player.GetGender()); - The.Player.SetPronounSet(n); - } - else if (newPronounSet != null) - { - The.Player.SetPronounSet(newPronounSet.Register()); - } - } - } -} diff --git a/Genders.xml b/XML/Genders.xml similarity index 100% rename from Genders.xml rename to XML/Genders.xml diff --git a/PronounSets.xml b/XML/PronounSets.xml similarity index 100% rename from PronounSets.xml rename to XML/PronounSets.xml diff --git a/manifest.json b/manifest.json index 9848aef..ea81060 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "title": "{{m-Y-g alternation|Qud: Genders Unleashed}}", "description": "Adds gendeers and pronounce", "tags": "Gender,Pronouns", - "version": "1.5", + "version": "1.6", "author": "{{R|librarianmage}}", "previewImage": "Assets/icon.png" } diff --git a/project.csproj b/project.csproj index 221b665..cfd8da5 100644 --- a/project.csproj +++ b/project.csproj @@ -3,6 +3,7 @@ QudGendersUnleashed QudGendersUnleashed librarianmage + true diff --git a/workshop.json b/workshop.json index 847418d..627c439 100644 --- a/workshop.json +++ b/workshop.json @@ -1,8 +1,8 @@ { "WorkshopId": 2815078000, "Title": "Qud: Genders Unleashed", - "Description": "A mod that works in conjunction with the [url=https://steamcommunity.com/sharedfiles/filedetails/?id=1735379738]Gender and Pronoun Sets[/url] mod to to provide a plethora of genders and pronouns for your game character.\n\nSpecifically, this mod allows you to pick from all personal genders and pronouns defined in game (including pseudo-plural ones), with a number of additions.\n\nAdditionally, all gender and pronoun prompts have been redesigned with inspirations from kernelmethod's [url=https://steamcommunity.com/sharedfiles/filedetails/?id=3006503292]Better Pet Selector[/url] mod.\n\nTested to work on game versions 204.100 / 205.54 (beta).\n\n[url=https://github.com/librarianmage/QudGendersUnleashed]Source Code (GitHub)[/url]\n\n[h1]Wishes[/h1]The wishes `changegender` and `changepronouns` have been added for quick access while playing.[h1]`=name=` Pronoun Sets[/h1]Starting with version 1.1, `=name=` in a pronoun set will be dynamically replaced with the player character's name (`=name's=` with the possessive version). To demonstrate this, a pronoun set consisting completely of `=name=` and `=name's=` has been added.", + "Description": "A mod that works in conjunction with the [url=https://steamcommunity.com/sharedfiles/filedetails/?id=1735379738]Gender and Pronoun Sets[/url] mod to to provide a plethora of genders and pronouns for your game character.\n\nSpecifically, this mod allows you to pick from all personal genders and pronouns defined in game (including pseudo-plural ones), with a number of additions.\n\nAdditionally, all gender and pronoun prompts have been redesigned with inspiration from kernelmethod's [url=https://steamcommunity.com/sharedfiles/filedetails/?id=3006503292]Better Pet Selector[/url].\n\nTested to work on game versions 207.67.\n\n[url=https://github.com/librarianmage/QudGendersUnleashed]Source Code (GitHub)[/url]\n\n[h1]Wishes[/h1]The wishes `changegender` and `changepronouns` have been added for quick access while playing.[h1]`=name=` Pronoun Sets[/h1]Starting with version 1.1, `=name=` in a pronoun set will be dynamically replaced with the player character's name (`=name's=` with the possessive version). To demonstrate this, a pronoun set consisting completely of `=name=` and `=name's=` has been added.", "Tags": "Stable,Beta,Script,Gender,Pronouns", "Visibility": "2", "ImagePath": "Assets/icon.png" -} +} \ No newline at end of file