From f3a878439d3321c42acd32b498e22484b74614e8 Mon Sep 17 00:00:00 2001
From: XingYeFish <154997195+XingYeNotFish@users.noreply.github.com>
Date: Thu, 14 Aug 2025 16:12:28 +0800
Subject: [PATCH 1/2] refactor: CustomScpTermination
---
EXILED/Exiled.API/Features/Cassie.cs | 44 +++++++++++++++++-----------
1 file changed, 27 insertions(+), 17 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Cassie.cs b/EXILED/Exiled.API/Features/Cassie.cs
index 0730f9a5a9..88707e4365 100644
--- a/EXILED/Exiled.API/Features/Cassie.cs
+++ b/EXILED/Exiled.API/Features/Cassie.cs
@@ -21,8 +21,7 @@ namespace Exiled.API.Features
using Respawning;
- using CustomFirearmHandler = DamageHandlers.FirearmDamageHandler;
- using CustomHandlerBase = DamageHandlers.DamageHandlerBase;
+ using CustomHandler = DamageHandlers.CustomDamageHandler;
///
/// A set of tools to use in-game C.A.S.S.I.E.
@@ -140,25 +139,36 @@ public static void ScpTermination(Player scp, DamageHandlerBase info)
=> NineTailedFoxAnnouncer.AnnounceScpTermination(scp.ReferenceHub, info);
///
- /// Announces the termination of a custom SCP name.
+ /// Announces the termination of a custom SCP Number.
///
- /// SCP Name. Note that for larger numbers, C.A.S.S.I.E will pronounce the place (eg. "457" -> "four hundred fifty seven"). Spaces can be used to prevent this behavior.
+ /// SCP Number. Note that for larger numbers, C.A.S.S.I.E will pronounce the place (eg. "457" -> "four hundred fifty seven"). Spaces can be used to prevent this behavior.
/// Hit Information.
- public static void CustomScpTermination(string scpName, CustomHandlerBase info)
+ public static void CustomScpTermination(string scpNumber, CustomHandler info)
{
- string result = scpName;
- if (info.Is(out MicroHidDamageHandler _))
- result += " SUCCESSFULLY TERMINATED BY AUTOMATIC SECURITY SYSTEM";
- else if (info.Is(out WarheadDamageHandler _))
- result += " SUCCESSFULLY TERMINATED BY ALPHA WARHEAD";
- else if (info.Is(out UniversalDamageHandler _))
- result += " LOST IN DECONTAMINATION SEQUENCE";
- else if (info.BaseIs(out CustomFirearmHandler firearmDamageHandler) && firearmDamageHandler.Attacker is Player attacker)
- result += " CONTAINEDSUCCESSFULLY " + ConvertTeam(attacker.Role.Team, attacker.UnitName);
-
- // result += "To be changed";
+ if (info is null)
+ throw new System.ArgumentNullException(nameof(info));
+
+ string result = "SCP " + scpNumber;
+ if (info.Attacker is null)
+ {
+ result += info.Type switch
+ {
+ Enums.DamageType.Warhead => " SUCCESSFULLY TERMINATED BY ALPHA WARHEAD",
+ Enums.DamageType.Decontamination => " LOST IN DECONTAMINATION SEQUENCE",
+ Enums.DamageType.Tesla => " SUCCESSFULLY TERMINATED BY AUTOMATIC SECURITY SYSTEM",
+ _ => " SUCCESSFULLY TERMINATED . TERMINATION CAUSE UNSPECIFIED",
+ };
+ }
else
- result += " SUCCESSFULLY TERMINATED . TERMINATION CAUSE UNSPECIFIED";
+ {
+ result += info.Attacker.Role.Team switch
+ {
+ Team.SCPs => " TERMINATED BY SCP " + string.Join(" ", info.Attacker.Role.Name.Remove(0, 4).ToCharArray()),
+ Team.Flamingos => " TERMINATED BY SCP 1 5 0 7",
+ Team.OtherAlive or Team.Dead => " SUCCESSFULLY TERMINATED . TERMINATION CAUSE UNSPECIFIED",
+ _ => " CONTAINEDSUCCESSFULLY " + ConvertTeam(info.Attacker.Role.Team, info.Attacker.UnitName),
+ };
+ }
float num = AlphaWarheadController.TimeUntilDetonation <= 0f ? 3.5f : 1f;
GlitchyMessage(result, UnityEngine.Random.Range(0.1f, 0.14f) * num, UnityEngine.Random.Range(0.07f, 0.08f) * num);
From f9860f6f27a49d5a3969a23e62917867dcee2d9b Mon Sep 17 00:00:00 2001
From: XingYeFish <154997195+XingYeNotFish@users.noreply.github.com>
Date: Sat, 20 Dec 2025 07:06:12 +0800
Subject: [PATCH 2/2] for new cassie update
---
EXILED/Exiled.API/Features/Cassie.cs | 116 +++++++++++++++++++--------
1 file changed, 82 insertions(+), 34 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Cassie.cs b/EXILED/Exiled.API/Features/Cassie.cs
index 88707e4365..21c54516c4 100644
--- a/EXILED/Exiled.API/Features/Cassie.cs
+++ b/EXILED/Exiled.API/Features/Cassie.cs
@@ -7,19 +7,19 @@
namespace Exiled.API.Features
{
+ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Exiled.API.Features.Pools;
-
+ using global::Cassie;
+ using global::Cassie.Interpreters;
using MEC;
-
using PlayerRoles;
-
using PlayerStatsSystem;
-
using Respawning;
+ using Respawning.NamingRules;
using CustomHandler = DamageHandlers.CustomDamageHandler;
@@ -28,20 +28,15 @@ namespace Exiled.API.Features
///
public static class Cassie
{
- ///
- /// Gets the singleton.
- ///
- public static NineTailedFoxAnnouncer Announcer => NineTailedFoxAnnouncer.singleton;
-
///
/// Gets a value indicating whether C.A.S.S.I.E is currently announcing. Does not include decontamination or Alpha Warhead Messages.
///
- public static bool IsSpeaking => Announcer.queue.Count != 0;
+ public static bool IsSpeaking => CassieAnnouncementDispatcher.AllAnnouncements.Count != 0;
///
- /// Gets a of objects that C.A.S.S.I.E recognizes.
+ /// Gets a of objects that C.A.S.S.I.E recognizes.
///
- public static IReadOnlyCollection VoiceLines => Announcer.voiceLines;
+ public static IReadOnlyCollection VoiceLines => CassieAnnouncementDispatcher.AllAnnouncements;
///
/// Reproduce a non-glitched C.A.S.S.I.E message.
@@ -51,7 +46,7 @@ public static class Cassie
/// Indicates whether C.A.S.S.I.E has to make noises during the message.
/// Indicates whether C.A.S.S.I.E has to make subtitles.
public static void Message(string message, bool isHeld = false, bool isNoisy = true, bool isSubtitles = false) =>
- RespawnEffectsController.PlayCassieAnnouncement(message, isHeld, isNoisy, isSubtitles);
+ new CassieAnnouncement(new CassieTtsPayload(message, isSubtitles, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue();
///
/// Reproduce a non-glitched C.A.S.S.I.E message with a possibility to custom the subtitles.
@@ -63,14 +58,7 @@ public static void Message(string message, bool isHeld = false, bool isNoisy = t
/// Indicates whether C.A.S.S.I.E has to make subtitles.
public static void MessageTranslated(string message, string translation, bool isHeld = false, bool isNoisy = true, bool isSubtitles = true)
{
- StringBuilder announcement = StringBuilderPool.Pool.Get();
- string[] cassies = message.Split('\n');
- string[] translations = translation.Split('\n');
- for (int i = 0; i < cassies.Length; i++)
- announcement.Append($"{translations[i].Replace(' ', ' ')} {cassies[i]} ");
-
- RespawnEffectsController.PlayCassieAnnouncement(announcement.ToString(), isHeld, isNoisy, isSubtitles);
- StringBuilderPool.Pool.Return(announcement);
+ new CassieAnnouncement(new CassieTtsPayload(message, translation, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue();
}
///
@@ -80,7 +68,7 @@ public static void MessageTranslated(string message, string translation, bool is
/// The chance of placing a glitch between each word.
/// The chance of jamming each word.
public static void GlitchyMessage(string message, float glitchChance, float jamChance) =>
- Announcer.ServerOnlyAddGlitchyPhrase(message, glitchChance, jamChance);
+ new CassieAnnouncement(new CassieTtsPayload(CassieGlitchifier.Glitchify(message, glitchChance, jamChance), true, true), 0f, 0f).AddToQueue();
///
/// Reproduce a non-glitched C.A.S.S.I.E message after a certain amount of seconds.
@@ -91,7 +79,7 @@ public static void GlitchyMessage(string message, float glitchChance, float jamC
/// Indicates whether C.A.S.S.I.E has to make noises during the message.
/// Indicates whether C.A.S.S.I.E has to make subtitles.
public static void DelayedMessage(string message, float delay, bool isHeld = false, bool isNoisy = true, bool isSubtitles = false) =>
- Timing.CallDelayed(delay, () => RespawnEffectsController.PlayCassieAnnouncement(message, isHeld, isNoisy, isSubtitles));
+ Timing.CallDelayed(delay, () => new CassieAnnouncement(new CassieTtsPayload(message, isSubtitles, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue());
///
/// Reproduce a glitchy C.A.S.S.I.E announcement after a certain period of seconds.
@@ -101,17 +89,53 @@ public static void DelayedMessage(string message, float delay, bool isHeld = fal
/// The chance of placing a glitch between each word.
/// The chance of jamming each word.
public static void DelayedGlitchyMessage(string message, float delay, float glitchChance, float jamChance) =>
- Timing.CallDelayed(delay, () => Announcer.ServerOnlyAddGlitchyPhrase(message, glitchChance, jamChance));
+ Timing.CallDelayed(delay, () => new CassieAnnouncement(new CassieTtsPayload(CassieGlitchifier.Glitchify(message, glitchChance, jamChance), true, true), 0f, 0f).AddToQueue());
///
/// Calculates the duration of a C.A.S.S.I.E message.
///
/// The message, which duration will be calculated.
- /// Determines if a number won't be converted to its full pronunciation.
- /// The speed of the message.
+ /// An obsolete parameter.
+ /// Another obsolete parameter.
/// Duration (in seconds) of specified message.
- public static float CalculateDuration(string message, bool rawNumber = false, float speed = 1f)
- => Announcer.CalculateDuration(message, rawNumber, speed);
+ [Obsolete("Please use CalculateDuration(string)", true)]
+ public static float CalculateDuration(string message, bool obsolete1, float obsolete2) => CalculateDuration(message);
+
+ ///
+ /// Calculates the duration of a C.A.S.S.I.E message.
+ ///
+ /// The message, which duration will be calculated.
+ /// Duration (in seconds) of specified message.
+ public static float CalculateDuration(string message)
+ {
+ if (!CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase))
+ {
+ return 0;
+ }
+
+ float value = 0;
+ string[] lines = message.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+
+ CassiePlaybackModifiers modifiers = new();
+ StringBuilder builder = StringBuilderPool.Pool.Get();
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ foreach (CassieInterpreter interpreter in CassieTtsAnnouncer.Interpreters)
+ {
+ bool halt;
+ foreach (CassieInterpreter.Result result in interpreter.GetResults(cassieLineDatabase, ref modifiers, lines[i], builder, out halt))
+ {
+ value += (float)result.Modifiers.GetTimeUntilNextWord(result.Line);
+ }
+
+ if (halt)
+ break;
+ }
+ }
+
+ return value;
+ }
///
/// Converts a into a Cassie-Readable CONTAINMENTUNIT.
@@ -119,8 +143,16 @@ public static float CalculateDuration(string message, bool rawNumber = false, fl
/// .
/// Unit Name.
/// Containment Unit text.
- public static string ConvertTeam(Team team, string unitName)
- => NineTailedFoxAnnouncer.ConvertTeam(team, unitName);
+ public static string ConvertTeam(Team team, string unitName) => team switch
+ {
+ Team.FoundationForces when NamingRulesManager.TryGetNamingRule(team, out UnitNamingRule unitNamingRule) => "CONTAINMENTUNIT " + unitNamingRule.TranslateToCassie(unitName),
+ Team.FoundationForces => "CONTAINMENTUNIT UNKNOWN",
+ Team.ChaosInsurgency => "BY CHAOSINSURGENCY",
+ Team.Scientists => "BY SCIENCE PERSONNEL",
+ Team.ClassD => "BY CLASSD PERSONNEL",
+ Team.Flamingos => "BY FLAMINGOS",
+ _ => "UNKNOWN",
+ };
///
/// Converts a number into a Cassie-Readable String.
@@ -128,7 +160,23 @@ public static string ConvertTeam(Team team, string unitName)
/// Number to convert.
/// A CASSIE-readable representing the number.
public static string ConvertNumber(int num)
- => NineTailedFoxAnnouncer.ConvertNumber(num);
+ {
+ if (!CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase))
+ {
+ return string.Empty;
+ }
+
+ NumberInterpreter numberInterpreter = (NumberInterpreter)CassieTtsAnnouncer.Interpreters.FirstOrDefault((CassieInterpreter x) => x is NumberInterpreter);
+ if (numberInterpreter == null)
+ {
+ return string.Empty;
+ }
+
+ CassiePlaybackModifiers cassiePlaybackModifiers = default;
+ StringBuilder stringBuilder = new();
+ numberInterpreter.GetResults(cassieLineDatabase, ref cassiePlaybackModifiers, num.ToString(), stringBuilder, out bool flag);
+ return stringBuilder.ToString();
+ }
///
/// Announce a SCP Termination.
@@ -136,7 +184,7 @@ public static string ConvertNumber(int num)
/// SCP to announce termination of.
/// HitInformation.
public static void ScpTermination(Player scp, DamageHandlerBase info)
- => NineTailedFoxAnnouncer.AnnounceScpTermination(scp.ReferenceHub, info);
+ => CassieScpTerminationAnnouncement.AnnounceScpTermination(scp.ReferenceHub, info);
///
/// Announces the termination of a custom SCP Number.
@@ -177,14 +225,14 @@ public static void CustomScpTermination(string scpNumber, CustomHandler info)
///
/// Clears the C.A.S.S.I.E queue.
///
- public static void Clear() => RespawnEffectsController.ClearQueue();
+ public static void Clear() => CassieAnnouncementDispatcher.ClearAll();
///
/// Gets a value indicating whether the given word is a valid C.A.S.S.I.E word.
///
/// The word to check.
/// if the word is valid; otherwise, .
- public static bool IsValid(string word) => Announcer.voiceLines.Any(line => line.apiName.ToUpper() == word.ToUpper());
+ public static bool IsValid(string word) => CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase) ? cassieLineDatabase.AllLines.Any(line => line.ApiName.ToUpper() == word.ToUpper()) : false;
///
/// Gets a value indicating whether the given sentence is all valid C.A.S.S.I.E word.