diff --git a/icon-small.png b/Assets/favicon.png similarity index 55% rename from icon-small.png rename to Assets/favicon.png index 03c9cf1..b39bead 100644 Binary files a/icon-small.png and b/Assets/favicon.png differ diff --git a/Assets/icon.aseprite b/Assets/icon.aseprite new file mode 100644 index 0000000..98b011e Binary files /dev/null and b/Assets/icon.aseprite differ diff --git a/icon.png b/Assets/icon.png similarity index 90% rename from icon.png rename to Assets/icon.png index 1de24e0..6d34a53 100644 Binary files a/icon.png and b/Assets/icon.png differ diff --git a/meme.png b/Assets/meme.png similarity index 100% rename from meme.png rename to Assets/meme.png diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..71cafbf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + +## v1.5 — 2023-08-15 +Tested to work on game versions 204.100 / 205.54 (beta) + +* Add new selector UI +* Fix error preventing loading +* Reduce `NamePronounWrapper` wrapping + +Meta: + +* Add `CHANGELOG.md` +* Update icon +* Update mod description +* Update `LICENSE` + +## v1.4 — 2022-10-15 +Tested to work on game version 203.54 and the Moon Stair alpha + +* Update mod to work with Moon Stair alpha + +## v1.3 — 2022-07-26 +Tested to work on game version 203.54 + +* Update documentation + +## v1.2 — 2022-07-26 + +* Update name only pronoun to use player's name in all third-person pronoun positions + +## v1.1 — 2022-07-06 + +* Add pronoun set that uses only the name of the thing being referred to +* Update playtime pronoun selector (seen on the character stat screen) to use the modern UI +* Update icon + +## v1.0 — 2022-05-31 + +* Initial release diff --git a/LICENSE b/LICENSE index 0e259d4..f852d42 100644 --- a/LICENSE +++ b/LICENSE @@ -1,121 +1,5 @@ -Creative Commons Legal Code +Copyright (C) 2022–2023 by librarianmage -CC0 1.0 Universal +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/namepronoun.cs b/NamePronoun.cs similarity index 50% rename from namepronoun.cs rename to NamePronoun.cs index c10cbc9..efc03f8 100644 --- a/namepronoun.cs +++ b/NamePronoun.cs @@ -5,42 +5,92 @@ namespace QudGendersUnleashed.NamePronoun { + /// + /// Wraps the returned by with a . + /// [HarmonyPatch(typeof(GameObject))] [HarmonyPatch(nameof(GameObject.GetPronounProvider))] public static class NameOnlyPronounPatch { - static IPronounProvider Postfix(IPronounProvider PronounSet, GameObject __instance) - { - return new NamePronounWrapper(PronounSet, __instance); - } + static IPronounProvider Postfix(IPronounProvider pronouns, GameObject __instance) + => NamePronounWrapper.Wrap(pronouns, __instance); } - // Wraps a IPronounProvider to replace =name=/=name's= with the holder's name + /// + /// Wraps a , replacing =name=/=name's= with the name of the pronouns' referent. + /// public class NamePronounWrapper : IPronounProvider { private IPronounProvider BasePronouns; - private GameObject Referrant; + 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 Referrant) + public NamePronounWrapper(IPronounProvider basePronouns, GameObject referent) { - this.BasePronouns = BasePronouns; - this.Referrant = Referrant; - // Consider: caching if replacing is needed + this.BasePronouns = basePronouns; + this.Referent = referent; } - string ReplaceWithName(string Pronoun, bool capitalize = false) + public string ReplaceWithName(string pronoun, bool capitalize = false) { - if (Pronoun.Contains("=name")) + if (pronoun.Contains("=name")) { - string DisplayName = Referrant.BaseDisplayNameStripped; - if (capitalize) { DisplayName = ColorUtility.CapitalizeExceptFormatting(DisplayName); } - string DisplayNamePosessive = Grammar.MakePossessive(DisplayName); - return Pronoun.Replace("=name=", DisplayName) - .Replace("=name's=", DisplayNamePosessive); + 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; + return pronoun; } } @@ -76,7 +126,6 @@ string ReplaceWithName(string Pronoun, bool capitalize = false) public string CapitalizedReflexive => ReplaceWithName(BasePronouns.CapitalizedReflexive, true); - // Consider: The methods below here are unlikely to contain =name=/=name's=, remove wrap? public string PersonTerm => ReplaceWithName(BasePronouns.PersonTerm); public string CapitalizedPersonTerm => ReplaceWithName(BasePronouns.CapitalizedPersonTerm, true); diff --git a/README.md b/README.md index 82b5072..9133be1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,31 @@ # Qud: Genders Unleashed -A mod that works in conjunction with the [Gender and Pronoun Sets](https://steamcommunity.com/sharedfiles/filedetails/?id=1735379738) mod to allow your delver to wreathe themselves in the gender identity and pronouns they feel most comfortable in. +A mod that works in conjunction with the [Gender and Pronoun Sets](https://steamcommunity.com/sharedfiles/filedetails/?id=1735379738) mod to provide a plethora of genders and pronouns for your game character. 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, the "choose pronoun" prompt in game has been modified to use the modern UI. This will facilitate picking from the extended list this mod provides. +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. -*`=name=` pronoun sets:* 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. +Tested to work on game versions 204.100 / 205.54 (beta). -Tested to work on game version 203.54 +[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) -[Github Page](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) +## Wishes + +The wishes `changegender` and `changepronouns` have been added for quick access while playing. + +## `=name=` Pronoun Sets + +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. + +## Installation + +Please refer to the Caves of Qud Wiki for [mod installation instructions](https://wiki.cavesofqud.com/wiki/Modding:Installing_a_mod). + +## Development + +In order for type-checking and auto-completion to work, place the game-generated `Mods.csproj` into the parent directory of this folder. + +## License + +I share the source of my mods with the intent that they can be useful for others. To this end, the code of this mod has been released with the Zero-Clause BSD (0BSD) license and the assets of this mod (namely, files in the `Assets/` folder) have been released with the Creative Commons Zero v1.0 Universal (CC0-1.0) license. diff --git a/SelectorPatches.cs b/SelectorPatches.cs new file mode 100644 index 0000000..98fa293 --- /dev/null +++ b/SelectorPatches.cs @@ -0,0 +1,38 @@ +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 new file mode 100644 index 0000000..ea51751 --- /dev/null +++ b/Selectors.cs @@ -0,0 +1,210 @@ +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/Wishes.cs b/Wishes.cs new file mode 100644 index 0000000..731d1b1 --- /dev/null +++ b/Wishes.cs @@ -0,0 +1,32 @@ +using XRL.UI; +using XRL.Wish; + +namespace QudGendersUnleashed +{ + /// Various wishes of varying utility. + [HasWishCommand] + 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(); + + /// Changes the user's pronouns. + [WishCommand(Command = "changepronouns")] + static public void ChangePronounSet() => Selectors.ChoosePronounSet(); + + /// Useless wish used to generate the header image. + [WishCommand(Command = "gendermeme")] + static public void GenderUploadForm() + { + string[] opts = { + "Male", + "Female", + "Custom [Upload custom gender (max 10MB)]" + }; + + Popup.ShowOptionList("Gender", opts); + } + } +} diff --git a/egg.cs b/egg.cs deleted file mode 100644 index 446a43d..0000000 --- a/egg.cs +++ /dev/null @@ -1,14 +0,0 @@ -using XRL.UI; -using XRL.Wish; - -[HasWishCommand] -public class GenderUploadForm -{ - [WishCommand(Command = "gendermeme")] - static public bool GenderMeme() - { - string[] opts = {"Male", "Female", "Custom [Upload custom gender (max 10MB)]"}; - Popup.ShowOptionList("Gender", opts); - return true; - } -} diff --git a/icon.aseprite b/icon.aseprite deleted file mode 100644 index f5ddd21..0000000 Binary files a/icon.aseprite and /dev/null differ diff --git a/manifest.json b/manifest.json index e903043..9848aef 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,9 @@ { - "id": "QudGendersUnleashed", - "title": "{{m-Y-g alternation|Qud: Genders Unleashed}}", - "description": "Adds gendeers and pronounce", - "version": "1.4", - "author": "librarianmage", - "previewImage": "icon.png" + "id": "QudGendersUnleashed", + "title": "{{m-Y-g alternation|Qud: Genders Unleashed}}", + "description": "Adds gendeers and pronounce", + "tags": "Gender,Pronouns", + "version": "1.5", + "author": "{{R|librarianmage}}", + "previewImage": "Assets/icon.png" } diff --git a/project.csproj b/project.csproj index 9823b73..221b665 100644 --- a/project.csproj +++ b/project.csproj @@ -1,7 +1,8 @@ - netstandard2.0 - false + QudGendersUnleashed + QudGendersUnleashed + librarianmage diff --git a/selectors.cs b/selectors.cs deleted file mode 100644 index 3c3c037..0000000 --- a/selectors.cs +++ /dev/null @@ -1,145 +0,0 @@ -using HarmonyLib; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using XRL; -using XRL.World; -using XRL.CharacterBuilds.Qud.UI; -using XRL.UI; -using System.Threading.Tasks; - -namespace QudGendersUnleashed.PronounAndGenderSelectorPatches -{ - [HarmonyPatch] - public static class CharacterCreationGenderPatch - { - public static MethodInfo TargetMethod() - { - return AccessTools.DeclaredMethod(AccessTools.Inner(typeof(QudCustomizeCharacterModuleWindow), "d__5"), "MoveNext"); - } - public static IEnumerable Transpiler(IEnumerable instructions) - { - MethodInfo oldPReefMethod = AccessTools.Method(typeof(Gender), nameof(Gender.GetAllGenericPersonalSingular)); - MethodInfo oldMStairMethod = AccessTools.Method(typeof(Gender), nameof(Gender.GetAllGenericPersonal)); - MethodInfo newMethod = AccessTools.Method(typeof(Gender), nameof(Gender.GetAllPersonal)); - - IEnumerable replacePReef = HarmonyLib.Transpilers.MethodReplacer(instructions, oldPReefMethod, newMethod); - IEnumerable replaceMStair = HarmonyLib.Transpilers.MethodReplacer(replacePReef, oldMStairMethod, newMethod); - - return replaceMStair; - } - } - - [HarmonyPatch] - public static class CharacterCreationPronounPatch - { - public static MethodBase TargetMethod() - { - return AccessTools.DeclaredMethod(AccessTools.Inner(typeof(QudCustomizeCharacterModuleWindow), "d__7"), "MoveNext"); - } - public static IEnumerable Transpiler(IEnumerable instructions) - { - - MethodInfo oldPReefMethod = AccessTools.Method(typeof(PronounSet), nameof(PronounSet.GetAllGenericPersonalSingular)); - MethodInfo oldMStairMethod = AccessTools.Method(typeof(PronounSet), nameof(PronounSet.GetAllGenericPersonal)); - MethodInfo newMethod = AccessTools.Method(typeof(PronounSet), nameof(PronounSet.GetAllPersonal)); - - IEnumerable replacePReef = HarmonyLib.Transpilers.MethodReplacer(instructions, oldPReefMethod, newMethod); - IEnumerable replaceMStair = HarmonyLib.Transpilers.MethodReplacer(replacePReef, oldMStairMethod, newMethod); - - return replaceMStair; - } - } - - // Technically unused - [HarmonyPatch(typeof(PronounAndGenderSets))] - [HarmonyPatch(nameof(PronounAndGenderSets.ShowChangePronounSet))] - public static class PlaytimePronounPatch - { - public static IEnumerable Transpiler(IEnumerable instructions) - { - MethodInfo oldMethod = AccessTools.Method(typeof(PronounSet), nameof(PronounSet.GetAllGenericPersonal)); - MethodInfo newMethod = AccessTools.Method(typeof(PronounSet), nameof(PronounSet.GetAllPersonal)); - return HarmonyLib.Transpilers.MethodReplacer(instructions, oldMethod, newMethod); - } - } - - - [HarmonyPatch(typeof(StatusScreen))] - [HarmonyPatch(nameof(StatusScreen.Show))] - public static class StatusScreenPatch - { - public static IEnumerable Transpiler(IEnumerable instructions) - { - MethodInfo oldMethod = AccessTools.Method(typeof(PronounAndGenderSets), nameof(PronounAndGenderSets.ShowChangePronounSet)); - MethodInfo newMethod = AccessTools.Method(typeof(StatusScreenPatch), nameof(StatusScreenPatch.ChangePronounSet)); - return HarmonyLib.Transpilers.MethodReplacer(instructions, oldMethod, newMethod); - } - - public static void ChangePronounSet(GameObject Player) - { - Task newPronounTask = OnChoosePronounSetAsync(Player); - newPronounTask.Wait(); - PronounSet newPronoun = newPronounTask.Result; - - if (newPronoun != null) - { - Player.SetPronounSet(newPronoun.Register()); - } - } - - public static async Task OnChoosePronounSetAsync(GameObject Player) - { - List availablePronounSets = PronounSet.GetAllPersonal(); - IEnumerable pronounNames = availablePronounSets.Select((PronounSet pronounSet) => pronounSet.Name); - - PronounSet currentPronounSet = Player.GetPronounSet(); - int indexCurrentPronounSet = availablePronounSets.FindIndex(p => p == currentPronounSet); - - List options = new List(); - options.Add(""); - options.AddRange(pronounNames); - options.Add(""); - - int index = await Popup.AsyncShowOptionsList( - Title: "Change Pronoun Set", - Options: options.ToArray(), - AllowEscape: true, - defaultSelected: indexCurrentPronounSet + 1); - - if (index > -1) - { - if (options[index] != "") - { - if (index == 0) - { - // Selected - Gender playerGender = Player.GetGender(); - return new PronounSet(playerGender); - } - else - { - return availablePronounSets[index - 1]; - } - } - - int basePronounIndex = await Popup.AsyncShowOptionsList( - Title: "Select Base Set", - Options: pronounNames.ToArray(), - RespectOptionNewlines: false, - AllowEscape: true); - - if (basePronounIndex > -1) - { - PronounSet original = availablePronounSets[basePronounIndex]; - PronounSet newPronounSet = new PronounSet(original); - if (await newPronounSet.CustomizeAsync()) - { - return newPronounSet; - } - } - } - return null; - } - } -} diff --git a/workshop.json b/workshop.json index 9dab11b..847418d 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 allow your delver to wreathe themselves in the gender identity and pronouns they feel most comfortable in.\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, the \"choose pronoun\" prompt in game has been modified to use the modern UI. This will facilitate picking from the extended list this mod provides.\n\n[i]`=name=` pronoun sets:[/i] 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.\n\nTested to work on game version 203.54 and the Moon Stair alpha.\n\n[url=https://github.com/librarianmage/QudGendersUnleashed]Github Page[/url]", + "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.", "Tags": "Stable,Beta,Script,Gender,Pronouns", "Visibility": "2", - "ImagePath": "icon.png" + "ImagePath": "Assets/icon.png" }