2023-12-22 09:13:45 -05:00
using System.Collections.Frozen ;
2023-01-25 17:29:41 +01:00
using Content.Shared.Chat.Prototypes ;
2024-04-29 07:38:23 +03:00
using Content.Shared.Speech ;
2023-01-25 17:29:41 +01:00
using Robust.Shared.Prototypes ;
using Robust.Shared.Random ;
namespace Content.Server.Chat.Systems ;
// emotes using emote prototype
public partial class ChatSystem
{
2023-12-22 09:13:45 -05:00
private FrozenDictionary < string , EmotePrototype > _wordEmoteDict = FrozenDictionary < string , EmotePrototype > . Empty ;
2023-01-25 17:29:41 +01:00
2023-12-22 09:13:45 -05:00
protected override void OnPrototypeReload ( PrototypesReloadedEventArgs obj )
2023-01-25 17:29:41 +01:00
{
2023-12-22 09:13:45 -05:00
base . OnPrototypeReload ( obj ) ;
if ( obj . WasModified < EmotePrototype > ( ) )
CacheEmotes ( ) ;
2023-01-25 17:29:41 +01:00
}
private void CacheEmotes ( )
{
2023-12-22 09:13:45 -05:00
var dict = new Dictionary < string , EmotePrototype > ( ) ;
2023-01-25 17:29:41 +01:00
var emotes = _prototypeManager . EnumeratePrototypes < EmotePrototype > ( ) ;
foreach ( var emote in emotes )
{
foreach ( var word in emote . ChatTriggers )
{
var lowerWord = word . ToLower ( ) ;
2023-12-22 09:13:45 -05:00
if ( dict . TryGetValue ( lowerWord , out var value ) )
2023-01-25 17:29:41 +01:00
{
2023-12-22 09:13:45 -05:00
var errMsg = $"Duplicate of emote word {lowerWord} in emotes {emote.ID} and {value.ID}" ;
Log . Error ( errMsg ) ;
2023-01-25 17:29:41 +01:00
continue ;
}
2023-12-22 09:13:45 -05:00
dict . Add ( lowerWord , emote ) ;
2023-01-25 17:29:41 +01:00
}
}
2023-12-22 09:13:45 -05:00
_wordEmoteDict = dict . ToFrozenDictionary ( ) ;
2023-01-25 17:29:41 +01:00
}
/// <summary>
/// Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
/// </summary>
/// <param name="source">The entity that is speaking</param>
/// <param name="emoteId">The id of emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
/// <param name="hideLog">Whether or not this message should appear in the adminlog window</param>
2023-05-04 20:08:08 +01:00
/// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</param>
2023-01-25 17:29:41 +01:00
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
2024-06-03 18:45:00 -05:00
/// <param name="forceEmote">Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote</param>
2023-08-11 22:56:34 -07:00
public void TryEmoteWithChat (
EntityUid source ,
string emoteId ,
ChatTransmitRange range = ChatTransmitRange . Normal ,
bool hideLog = false ,
string? nameOverride = null ,
2024-06-03 18:45:00 -05:00
bool ignoreActionBlocker = false ,
bool forceEmote = false
2023-08-11 22:56:34 -07:00
)
2023-01-25 17:29:41 +01:00
{
if ( ! _prototypeManager . TryIndex < EmotePrototype > ( emoteId , out var proto ) )
return ;
2024-06-03 18:45:00 -05:00
TryEmoteWithChat ( source , proto , range , hideLog : hideLog , nameOverride , ignoreActionBlocker : ignoreActionBlocker , forceEmote : forceEmote ) ;
2023-01-25 17:29:41 +01:00
}
/// <summary>
/// Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
/// </summary>
/// <param name="source">The entity that is speaking</param>
/// <param name="emote">The emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
/// <param name="hideLog">Whether or not this message should appear in the adminlog window</param>
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
2023-05-04 20:08:08 +01:00
/// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</param>
2023-01-25 17:29:41 +01:00
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
2024-06-03 18:45:00 -05:00
/// <param name="forceEmote">Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote</param>
2023-08-11 22:56:34 -07:00
public void TryEmoteWithChat (
EntityUid source ,
EmotePrototype emote ,
ChatTransmitRange range = ChatTransmitRange . Normal ,
bool hideLog = false ,
string? nameOverride = null ,
2024-06-03 18:45:00 -05:00
bool ignoreActionBlocker = false ,
bool forceEmote = false
2023-08-11 22:56:34 -07:00
)
2023-01-25 17:29:41 +01:00
{
2024-06-03 18:45:00 -05:00
if ( ! forceEmote & & ! AllowedToUseEmote ( source , emote ) )
2024-04-29 07:38:23 +03:00
return ;
2023-01-25 17:29:41 +01:00
// check if proto has valid message for chat
if ( emote . ChatMessages . Count ! = 0 )
{
2023-08-11 22:56:34 -07:00
// not all emotes are loc'd, but for the ones that are we pass in entity
var action = Loc . GetString ( _random . Pick ( emote . ChatMessages ) , ( "entity" , source ) ) ;
2023-08-18 23:59:23 +10:00
SendEntityEmote ( source , action , range , nameOverride , hideLog : hideLog , checkEmote : false , ignoreActionBlocker : ignoreActionBlocker ) ;
2023-01-25 17:29:41 +01:00
}
// do the rest of emote event logic here
2023-08-11 22:56:34 -07:00
TryEmoteWithoutChat ( source , emote , ignoreActionBlocker ) ;
2023-01-25 17:29:41 +01:00
}
/// <summary>
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
/// </summary>
2023-08-11 22:56:34 -07:00
public void TryEmoteWithoutChat ( EntityUid uid , string emoteId , bool ignoreActionBlocker = false )
2023-01-25 17:29:41 +01:00
{
if ( ! _prototypeManager . TryIndex < EmotePrototype > ( emoteId , out var proto ) )
return ;
2023-08-11 22:56:34 -07:00
TryEmoteWithoutChat ( uid , proto , ignoreActionBlocker ) ;
2023-01-25 17:29:41 +01:00
}
/// <summary>
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
/// </summary>
2023-08-11 22:56:34 -07:00
public void TryEmoteWithoutChat ( EntityUid uid , EmotePrototype proto , bool ignoreActionBlocker = false )
2023-01-25 17:29:41 +01:00
{
2023-08-11 22:56:34 -07:00
if ( ! _actionBlocker . CanEmote ( uid ) & & ! ignoreActionBlocker )
2023-01-25 17:29:41 +01:00
return ;
InvokeEmoteEvent ( uid , proto ) ;
}
/// <summary>
/// Tries to find and play relevant emote sound in emote sounds collection.
/// </summary>
/// <returns>True if emote sound was played.</returns>
public bool TryPlayEmoteSound ( EntityUid uid , EmoteSoundsPrototype ? proto , EmotePrototype emote )
{
return TryPlayEmoteSound ( uid , proto , emote . ID ) ;
}
/// <summary>
/// Tries to find and play relevant emote sound in emote sounds collection.
/// </summary>
/// <returns>True if emote sound was played.</returns>
public bool TryPlayEmoteSound ( EntityUid uid , EmoteSoundsPrototype ? proto , string emoteId )
{
if ( proto = = null )
return false ;
// try to get specific sound for this emote
if ( ! proto . Sounds . TryGetValue ( emoteId , out var sound ) )
{
// no specific sound - check fallback
sound = proto . FallbackSound ;
if ( sound = = null )
return false ;
}
// if general params for all sounds set - use them
var param = proto . GeneralParams ? ? sound . Params ;
_audio . PlayPvs ( sound , uid , param ) ;
return true ;
}
2024-06-03 18:45:00 -05:00
/// <summary>
/// Checks if a valid emote was typed, to play sounds and etc and invokes an event.
/// </summary>
/// <param name="uid"></param>
/// <param name="textInput"></param>
2023-01-25 17:29:41 +01:00
private void TryEmoteChatInput ( EntityUid uid , string textInput )
{
2024-08-19 00:49:07 +02:00
var actionTrimmedLower = TrimPunctuation ( textInput . ToLower ( ) ) ;
if ( ! _wordEmoteDict . TryGetValue ( actionTrimmedLower , out var emote ) )
2023-01-25 17:29:41 +01:00
return ;
2024-06-03 18:45:00 -05:00
if ( ! AllowedToUseEmote ( uid , emote ) )
return ;
2023-01-25 17:29:41 +01:00
InvokeEmoteEvent ( uid , emote ) ;
2024-08-19 00:49:07 +02:00
return ;
static string TrimPunctuation ( string textInput )
{
var trimEnd = textInput . Length ;
while ( trimEnd > 0 & & char . IsPunctuation ( textInput [ trimEnd - 1 ] ) )
{
trimEnd - - ;
}
var trimStart = 0 ;
while ( trimStart < trimEnd & & char . IsPunctuation ( textInput [ trimStart ] ) )
{
trimStart + + ;
}
return textInput [ trimStart . . trimEnd ] ;
}
2023-01-25 17:29:41 +01:00
}
2024-06-03 18:45:00 -05:00
/// <summary>
/// Checks if we can use this emote based on the emotes whitelist, blacklist, and availibility to the entity.
/// </summary>
/// <param name="source">The entity that is speaking</param>
/// <param name="emote">The emote being used</param>
/// <returns></returns>
private bool AllowedToUseEmote ( EntityUid source , EmotePrototype emote )
{
2025-02-17 22:59:33 +01:00
// If emote is in AllowedEmotes, it will bypass whitelist and blacklist
if ( TryComp < SpeechComponent > ( source , out var speech ) & &
speech . AllowedEmotes . Contains ( emote . ID ) )
{
return true ;
}
// Check the whitelist and blacklist
if ( _whitelistSystem . IsWhitelistFail ( emote . Whitelist , source ) | |
_whitelistSystem . IsBlacklistPass ( emote . Blacklist , source ) )
{
2024-06-03 18:45:00 -05:00
return false ;
2025-02-17 22:59:33 +01:00
}
2024-06-03 18:45:00 -05:00
2025-02-17 22:59:33 +01:00
// Check if the emote is available for all
if ( ! emote . Available )
{
2024-06-03 18:45:00 -05:00
return false ;
2025-02-17 22:59:33 +01:00
}
2024-06-03 18:45:00 -05:00
return true ;
}
2023-01-25 17:29:41 +01:00
2025-02-17 22:59:33 +01:00
2023-01-25 17:29:41 +01:00
private void InvokeEmoteEvent ( EntityUid uid , EmotePrototype proto )
{
var ev = new EmoteEvent ( proto ) ;
RaiseLocalEvent ( uid , ref ev ) ;
}
}
/// <summary>
/// Raised by chat system when entity made some emote.
/// Use it to play sound, change sprite or something else.
/// </summary>
[ByRefEvent]
public struct EmoteEvent
{
public bool Handled ;
public readonly EmotePrototype Emote ;
public EmoteEvent ( EmotePrototype emote )
{
Emote = emote ;
Handled = false ;
}
}