diff --git a/Content.Tests/DMProject/Tests/Text/StringInterpolation9.dm b/Content.Tests/DMProject/Tests/Text/StringInterpolation9.dm new file mode 100644 index 0000000000..9ff619a52a --- /dev/null +++ b/Content.Tests/DMProject/Tests/Text/StringInterpolation9.dm @@ -0,0 +1,39 @@ +/datum/thing + var/name = "thing" + +/datum/Thing + var/name = "Thing" + +/datum/proper_thing + var/name = "\proper thing" + +/datum/plural_things + var/name = "things" + var/gender = PLURAL + +/proc/RunTest() + // Lowercase \a on datums + ASSERT("\a [new /datum/thing]" == "a thing") + ASSERT("\a [new /datum/Thing]" == "Thing") + ASSERT("\a [new /datum/proper_thing]" == "thing") + ASSERT("\a [new /datum/plural_things]" == "some things") + + // Uppercase \A on datums + ASSERT("\A [new /datum/thing]" == "A thing") + ASSERT("\A [new /datum/Thing]" == "Thing") + ASSERT("\A [new /datum/proper_thing]" == "thing") + ASSERT("\A [new /datum/plural_things]" == "Some things") + + // Lowercase \a on strings + ASSERT("\a ["thing"]" == "a thing") + ASSERT("\a ["Thing"]" == "Thing") + ASSERT("\a ["\proper thing"]" == "thing") + + // Uppercase \A on strings + ASSERT("\A ["thing"]" == "A thing") + ASSERT("\A ["Thing"]" == "Thing") + ASSERT("\A ["\proper thing"]" == "thing") + + // Invalid \a + ASSERT("\a [123]" == "") + ASSERT("\A [123]" == "") \ No newline at end of file diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index 305ec32b0e..3d054eb781 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -276,8 +276,6 @@ public static bool StringIsProper(string str) { return true; case StringFormatEncoder.FormatSuffix.Improper: return false; - default: - break; } } @@ -312,9 +310,7 @@ public string GetDisplayName(StringFormatEncoder.FormatSuffix? suffix = null) { if (this is DreamObjectClient client) return client.Connection.Session!.Name; - if (!TryGetVariable("name", out DreamValue nameVar) || !nameVar.TryGetValueAsString(out string? name)) - return ObjectDefinition.Type.ToString(); - + var name = GetRawName(); bool isProper = StringIsProper(name); name = StringFormatEncoder.RemoveFormatting(name); // TODO: Care about other formatting macros for obj names beyond \proper & \improper if(!isProper) { @@ -335,9 +331,18 @@ public string GetDisplayName(StringFormatEncoder.FormatSuffix? suffix = null) { /// Similar to except it just returns the name as plaintext, with formatting removed. No article or anything. /// public string GetNameUnformatted() { + return StringFormatEncoder.RemoveFormatting(GetRawName()); + } + + /// + /// Returns the name of this object with no formatting evaluated + /// + /// + public string GetRawName() { if (!TryGetVariable("name", out DreamValue nameVar) || !nameVar.TryGetValueAsString(out string? name)) - return ObjectDefinition?.Type.ToString() ?? String.Empty; - return StringFormatEncoder.RemoveFormatting(name); + return ObjectDefinition.Type.ToString(); + + return name; } #endregion Name Helpers diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index f9b8cab3ca..0043d5c807 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -372,26 +372,28 @@ public static ProcStatus FormatString(DMProcState state) { nextInterpIndex++; continue; } - case StringFormatEncoder.FormatSuffix.StringifyNoArticle: - { - if (interps[nextInterpIndex].TryGetValueAsDreamObject(out var dreamObject) && dreamObject != null) { + case StringFormatEncoder.FormatSuffix.StringifyNoArticle: { + if (interps[nextInterpIndex].TryGetValueAsDreamObject(out var dreamObject)) { formattedString.Append(dreamObject.GetNameUnformatted()); + } else if (interps[nextInterpIndex].TryGetValueAsString(out var interpStr)) { + formattedString.Append(StringFormatEncoder.RemoveFormatting(interpStr)); } // NOTE probably should put this above the TryGetAsDreamObject function and continue if formatting has occured if(postPrefix != null) { // Cursed Hack - switch(postPrefix) { + switch (postPrefix) { case StringFormatEncoder.FormatSuffix.LowerRoman: ToRoman(ref formattedString, interps, nextInterpIndex, false); break; case StringFormatEncoder.FormatSuffix.UpperRoman: ToRoman(ref formattedString, interps, nextInterpIndex, true); break; - default: break; } + postPrefix = null; } - //Things that aren't objects just print nothing in this case + + //Things that aren't objects or strings just print nothing in this case prevInterpIndex = nextInterpIndex; nextInterpIndex++; continue; @@ -414,33 +416,40 @@ public static ProcStatus FormatString(DMProcState state) { continue; } case StringFormatEncoder.FormatSuffix.UpperIndefiniteArticle: - case StringFormatEncoder.FormatSuffix.LowerIndefiniteArticle: - { - bool wasCapital = formatType == StringFormatEncoder.FormatSuffix.UpperIndefiniteArticle; // saves some wordiness with the ternaries below - if (interps[nextInterpIndex].TryGetValueAsDreamObject(out var dreamObject) && dreamObject != null) - { - bool hasName = dreamObject.TryGetVariable("name", out var objectName); - string nameStr = objectName.Stringify(); - if (!hasName) continue; // datums that lack a name var don't use articles - if (DreamObject.StringIsProper(nameStr)) continue; // Proper nouns don't need articles, I guess. - - if (dreamObject.TryGetVariable("gender", out var gender)) // Aayy babe whats ya pronouns - { - if (gender.TryGetValueAsString(out var str) && str == "plural") // NOTE: In Byond, this part does not work if var/gender is not a native property of this object. - { - formattedString.Append(wasCapital ? "Some" : "some"); - continue; - } - } - if (DreamObject.StringStartsWithVowel(nameStr)) - { - formattedString.Append(wasCapital ? "An " : "an "); - continue; + case StringFormatEncoder.FormatSuffix.LowerIndefiniteArticle: { + var interpValue = interps[nextInterpIndex]; + string displayName; + bool isPlural = false; + + if (interpValue.TryGetValueAsDreamObject(out var dreamObject)) { + displayName = dreamObject.GetRawName(); + + // Aayy babe whats ya pronouns + if (dreamObject.TryGetVariable("gender", out var gender) && + gender.TryGetValueAsString(out var genderStr)) { + // NOTE: In Byond, this part does not work if var/gender is not a native property of this object. + isPlural = (genderStr == "plural"); } - formattedString.Append(wasCapital ? "A " : "a "); - continue; + } else if (interpValue.TryGetValueAsString(out var interpStr)) { + displayName = interpStr; + } else { + break; } - continue; + + if (DreamObject.StringIsProper(displayName)) + break; // Proper nouns don't need articles, I guess. + + // saves some wordiness with the ternaries below + bool wasCapital = formatType == StringFormatEncoder.FormatSuffix.UpperIndefiniteArticle; + + if (isPlural) + formattedString.Append(wasCapital ? "Some " : "some "); + else if (DreamObject.StringStartsWithVowel(displayName)) + formattedString.Append(wasCapital ? "An " : "an "); + else + formattedString.Append(wasCapital ? "A " : "a "); + + break; } //Suffix macros case StringFormatEncoder.FormatSuffix.UpperSubjectPronoun: