Tutorial: Adding Gender Neutrality Mod Tokens (GNMT) Compatibility

From Stardew Modding Wiki
Jump to navigation Jump to search

This tutorial is aimed at mod authors who would like to add compatibility with Gender Neutrality and Gender Neutrality Mod Tokens to their mod or adapt another mod so that it offers gender neutral or nonbinary dialogue.


What is Gender Neutrality Mod Tokens?

Gender Neutrality Mod Tokens (GNMT) is a mod created by Hanatsuki that allows players to set custom pronouns, title of address, spouse & family nouns, and other similar words related to the farmer's gender, including the option to randomise between multiple sets. By using C# tokens, players can set their pronouns in GNMT's configuration options and other mods will be able to detect what the player has chosen and use the appropriate pronouns in their own dialogue! This saves mod authors from having to create their own config options and players from having to configure their pronouns multiple times or be stuck with they/them as their only nonbinary pronouns. Although complicated at first glance, it's a powerful and versatile tool once you understand how to use it.


How Do I Use It?

If you're not using i18n for easy translations (more on that later), then using GNMT's tokens is pretty straightforward. If you haven't used tokens before, think of them as being like a box that can swap out a word or phrase (or more if you get creative) depending on different conditions. For example, say you had the following dialogue that changes depending on the farmer's gender:

"spring_Wed": "The new farmer, huh? He looked kinda weird.^The new farmer, huh? She looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

If you wanted to allow nonbinary pronouns for this line, you could remove the ^ (which swaps the dialogue depending on the farmer's gender) and swap the "he" or "she" for {{Hana.GNMTokens/SubjectivePronoun}} like so:

"spring_Wed": "The new farmer, huh? {{Hana.GNMTokens/SubjectivePronoun}} looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

The token will read the player's GNMT config file and swap in whatever they've written for the SubjectivePronoun option. So if they've written "They", then the line would read: "The new farmer, huh? They looked kinda weird.", etc, or if they've written "Xe", it would read: "The new farmer, huh? Xe looked kinda weird.".

For a list of what tokens are available, see the GNMT Github page.


Lowercase & Uppercase

One little snag you might run into is how to handle capitalisation. When the player writes their choices in GNMT's config options, they are encouraged to write them with the first letter capitalised. How then do we handle using the same token in sentences that require it to be lowercase? The answer is Content Patcher's lowercase string manipulation code. If our token is a box, then this code is wrapping paper for that box that tells us a little bit about what to expect from the box.

Say we have the following dialogue:

"spring_Wed4": "The farmer keeps visiting. Do you think he might, y'know, like me or something?^The farmer keeps visiting. Do you think she might, y'know, like me or something?#$e#Agh! You! You didn't hear anything!"

If you just use the token as we did in the previous example, then it would fill in "Do you think They might", which is of course incorrectly uppercase. If we add {{Lowercase:}} on the outside of our token though, it'll force anything swapped in by that token to be in lowercase. For example:

"spring_Wed4": "The farmer keeps visiting. Do you think {{Lowercase:{{Hana.GNMTokens/SubjectivePronoun}}}} might, y'know, like me or something?#$e#Agh! You! You didn't hear anything!"

Which would then give us "Do you think they might". Note: Content Patcher also has {{Uppercase:}} as an option. This will make the entire token uppercase (ie. "Do you think THEY might"), which may be useful in select circumstances such as if you need emphasis or if your character is shouting.

Just make sure you have your brackets all lined up correctly!


Randomisation

Another neat feature offered by GNMT is the ability for players to set multiple options for a config, such as "she,they" for SubjectivePronoun or "partner,spouse" for SpouseNoun. In order to allow your dialogue to properly randomise between these options though, you'll need to use another bit of Content Patcher code, the Random token. The Random token will read anything inside it separated by commas (or another custom separator, but we'll keep it simple for now) and randomly select one. If you don't have this and the player writes in multiple options, then the token will fill in those values directly; using our previous example, if the player writes "She,They" as their pronoun options, then our dialogue would end up saying "The new farmer, huh? She,They looked kinda weird.", which obviously is not ideal.

Like our uppercase & lowercase code, Random can wrap around our token like so:

"spring_Wed": "The new farmer, huh? {{Random:{{Hana.GNMTokens/SubjectivePronoun}}}} looked kinda weird.#$e#Wait, YOU're the new farmer? Sorry! I didn't realise!"

which would then correctly fill in either "She looked kinda weird." or "They looked kinda weird.".

You can also combine Random and Uppercase/Lowercase by adding both. Watch out for your brackets with this, as it's easy to lose track of which opening brackets connect up with which closing brackets. Here's an example of using both:

"spring_Wed": "The new farmer, huh? {{Uppercase:{{Random:{{Hana.GNMTokens/SubjectivePronoun}}}}}} looked kinda weird.#$e#Wait, YOU're the new farmer? Sorry! I didn't realise!"
"spring_Wed4": "The farmer keeps visiting. Do you think {{Lowercase:{{Random:{{Hana.GNMTokens/SubjectivePronoun}}}}}} might, y'know, like me or something?#$e#Agh! You! You didn't hear anything!"

This will tell your code to first force the token to be uppercase or lowercase, then select one of the random options, then swap it in to the dialogue.


i18n Integration

What if you're using i18n to make it easy to translate your mod? Tokens can't be used directly in i18n-ified dialogue because it'll just paste the token directly into the dialogue and the NPC will say the code directly, like so:

"spring_Wed": "{{i18n: MyObliviousNPC.Dialogue.spring_Wed}}"
"Oblivia": "The new farmer, huh? {{Hana.GNMTokens/SubjectivePronoun}} looked kinda weird."
"Oblivia": "Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

What you need to do is to "pass" the token into the i18n by adding an extra key on the end so that the i18n knows how to read the token appropriately. This will be in the form " |[i18n token]={{GNMTToken}}". With the [i18n token], this can be named anything you want it to be as long as it matches in both the key and the i18n dialogue, but it's recommended not to make it the exact same word as the GNMT token, as this can cause issues sometimes. Here's an example:

"spring_Wed": "{{i18n: MyObliviousNPC.Dialogue.spring_Wed |SubjectivePronoun={{Hana.GNMTokens/SubjectivePronoun}}}}"
"MyObliviousNPC.Dialogue.spring_Wed": "The new farmer, huh? {{SubjectivePronoun}} looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

This tells our code "when you find {{SubjectivePronoun}}, replace it with the token {{Hana.GNMTokens/SubjectivePronoun}}", which then lets the i18n display it properly.


If you want to combine it with the previous uppercase/lowercase and Random code, you can add those to the key as well! Hold onto your brackets here, it gets long:

"spring_Wed": "{{i18n: MyObliviousNPC.Dialogue.spring_Wed |SubjectivePronoun={{Uppercase:{{Random:{{Hana.GNMTokens/SubjectivePronoun}}}}}}}}"
"MyObliviousNPC.Dialogue.spring_Wed": "The new farmer, huh? {{SubjectivePronoun}} looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

Yes, that is eight curly brackets on the end.


Automatic Switching On & Off

The last section of this guide involves setting up your mod to only enable the GNMT tokens if it detects GNMT is installed, so that players can use the mod without it if they like. One easy way to do this would be to write your dialogue as normal and then write a second code block with a When condition and a HasMod check that has any dialogue that needs to be made gender-neutral. For example:

		{
			"LogName": "Base Dialogue",
			"Action": "EditData",
			"Target": "Characters/Dialogue/Oblivia",
			"Entries": {
			   "spring_Wed": "The new farmer, huh? He looked kinda weird.^The new farmer, huh? She looked kinda weird.#$e#Wait, YOU're the new farmer? Sorry! I didn't realise!"
			}
		},
		{
			"LogName": "GNMT-Compatible Dialogue",
			"Action": "EditData",
			"Target": "Characters/Dialogue/Oblivia",
			"Entries": {
			   "spring_Wed": "The new farmer, huh? {{Uppercase:{{Random:{{Hana.GNMTokens/SubjectivePronoun}}}}}} looked kinda weird.#$e#Wait, YOU're the new farmer? Sorry! I didn't realise!"
			"When": { "HasMod": "Hana.GNMTokens" }
			}
		},

If GNMT isn't installed, then the mod will use the original dialogue, but if GNMT is installed, then it'll overwrite the original dialogue with the GNMT-specific version. For GNMT-specific versions of your events though, this involves copying your entire event even if you only need to have one line change - it'll still work, but you'll end up with a lot of duplicate code.


If you're using i18n though, it's possible to set up a dynamic token (this guide will assume that you know the basics of how to set up a simple dynamic token) to automatically switch between the original dialogue line and the GNMT-specific line without requiring duplicate code! Create a dynamic token like so (the token itself and the value with the HasMod can be whatever you like):

		{
			"Name": "GenderNeutralToken",
			"Value": "",
		},
		{
			"Name": "GenderNeutralToken",
			"Value": ".GN",
			"When": { "HasMod": "Hana.GNMTokens" }
		},

and then create a dynamic token for each GNMT token you're using:

		{
			"Name": "GNMTSubjectivePronoun",
			"Value": "false",
		},
		{
			"Name": "GNMTSubjectivePronoun",
			"Value": "{{Hana.GNMTokens/SubjectivePronoun}}",
			"When": { "HasMod": "Hana.GNMTokens" }
		},
		{
			"Name": "GNMTObjectivePronoun",
			"Value": "false",
		},
		{
			"Name": "GNMTObjectivePronoun",
			"Value": "{{Hana.GNMTokens/ObjectivePronoun}}",
			"When": { "HasMod": "Hana.GNMTokens" }
		},

For the first dynamic token, add your {{GenderNeutralToken}} to the end of your i18n key and add the .GN value to the end of the GNMT-specific i18n line. For the second, remember the earlier section about "passing" tokens into i18n with this code: " |[i18n token]={{GNMTToken}}"? We need to replace the {{GNMTToken}} bit with our dynamic token name instead. Here's how it looks altogether:

"spring_Wed": "{{i18n: MyObliviousNPC.Dialogue.spring_Wed{{GenderNeutralToken}} |SubjectivePronoun={{GNMTSubjectivePronoun}}}}"
"MyObliviousNPC.Dialogue.spring_Wed": "The new farmer, huh? He looked kinda weird.^The new farmer, huh? She looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"
"MyObliviousNPC.Dialogue.spring_Wed.GN": "The new farmer, huh? {{SubjectivePronoun}} looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

If GNMT isn't installed, then the {{GenderNeutralToken}} token will simply be blank and the i18n will load the "MyObliviousNPC.Dialogue.spring_Wed" dialogue. It will also replace the i18n pass code with "false" - this is necessary because otherwise Content Patcher won't be happy about trying to read a C# token that doesn't exist and it won't load your dialogue at all.

If it is installed, then the {{GenderNeutralToken}} token will be replaced by .GN and the i18n will load the "MyObliviousNPC.Dialogue.spring_Wed.GN" dialogue. {{GNMTSubjectivePronoun}} will swap in the appropriate C# token from GNMT, which will then be read by the i18n. And as always, this can be combined with our uppercase/lowercase and Random code:

"spring_Wed": "{{i18n: MyObliviousNPC.Dialogue.spring_Wed{{GenderNeutralToken}} |SubjectivePronoun={{Uppercase:{{Random:{{GNMTSubjectivePronoun}}}}}}}}"
"MyObliviousNPC.Dialogue.spring_Wed": "The new farmer, huh? He looked kinda weird.^The new farmer, huh? She looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"
"MyObliviousNPC.Dialogue.spring_Wed.GN": "The new farmer, huh? {{SubjectivePronoun}} looked kinda weird.#$e#Wait, YOU'RE the new farmer? Sorry! I didn't realise!"

This might all seem very complex and it is the first few times you look at it. Don't feel bad if you'd rather stick to an easier method at first or if you don't feel comfortable implementing everything at once. But keep reading it over, make a backup of your code, and experiment with things, and it will become easier with experience. Good luck!