From 132ba814228356f8f84d7078f05a57d866bc0b60 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Emmanuel Zorg <zorgjeanbe@proton.me>
Date: Sat, 23 Jul 2022 16:34:27 +0200
Subject: [PATCH 1/4] Refactor how nicknames are set

This turns the nickname setting into a helper function in Character.js,
and extends it so it returns validation failures, and updates Title.js
so any issue is made clear to the user.
---
 .../Screens/Character/Title/Text_Title.csv    |  2 ++
 BondageClub/Screens/Character/Title/Title.js  | 24 +++++++++------
 BondageClub/Scripts/Character.js              | 29 +++++++++++++++++++
 3 files changed, 46 insertions(+), 9 deletions(-)

diff --git a/BondageClub/Screens/Character/Title/Text_Title.csv b/BondageClub/Screens/Character/Title/Text_Title.csv
index 4a09112acc..cfae60a456 100644
--- a/BondageClub/Screens/Character/Title/Text_Title.csv
+++ b/BondageClub/Screens/Character/Title/Text_Title.csv
@@ -2,6 +2,8 @@ SelectTitle,Select your displayed name and title
 CurrentTitle,Current title: TITLE
 Nickname,Nickname (letters and spaces)
 NicknameLocked,Nickname locked by your owner
+NicknameTooLong,Nickname is too long (max. 20 characters)
+NicknameInvalidChars,Nickname contains invalid characters
 TitleNone,None
 TitleMistress,Mistress
 TitleClubSlave,Club Slave
diff --git a/BondageClub/Screens/Character/Title/Title.js b/BondageClub/Screens/Character/Title/Title.js
index b6a0b55d9e..20d6887e3d 100644
--- a/BondageClub/Screens/Character/Title/Title.js
+++ b/BondageClub/Screens/Character/Title/Title.js
@@ -61,6 +61,8 @@ var TitleList = [
 	{ Name: "Drone", Requirement: function () { return (AsylumGGTSGetLevel(Player) >= 6); }, Earned: true }
 ];
 var TitleCanEditNickname = true;
+/** @type {string} */
+var TitleNicknameStatus = null;
 let TitleOffset = 0;
 let TitleListFiltered = [];
 const TitlePerPage = 28;
@@ -145,6 +147,7 @@ function TitleLoad() {
 		E.removeAttribute("onfocus");
 		E.setAttribute("readonly", "readonly");
 	}
+	TitleNicknameStatus = null;
 }
 
 /**
@@ -159,8 +162,10 @@ function TitleRun() {
 	DrawText(TextGet("CurrentTitle").replace("TITLE", TextGet("Title" + (Player.Title || "None"))), 300, 100, "Black", "Gray");
 	DrawText(TextGet(TitleCanEditNickname ? "Nickname" : "NicknameLocked"), 750, 180, "Black", "Gray");
 	ElementPosition("InputNickname", 1300, 175, 500, 60);
+	if (TitleNicknameStatus)
+		DrawText(TextGet(TitleNicknameStatus), 1000, 250, "red");
 	let X = 130;
-	let Y = 250;
+	let Y = 325;
 	for (let T = TitleOffset; T < TitleOffset + TitlePerPage && T < TitleListFiltered.length; T++) {
 		DrawButton(X, Y, 400, 65, TextGet("Title" + TitleListFiltered[T].Name), 'White', undefined, undefined, Player.Title == TitleListFiltered[T].Name);
 		X = X + 450;
@@ -191,7 +196,7 @@ function TitleClick() {
 
 	// When the user selects a title
 	let X = 130;
-	let Y = 250;
+	let Y = 325;
 	for (let T = TitleOffset; T < TitleOffset + TitlePerPage && T < TitleListFiltered.length; T++) {
 		if (MouseIn(X, Y, 400, 65)) {
 			TitleSet(TitleListFiltered[T].Name);
@@ -211,14 +216,15 @@ function TitleClick() {
  * @returns {void} - Nothing
  */
 function TitleExit() {
-	let Regex = /^[a-zA-Z\s]*$/;
 	let Nick = ElementValue("InputNickname");
 	if (Nick == null) Nick = "";
-	Nick = Nick.trim().substring(0, 20);
-	if (Regex.test(Nick)) {
-		Player.Nickname = Nick;
-		ServerAccountUpdate.QueueData({ Nickname: Nick });
-		ElementRemove("InputNickname");
-		CommonSetScreen("Character", "InformationSheet");
+	const status = CharacterSetNickname(Player, Nick);
+	if (status) {
+		TitleNicknameStatus = status;
+		return;
 	}
+
+	// Nickname was fine, return to the Info sheet
+	ElementRemove("InputNickname");
+	CommonSetScreen("Character", "InformationSheet");
 }
diff --git a/BondageClub/Scripts/Character.js b/BondageClub/Scripts/Character.js
index fad22e4e68..6909fac7c5 100644
--- a/BondageClub/Scripts/Character.js
+++ b/BondageClub/Scripts/Character.js
@@ -1784,3 +1784,32 @@ function CharacterNickname(C) {
 	if ((Nick == "") || !Regex.test(Nick)) Nick = C.Name;
 	return AsylumGGTSCharacterName(C, Nick);
 }
+
+/**
+ * Update the given character's nickname.
+ *
+ * Note that changing any nickname but yours (ie. Player) is not supported.
+ *
+ * @param {Character} C - The character to change the nickname of.
+ * @param {string} Nick - The name to use as the new nickname.
+ * @return {string} null if the nickname was valid, or an explanation for why the nickname was rejected.
+ */
+function CharacterSetNickname(C, Nick) {
+	if (!C.IsPlayer()) return null;
+
+	let Regex = /^[a-zA-Z\s]*$/;
+
+	Nick = Nick.trim();
+	if (Nick.length > 20) return "NicknameTooLong";
+
+	if (!Regex.test(Nick)) return "NicknameInvalidChars";
+
+	if (C.Nickname != Nick) {
+		C.Nickname = Nick;
+		if (C.IsPlayer()) {
+			ServerAccountUpdate.QueueData({ Nickname: Nick });
+		}
+	}
+
+	return null;
+}
-- 
GitLab


From b52512a8d083c83e7c2c84f345e0b416407ebdcf Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Emmanuel Zorg <zorgjeanbe@proton.me>
Date: Sat, 23 Jul 2022 16:41:36 +0200
Subject: [PATCH 2/4] A few QoL changes to the title screen itself

This:
- stops changing the title from exiting the screen automatically
- makes the currently set title obvious by drawing it in yellow
- aligns the current title label with the title selector itself
- only update the title on screen close, like the nickname
---
 BondageClub/Screens/Character/Title/Title.js | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/BondageClub/Screens/Character/Title/Title.js b/BondageClub/Screens/Character/Title/Title.js
index 20d6887e3d..ab7ef570f7 100644
--- a/BondageClub/Screens/Character/Title/Title.js
+++ b/BondageClub/Screens/Character/Title/Title.js
@@ -60,6 +60,7 @@ var TitleList = [
 	{ Name: "GoodSlave", Requirement: function () { return (AsylumGGTSGetLevel(Player) >= 6); }, Earned: true },
 	{ Name: "Drone", Requirement: function () { return (AsylumGGTSGetLevel(Player) >= 6); }, Earned: true }
 ];
+var TitleSelectedTitle = null;
 var TitleCanEditNickname = true;
 /** @type {string} */
 var TitleNicknameStatus = null;
@@ -139,6 +140,7 @@ function TitleIsEarned(Title) {
  * @returns {void} - Nothing
  */
 function TitleLoad() {
+	TitleSelectedTitle = TitleGet(Player);
 	TitleListFiltered = TitleList.filter(T => T.Requirement());
 	TitleCanEditNickname = (!LogQuery("BlockNickname", "OwnerRule") || (Player.Ownership == null) || (Player.Ownership.Stage !== 1));
 	let E = ElementCreateInput("InputNickname", "text", Player.Nickname, "20");
@@ -157,17 +159,24 @@ function TitleLoad() {
  */
 function TitleRun() {
 
-	// List all the available titles
 	DrawText(TextGet("SelectTitle"), 1000, 100, "Black", "Gray");
-	DrawText(TextGet("CurrentTitle").replace("TITLE", TextGet("Title" + (Player.Title || "None"))), 300, 100, "Black", "Gray");
+
+	// Draw nickname field
 	DrawText(TextGet(TitleCanEditNickname ? "Nickname" : "NicknameLocked"), 750, 180, "Black", "Gray");
 	ElementPosition("InputNickname", 1300, 175, 500, 60);
 	if (TitleNicknameStatus)
 		DrawText(TextGet(TitleNicknameStatus), 1000, 250, "red");
+
+	MainCanvas.textAlign = "left";
+	DrawText(TextGet("CurrentTitle").replace("TITLE", TextGet("Title" + (Player.Title || "None"))), 130, 285, "Black", "Gray");
+	MainCanvas.textAlign = "center";
+
+	// List all the available titles
 	let X = 130;
 	let Y = 325;
 	for (let T = TitleOffset; T < TitleOffset + TitlePerPage && T < TitleListFiltered.length; T++) {
-		DrawButton(X, Y, 400, 65, TextGet("Title" + TitleListFiltered[T].Name), 'White', undefined, undefined, Player.Title == TitleListFiltered[T].Name);
+		const isCurrentTitle = TitleSelectedTitle == TitleListFiltered[T].Name;
+		DrawButton(X, Y, 400, 65, TextGet("Title" + TitleListFiltered[T].Name), isCurrentTitle ? "yellow" : 'White', undefined, undefined, isCurrentTitle);
 		X = X + 450;
 		if (X > 1500) {
 			X = 130;
@@ -199,8 +208,7 @@ function TitleClick() {
 	let Y = 325;
 	for (let T = TitleOffset; T < TitleOffset + TitlePerPage && T < TitleListFiltered.length; T++) {
 		if (MouseIn(X, Y, 400, 65)) {
-			TitleSet(TitleListFiltered[T].Name);
-			TitleExit();
+			TitleSelectedTitle = TitleListFiltered[T].Name;
 		}
 		X = X + 450;
 		if (X > 1500) {
@@ -224,6 +232,8 @@ function TitleExit() {
 		return;
 	}
 
+	TitleSet(TitleSelectedTitle);
+
 	// Nickname was fine, return to the Info sheet
 	ElementRemove("InputNickname");
 	CommonSetScreen("Character", "InformationSheet");
-- 
GitLab


From d45a540759ade4e1f76b2f5722c1fc836ffb5cb7 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Emmanuel Zorg <zorgjeanbe@proton.me>
Date: Sat, 23 Jul 2022 16:43:32 +0200
Subject: [PATCH 3/4] Move the nickname validation regex to a global under
 Server.js

---
 BondageClub/Scripts/Character.js | 4 +---
 BondageClub/Scripts/Server.js    | 1 +
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/BondageClub/Scripts/Character.js b/BondageClub/Scripts/Character.js
index 6909fac7c5..21e103410d 100644
--- a/BondageClub/Scripts/Character.js
+++ b/BondageClub/Scripts/Character.js
@@ -1797,12 +1797,10 @@ function CharacterNickname(C) {
 function CharacterSetNickname(C, Nick) {
 	if (!C.IsPlayer()) return null;
 
-	let Regex = /^[a-zA-Z\s]*$/;
-
 	Nick = Nick.trim();
 	if (Nick.length > 20) return "NicknameTooLong";
 
-	if (!Regex.test(Nick)) return "NicknameInvalidChars";
+	if (!ServerCharacterNicknameRegex.test(Nick)) return "NicknameInvalidChars";
 
 	if (C.Nickname != Nick) {
 		C.Nickname = Nick;
diff --git a/BondageClub/Scripts/Server.js b/BondageClub/Scripts/Server.js
index e5833a7ea8..dc91082699 100644
--- a/BondageClub/Scripts/Server.js
+++ b/BondageClub/Scripts/Server.js
@@ -13,6 +13,7 @@ var ServerURL = "http://localhost:4288";
 var ServerBeep = { Message: "", Timer: 0 };
 var ServerIsConnected = false;
 var ServerReconnectCount = 0;
+var ServerCharacterNicknameRegex = /^[a-zA-Z\s]*$/;
 
 const ServerScriptMessage = "WARNING! Console scripts can break your account or steal your data. Only run scripts if " +
 	"you know what you're doing and you trust the source. See " +
-- 
GitLab


From 3838c9c2202f9d3e63e796db283f3f774372cbfd Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Emmanuel Zorg <zorgjeanbe@proton.me>
Date: Sat, 23 Jul 2022 16:54:18 +0200
Subject: [PATCH 4/4] Notify the chatroom that the character changed their
 nickname

This is inspired by !3445
---
 .../Screens/Character/Player/Dialog_Player.csv      |  1 +
 BondageClub/Scripts/Character.js                    | 13 +++++++++++++
 2 files changed, 14 insertions(+)

diff --git a/BondageClub/Screens/Character/Player/Dialog_Player.csv b/BondageClub/Screens/Character/Player/Dialog_Player.csv
index afd333a9d4..a9dafc7931 100644
--- a/BondageClub/Screens/Character/Player/Dialog_Player.csv
+++ b/BondageClub/Screens/Character/Player/Dialog_Player.csv
@@ -3466,3 +3466,4 @@ ChainClapNipplesChain,,,Style 1,,
 ChainClapNipplesChain2,,,Style 2,,
 ChainClampSetChain,,,SourceCharacter changes the chain style on DestinationCharacter chest.,,
 ChainClampSetChain2,,,SourceCharacter changes the chain style on DestinationCharacter chest.,,
+CharacterNicknameUpdated,,,OldNick is now known as NewNick.,,
diff --git a/BondageClub/Scripts/Character.js b/BondageClub/Scripts/Character.js
index 21e103410d..99ea3991ad 100644
--- a/BondageClub/Scripts/Character.js
+++ b/BondageClub/Scripts/Character.js
@@ -1803,10 +1803,23 @@ function CharacterSetNickname(C, Nick) {
 	if (!ServerCharacterNicknameRegex.test(Nick)) return "NicknameInvalidChars";
 
 	if (C.Nickname != Nick) {
+		const oldNick = C.Nickname || C.Name;
 		C.Nickname = Nick;
 		if (C.IsPlayer()) {
 			ServerAccountUpdate.QueueData({ Nickname: Nick });
 		}
+
+		if (ServerPlayerIsInChatRoom()) {
+			// When in a chatroom, send a notification that the player updated their nick
+			const Dictionary = [
+				{ Tag: "SourceCharacter", Text: CharacterNickname(C), MemberNumber: C.MemberNumber },
+				{ Tag: "DestinationCharacter", Text: CharacterNickname(C), MemberNumber: C.MemberNumber },
+				{ Tag: "OldNick", Text: oldNick },
+				{ Tag: "NewNick", Text: CharacterNickname(C) },
+			];
+
+			ServerSend("ChatRoomChat", { Content: "CharacterNicknameUpdated", Type: "Action", Dictionary: Dictionary });
+		}
 	}
 
 	return null;
-- 
GitLab