Deathgasp + last words / succumbing / fake deathgasp as crit actions (#18993)
This commit is contained in:
@@ -55,11 +55,18 @@ public partial class ChatSystem
|
|||||||
/// <param name="hideLog">Whether or not this message should appear in the adminlog window</param>
|
/// <param name="hideLog">Whether or not this message should appear in the adminlog window</param>
|
||||||
/// <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>
|
/// <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>
|
||||||
/// <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>
|
/// <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>
|
||||||
public void TryEmoteWithChat(EntityUid source, string emoteId, ChatTransmitRange range = ChatTransmitRange.Normal, bool hideLog = false, string? nameOverride = null)
|
public void TryEmoteWithChat(
|
||||||
|
EntityUid source,
|
||||||
|
string emoteId,
|
||||||
|
ChatTransmitRange range = ChatTransmitRange.Normal,
|
||||||
|
bool hideLog = false,
|
||||||
|
string? nameOverride = null,
|
||||||
|
bool ignoreActionBlocker = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||||
return;
|
return;
|
||||||
TryEmoteWithChat(source, proto, range, hideLog, nameOverride);
|
TryEmoteWithChat(source, proto, range, hideLog, nameOverride, ignoreActionBlocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -71,35 +78,44 @@ public partial class ChatSystem
|
|||||||
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
|
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
|
||||||
/// <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>
|
/// <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>
|
||||||
/// <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>
|
/// <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>
|
||||||
public void TryEmoteWithChat(EntityUid source, EmotePrototype emote, ChatTransmitRange range = ChatTransmitRange.Normal, bool hideLog = false, string? nameOverride = null)
|
public void TryEmoteWithChat(
|
||||||
|
EntityUid source,
|
||||||
|
EmotePrototype emote,
|
||||||
|
ChatTransmitRange range = ChatTransmitRange.Normal,
|
||||||
|
bool hideLog = false,
|
||||||
|
string? nameOverride = null,
|
||||||
|
bool ignoreActionBlocker = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// check if proto has valid message for chat
|
// check if proto has valid message for chat
|
||||||
if (emote.ChatMessages.Count != 0)
|
if (emote.ChatMessages.Count != 0)
|
||||||
{
|
{
|
||||||
var action = _random.Pick(emote.ChatMessages);
|
// not all emotes are loc'd, but for the ones that are we pass in entity
|
||||||
SendEntityEmote(source, action, range, nameOverride, false, hideLog);
|
var action = Loc.GetString(_random.Pick(emote.ChatMessages), ("entity", source));
|
||||||
|
SendEntityEmote(source, action, range, nameOverride, false, hideLog, ignoreActionBlocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the rest of emote event logic here
|
// do the rest of emote event logic here
|
||||||
TryEmoteWithoutChat(source, emote);
|
TryEmoteWithoutChat(source, emote, ignoreActionBlocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void TryEmoteWithoutChat(EntityUid uid, string emoteId)
|
public void TryEmoteWithoutChat(EntityUid uid, string emoteId, bool ignoreActionBlocker = false)
|
||||||
{
|
{
|
||||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||||
return;
|
return;
|
||||||
TryEmoteWithoutChat(uid, proto);
|
|
||||||
|
TryEmoteWithoutChat(uid, proto, ignoreActionBlocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto)
|
public void TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto, bool ignoreActionBlocker = false)
|
||||||
{
|
{
|
||||||
if (!_actionBlocker.CanEmote(uid))
|
if (!_actionBlocker.CanEmote(uid) && !ignoreActionBlocker)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
InvokeEmoteEvent(uid, proto);
|
InvokeEmoteEvent(uid, proto);
|
||||||
|
|||||||
@@ -137,10 +137,17 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
/// <param name="shell"></param>
|
/// <param name="shell"></param>
|
||||||
/// <param name="player">The player doing the speaking</param>
|
/// <param name="player">The player doing the speaking</param>
|
||||||
/// <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>
|
/// <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>
|
||||||
public void TrySendInGameICMessage(EntityUid source, string message, InGameICChatType desiredType, bool hideChat, bool hideLog = false,
|
public void TrySendInGameICMessage(
|
||||||
IConsoleShell? shell = null, IPlayerSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true)
|
EntityUid source,
|
||||||
|
string message,
|
||||||
|
InGameICChatType desiredType,
|
||||||
|
bool hideChat, bool hideLog = false,
|
||||||
|
IConsoleShell? shell = null,
|
||||||
|
IPlayerSession? player = null, string? nameOverride = null,
|
||||||
|
bool checkRadioPrefix = true,
|
||||||
|
bool ignoreActionBlocker = false)
|
||||||
{
|
{
|
||||||
TrySendInGameICMessage(source, message, desiredType, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, hideLog, shell, player, nameOverride, checkRadioPrefix);
|
TrySendInGameICMessage(source, message, desiredType, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, hideLog, shell, player, nameOverride, checkRadioPrefix, ignoreActionBlocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -153,8 +160,19 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
/// <param name="shell"></param>
|
/// <param name="shell"></param>
|
||||||
/// <param name="player">The player doing the speaking</param>
|
/// <param name="player">The player doing the speaking</param>
|
||||||
/// <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>
|
/// <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>
|
||||||
public void TrySendInGameICMessage(EntityUid source, string message, InGameICChatType desiredType, ChatTransmitRange range, bool hideLog = false,
|
/// <param name="ignoreActionBlocker">If set to true, action blocker will not be considered for whether an entity can send this message.</param>
|
||||||
IConsoleShell? shell = null, IPlayerSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true)
|
public void TrySendInGameICMessage(
|
||||||
|
EntityUid source,
|
||||||
|
string message,
|
||||||
|
InGameICChatType desiredType,
|
||||||
|
ChatTransmitRange range,
|
||||||
|
bool hideLog = false,
|
||||||
|
IConsoleShell? shell = null,
|
||||||
|
IPlayerSession? player = null,
|
||||||
|
string? nameOverride = null,
|
||||||
|
bool checkRadioPrefix = true,
|
||||||
|
bool ignoreActionBlocker = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (HasComp<GhostComponent>(source))
|
if (HasComp<GhostComponent>(source))
|
||||||
{
|
{
|
||||||
@@ -187,7 +205,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
// Was there an emote in the message? If so, send it.
|
// Was there an emote in the message? If so, send it.
|
||||||
if (player != null && emoteStr != message && emoteStr != null)
|
if (player != null && emoteStr != message && emoteStr != null)
|
||||||
{
|
{
|
||||||
SendEntityEmote(source, emoteStr, range, nameOverride);
|
SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This can happen if the entire string is sanitized out.
|
// This can happen if the entire string is sanitized out.
|
||||||
@@ -199,7 +217,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
{
|
{
|
||||||
if (TryProccessRadioMessage(source, message, out var modMessage, out var channel))
|
if (TryProccessRadioMessage(source, message, out var modMessage, out var channel))
|
||||||
{
|
{
|
||||||
SendEntityWhisper(source, modMessage, range, channel, nameOverride);
|
SendEntityWhisper(source, modMessage, range, channel, nameOverride, ignoreActionBlocker);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,19 +226,25 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
switch (desiredType)
|
switch (desiredType)
|
||||||
{
|
{
|
||||||
case InGameICChatType.Speak:
|
case InGameICChatType.Speak:
|
||||||
SendEntitySpeak(source, message, range, nameOverride, hideLog);
|
SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker);
|
||||||
break;
|
break;
|
||||||
case InGameICChatType.Whisper:
|
case InGameICChatType.Whisper:
|
||||||
SendEntityWhisper(source, message, range, null, nameOverride, hideLog);
|
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
|
||||||
break;
|
break;
|
||||||
case InGameICChatType.Emote:
|
case InGameICChatType.Emote:
|
||||||
SendEntityEmote(source, message, range, nameOverride, hideLog);
|
SendEntityEmote(source, message, range, nameOverride, hideLog, ignoreActionBlocker);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TrySendInGameOOCMessage(EntityUid source, string message, InGameOOCChatType type, bool hideChat,
|
public void TrySendInGameOOCMessage(
|
||||||
IConsoleShell? shell = null, IPlayerSession? player = null)
|
EntityUid source,
|
||||||
|
string message,
|
||||||
|
InGameOOCChatType type,
|
||||||
|
bool hideChat,
|
||||||
|
IConsoleShell? shell = null,
|
||||||
|
IPlayerSession? player = null
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!CanSendInGame(message, shell, player))
|
if (!CanSendInGame(message, shell, player))
|
||||||
return;
|
return;
|
||||||
@@ -262,8 +286,13 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
|
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
|
||||||
/// <param name="playSound">Play the announcement sound</param>
|
/// <param name="playSound">Play the announcement sound</param>
|
||||||
/// <param name="colorOverride">Optional color for the announcement message</param>
|
/// <param name="colorOverride">Optional color for the announcement message</param>
|
||||||
public void DispatchGlobalAnnouncement(string message, string sender = "Central Command",
|
public void DispatchGlobalAnnouncement(
|
||||||
bool playSound = true, SoundSpecifier? announcementSound = null, Color? colorOverride = null)
|
string message,
|
||||||
|
string sender = "Central Command",
|
||||||
|
bool playSound = true,
|
||||||
|
SoundSpecifier? announcementSound = null,
|
||||||
|
Color? colorOverride = null
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
|
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
|
||||||
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
|
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
|
||||||
@@ -282,8 +311,13 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
|
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
|
||||||
/// <param name="playDefaultSound">Play the announcement sound</param>
|
/// <param name="playDefaultSound">Play the announcement sound</param>
|
||||||
/// <param name="colorOverride">Optional color for the announcement message</param>
|
/// <param name="colorOverride">Optional color for the announcement message</param>
|
||||||
public void DispatchStationAnnouncement(EntityUid source, string message, string sender = "Central Command",
|
public void DispatchStationAnnouncement(
|
||||||
bool playDefaultSound = true, SoundSpecifier? announcementSound = null, Color? colorOverride = null)
|
EntityUid source,
|
||||||
|
string message,
|
||||||
|
string sender = "Central Command",
|
||||||
|
bool playDefaultSound = true,
|
||||||
|
SoundSpecifier? announcementSound = null,
|
||||||
|
Color? colorOverride = null)
|
||||||
{
|
{
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
|
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
|
||||||
var station = _stationSystem.GetOwningStation(source);
|
var station = _stationSystem.GetOwningStation(source);
|
||||||
@@ -312,9 +346,16 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
|
|
||||||
#region Private API
|
#region Private API
|
||||||
|
|
||||||
private void SendEntitySpeak(EntityUid source, string originalMessage, ChatTransmitRange range, string? nameOverride, bool hideLog = false)
|
private void SendEntitySpeak(
|
||||||
|
EntityUid source,
|
||||||
|
string originalMessage,
|
||||||
|
ChatTransmitRange range,
|
||||||
|
string? nameOverride,
|
||||||
|
bool hideLog = false,
|
||||||
|
bool ignoreActionBlocker = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!_actionBlocker.CanSpeak(source))
|
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var message = TransformSpeech(source, originalMessage);
|
var message = TransformSpeech(source, originalMessage);
|
||||||
@@ -366,9 +407,17 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendEntityWhisper(EntityUid source, string originalMessage, ChatTransmitRange range, RadioChannelPrototype? channel, string? nameOverride, bool hideLog = false)
|
private void SendEntityWhisper(
|
||||||
|
EntityUid source,
|
||||||
|
string originalMessage,
|
||||||
|
ChatTransmitRange range,
|
||||||
|
RadioChannelPrototype? channel,
|
||||||
|
string? nameOverride,
|
||||||
|
bool hideLog = false,
|
||||||
|
bool ignoreActionBlocker = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!_actionBlocker.CanSpeak(source))
|
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var message = TransformSpeech(source, originalMessage);
|
var message = TransformSpeech(source, originalMessage);
|
||||||
@@ -449,16 +498,27 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendEntityEmote(EntityUid source, string action, ChatTransmitRange range, string? nameOverride, bool hideLog = false, bool checkEmote = true)
|
private void SendEntityEmote(
|
||||||
|
EntityUid source,
|
||||||
|
string action,
|
||||||
|
ChatTransmitRange range,
|
||||||
|
string? nameOverride,
|
||||||
|
bool hideLog = false,
|
||||||
|
bool checkEmote = true,
|
||||||
|
bool ignoreActionBlocker = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!_actionBlocker.CanEmote(source)) return;
|
if (!_actionBlocker.CanEmote(source) && !ignoreActionBlocker)
|
||||||
|
return;
|
||||||
|
|
||||||
// get the entity's apparent name (if no override provided).
|
// get the entity's apparent name (if no override provided).
|
||||||
string name = FormattedMessage.EscapeText(nameOverride ?? Identity.Name(source, EntityManager));
|
var ent = Identity.Entity(source, EntityManager);
|
||||||
|
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||||
|
|
||||||
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
|
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
||||||
("entityName", name),
|
("entityName", name),
|
||||||
|
("entity", ent),
|
||||||
("message", FormattedMessage.EscapeText(action)));
|
("message", FormattedMessage.EscapeText(action)));
|
||||||
|
|
||||||
if (checkEmote)
|
if (checkEmote)
|
||||||
|
|||||||
98
Content.Server/Mobs/CritMobActionsSystem.cs
Normal file
98
Content.Server/Mobs/CritMobActionsSystem.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.Mind.Components;
|
||||||
|
using Content.Shared.Actions;
|
||||||
|
using Content.Shared.Mobs.Components;
|
||||||
|
using Content.Shared.Mobs.Systems;
|
||||||
|
using Robust.Server.Console;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Server.Mobs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles performing crit-specific actions.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CritMobActionsSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
[Dependency] private readonly DeathgaspSystem _deathgasp = default!;
|
||||||
|
[Dependency] private readonly IServerConsoleHost _host = default!;
|
||||||
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
|
[Dependency] private readonly QuickDialogSystem _quickDialog = default!;
|
||||||
|
|
||||||
|
private const int MaxLastWordsLength = 30;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MobStateActionsComponent, CritSuccumbEvent>(OnSuccumb);
|
||||||
|
SubscribeLocalEvent<MobStateActionsComponent, CritFakeDeathEvent>(OnFakeDeath);
|
||||||
|
SubscribeLocalEvent<MobStateActionsComponent, CritLastWordsEvent>(OnLastWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSuccumb(EntityUid uid, MobStateActionsComponent component, CritSuccumbEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<ActorComponent>(uid, out var actor) || !_mobState.IsCritical(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_host.ExecuteCommand(actor.PlayerSession, "ghost");
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFakeDeath(EntityUid uid, MobStateActionsComponent component, CritFakeDeathEvent args)
|
||||||
|
{
|
||||||
|
if (!_mobState.IsCritical(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = _deathgasp.Deathgasp(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLastWords(EntityUid uid, MobStateActionsComponent component, CritLastWordsEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString("action-name-crit-last-words"), "",
|
||||||
|
(string lastWords) =>
|
||||||
|
{
|
||||||
|
if (actor.PlayerSession.AttachedEntity != uid
|
||||||
|
|| !_mobState.IsCritical(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (lastWords.Length > MaxLastWordsLength)
|
||||||
|
{
|
||||||
|
lastWords = lastWords.Substring(0, MaxLastWordsLength);
|
||||||
|
}
|
||||||
|
lastWords += "...";
|
||||||
|
|
||||||
|
_chat.TrySendInGameICMessage(uid, lastWords, InGameICChatType.Whisper, ChatTransmitRange.Normal, ignoreActionBlocker: true);
|
||||||
|
_host.ExecuteCommand(actor.PlayerSession, "ghost");
|
||||||
|
});
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only applies to mobs in crit capable of ghosting/succumbing
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CritSuccumbEvent : InstantActionEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only applies/has functionality to mobs in crit that have <see cref="DeathgaspComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CritFakeDeathEvent : InstantActionEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only applies to mobs capable of speaking, as a last resort in crit
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CritLastWordsEvent : InstantActionEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
18
Content.Server/Mobs/DeathgaspComponent.cs
Normal file
18
Content.Server/Mobs/DeathgaspComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Shared.Chat.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.Mobs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mobs with this component will emote a deathgasp when they die.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="DeathgaspSystem"/>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class DeathgaspComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The emote prototype to use.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||||
|
public string Prototype = "DefaultDeathgasp";
|
||||||
|
}
|
||||||
40
Content.Server/Mobs/DeathgaspSystem.cs
Normal file
40
Content.Server/Mobs/DeathgaspSystem.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Shared.Mobs;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Mobs;
|
||||||
|
|
||||||
|
/// <see cref="DeathgaspComponent"/>
|
||||||
|
public sealed class DeathgaspSystem: EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<DeathgaspComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMobStateChanged(EntityUid uid, DeathgaspComponent component, MobStateChangedEvent args)
|
||||||
|
{
|
||||||
|
// don't deathgasp if they arent going straight from crit to dead
|
||||||
|
if (args.NewMobState != MobState.Dead || args.OldMobState != MobState.Critical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Deathgasp(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes an entity to perform their deathgasp emote, if they have one.
|
||||||
|
/// </summary>
|
||||||
|
public bool Deathgasp(EntityUid uid, DeathgaspComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_chat.TryEmoteWithChat(uid, component.Prototype, ignoreActionBlocker: true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Content.Shared/Mobs/Components/MobStateActionsComponent.cs
Normal file
27
Content.Shared/Mobs/Components/MobStateActionsComponent.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Content.Shared.Mobs.Systems;
|
||||||
|
|
||||||
|
namespace Content.Shared.Mobs.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for specifying actions that should be automatically added/removed on mob state transitions
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Mostly for crit-specific actions.
|
||||||
|
/// </remarks>
|
||||||
|
/// <see cref="MobStateActionsSystem"/>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class MobStateActionsComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a list of actions that should be available if a mob is in a given state.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// actions:
|
||||||
|
/// Critical:
|
||||||
|
/// - CritSuccumb
|
||||||
|
/// Alive:
|
||||||
|
/// - AnimalLayEgg
|
||||||
|
/// </example>
|
||||||
|
[DataField("actions")]
|
||||||
|
public Dictionary<MobState, List<string>> Actions = new();
|
||||||
|
}
|
||||||
54
Content.Shared/Mobs/Systems/MobStateActionsSystem.cs
Normal file
54
Content.Shared/Mobs/Systems/MobStateActionsSystem.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Content.Shared.Actions;
|
||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Content.Shared.Mobs.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Mobs.Systems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds and removes defined actions when a mob's <see cref="MobState"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MobStateActionsSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<MobStateActionsComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMobStateChanged(EntityUid uid, MobStateActionsComponent component, MobStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<ActionsComponent>(uid, out var action))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (state, acts) in component.Actions)
|
||||||
|
{
|
||||||
|
if (state != args.NewMobState && state != args.OldMobState)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var item in acts)
|
||||||
|
{
|
||||||
|
if (!_proto.TryIndex<InstantActionPrototype>(item, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var instance = new InstantAction(proto);
|
||||||
|
if (state == args.OldMobState)
|
||||||
|
{
|
||||||
|
// Don't remove actions that would be getting readded anyway
|
||||||
|
if (component.Actions.TryGetValue(args.NewMobState, out var value)
|
||||||
|
&& value.Contains(item))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_actions.RemoveAction(uid, instance, action);
|
||||||
|
}
|
||||||
|
else if (state == args.NewMobState)
|
||||||
|
{
|
||||||
|
_actions.AddAction(uid, instance, null, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Resources/Locale/en-US/actions/actions/crit.ftl
Normal file
9
Resources/Locale/en-US/actions/actions/crit.ftl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
action-name-crit-succumb = Succumb
|
||||||
|
action-description-crit-succumb = Accept your fate.
|
||||||
|
|
||||||
|
action-name-crit-fake-death = Fake Death
|
||||||
|
action-description-crit-fake-death = Pretend to take your final breath while staying alive.
|
||||||
|
|
||||||
|
action-name-crit-last-words = Say Last Words
|
||||||
|
action-description-crit-last-words = Whisper your last words to anyone nearby, and then succumb to your fate. You only have 30 characters to work with.
|
||||||
|
|
||||||
@@ -11,25 +11,36 @@ chat-manager-crit-looc-chat-enabled-message = Crit players can now use LOOC.
|
|||||||
chat-manager-crit-looc-chat-disabled-message = Crit players can no longer use LOOC.
|
chat-manager-crit-looc-chat-disabled-message = Crit players can no longer use LOOC.
|
||||||
chat-manager-admin-ooc-chat-enabled-message = Admin OOC chat has been enabled.
|
chat-manager-admin-ooc-chat-enabled-message = Admin OOC chat has been enabled.
|
||||||
chat-manager-admin-ooc-chat-disabled-message = Admin OOC chat has been disabled.
|
chat-manager-admin-ooc-chat-disabled-message = Admin OOC chat has been disabled.
|
||||||
|
|
||||||
chat-manager-max-message-length-exceeded-message = Your message exceeded {$limit} character limit
|
chat-manager-max-message-length-exceeded-message = Your message exceeded {$limit} character limit
|
||||||
chat-manager-no-headset-on-message = You don't have a headset on!
|
chat-manager-no-headset-on-message = You don't have a headset on!
|
||||||
chat-manager-no-radio-key = No radio key specified!
|
chat-manager-no-radio-key = No radio key specified!
|
||||||
chat-manager-no-such-channel = There is no channel with key '{$key}'!
|
chat-manager-no-such-channel = There is no channel with key '{$key}'!
|
||||||
chat-manager-whisper-headset-on-message = You can't whisper on the radio!
|
chat-manager-whisper-headset-on-message = You can't whisper on the radio!
|
||||||
|
|
||||||
chat-manager-server-wrap-message = [bold]{$message}[/bold]
|
chat-manager-server-wrap-message = [bold]{$message}[/bold]
|
||||||
chat-manager-sender-announcement-wrap-message = [font size=14][bold]{$sender} Announcement:[/font][font size=12]
|
chat-manager-sender-announcement-wrap-message = [font size=14][bold]{$sender} Announcement:[/font][font size=12]
|
||||||
{$message}[/bold][/font]
|
{$message}[/bold][/font]
|
||||||
chat-manager-entity-say-wrap-message = [bold]{$entityName}[/bold] says, "{$message}"
|
chat-manager-entity-say-wrap-message = [bold]{$entityName}[/bold] says, "{$message}"
|
||||||
|
|
||||||
chat-manager-entity-whisper-wrap-message = [font size=11][italic]{$entityName} whispers, "{$message}"[/italic][/font]
|
chat-manager-entity-whisper-wrap-message = [font size=11][italic]{$entityName} whispers, "{$message}"[/italic][/font]
|
||||||
chat-manager-entity-whisper-unknown-wrap-message = [font size=11][italic]Someone whispers, "{$message}"[/italic][/font]
|
chat-manager-entity-whisper-unknown-wrap-message = [font size=11][italic]Someone whispers, "{$message}"[/italic][/font]
|
||||||
chat-manager-entity-me-wrap-message = [italic]{$entityName} {$message}[/italic]
|
|
||||||
|
# THE() is not used here because the entity and its name can technically be disconnected if a nameOverride is passed...
|
||||||
|
chat-manager-entity-me-wrap-message = [italic]{ PROPER($entity) ->
|
||||||
|
*[false] the {$entityName} {$message}[/italic]
|
||||||
|
[true] {$entityName} {$message}[/italic]
|
||||||
|
}
|
||||||
|
|
||||||
chat-manager-entity-looc-wrap-message = LOOC: [bold]{$entityName}:[/bold] {$message}
|
chat-manager-entity-looc-wrap-message = LOOC: [bold]{$entityName}:[/bold] {$message}
|
||||||
chat-manager-send-ooc-wrap-message = OOC: [bold]{$playerName}:[/bold] {$message}
|
chat-manager-send-ooc-wrap-message = OOC: [bold]{$playerName}:[/bold] {$message}
|
||||||
chat-manager-send-ooc-patron-wrap-message = OOC: [bold][color={$patronColor}]{$playerName}[/color]:[/bold] {$message}
|
chat-manager-send-ooc-patron-wrap-message = OOC: [bold][color={$patronColor}]{$playerName}[/color]:[/bold] {$message}
|
||||||
|
|
||||||
chat-manager-send-dead-chat-wrap-message = {$deadChannelName}: [bold]{$playerName}:[/bold] {$message}
|
chat-manager-send-dead-chat-wrap-message = {$deadChannelName}: [bold]{$playerName}:[/bold] {$message}
|
||||||
chat-manager-send-admin-dead-chat-wrap-message = {$adminChannelName}: [bold]({$userName}):[/bold] {$message}
|
chat-manager-send-admin-dead-chat-wrap-message = {$adminChannelName}: [bold]({$userName}):[/bold] {$message}
|
||||||
chat-manager-send-admin-chat-wrap-message = {$adminChannelName}: [bold]{$playerName}:[/bold] {$message}
|
chat-manager-send-admin-chat-wrap-message = {$adminChannelName}: [bold]{$playerName}:[/bold] {$message}
|
||||||
chat-manager-send-admin-announcement-wrap-message = [bold]{$adminChannelName}: {$message}[/bold]
|
chat-manager-send-admin-announcement-wrap-message = [bold]{$adminChannelName}: {$message}[/bold]
|
||||||
|
|
||||||
chat-manager-send-hook-ooc-wrap-message = OOC: [bold](D){$senderName}:[/bold] {$message}
|
chat-manager-send-hook-ooc-wrap-message = OOC: [bold](D){$senderName}:[/bold] {$message}
|
||||||
|
|
||||||
chat-manager-dead-channel-name = DEAD
|
chat-manager-dead-channel-name = DEAD
|
||||||
|
|||||||
1
Resources/Locale/en-US/emotes/emotes.ftl
Normal file
1
Resources/Locale/en-US/emotes/emotes.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
emote-deathgasp = seizes up and falls limp, {POSS-ADJ($entity)} eyes dead and lifeless...
|
||||||
34
Resources/Prototypes/Actions/crit.yml
Normal file
34
Resources/Prototypes/Actions/crit.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Actions added to mobs in crit.
|
||||||
|
- type: instantAction
|
||||||
|
id: CritSuccumb
|
||||||
|
name: action-name-crit-succumb
|
||||||
|
description: action-description-crit-succumb
|
||||||
|
itemIconStyle: NoItem
|
||||||
|
checkCanInteract: false
|
||||||
|
icon:
|
||||||
|
sprite: Mobs/Ghosts/ghost_human.rsi
|
||||||
|
state: icon
|
||||||
|
serverEvent: !type:CritSuccumbEvent
|
||||||
|
|
||||||
|
- type: instantAction
|
||||||
|
id: CritFakeDeath
|
||||||
|
name: action-name-crit-fake-death
|
||||||
|
description: action-description-crit-fake-death
|
||||||
|
itemIconStyle: NoItem
|
||||||
|
checkCanInteract: false
|
||||||
|
icon:
|
||||||
|
sprite: Interface/Actions/actions_crit.rsi
|
||||||
|
state: fakedeath
|
||||||
|
serverEvent: !type:CritFakeDeathEvent
|
||||||
|
useDelay: 30
|
||||||
|
|
||||||
|
- type: instantAction
|
||||||
|
id: CritLastWords
|
||||||
|
name: action-name-crit-last-words
|
||||||
|
description: action-description-crit-last-words
|
||||||
|
itemIconStyle: NoItem
|
||||||
|
checkCanInteract: false
|
||||||
|
icon:
|
||||||
|
sprite: Interface/Actions/actions_crit.rsi
|
||||||
|
state: lastwords
|
||||||
|
serverEvent: !type:CritLastWordsEvent
|
||||||
@@ -933,6 +933,13 @@
|
|||||||
layer:
|
layer:
|
||||||
- SmallMobLayer
|
- SmallMobLayer
|
||||||
- type: MobState
|
- type: MobState
|
||||||
|
- type: Deathgasp
|
||||||
|
- type: MobStateActions
|
||||||
|
actions:
|
||||||
|
Critical:
|
||||||
|
- CritSuccumb
|
||||||
|
- CritFakeDeath
|
||||||
|
- CritLastWords
|
||||||
- type: MobThresholds
|
- type: MobThresholds
|
||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
@@ -2233,6 +2240,13 @@
|
|||||||
layer:
|
layer:
|
||||||
- SmallMobLayer
|
- SmallMobLayer
|
||||||
- type: MobState
|
- type: MobState
|
||||||
|
- type: Deathgasp
|
||||||
|
- type: MobStateActions
|
||||||
|
actions:
|
||||||
|
Critical:
|
||||||
|
- CritSuccumb
|
||||||
|
- CritFakeDeath
|
||||||
|
- CritLastWords
|
||||||
- type: MobThresholds
|
- type: MobThresholds
|
||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
|
|||||||
@@ -91,6 +91,13 @@
|
|||||||
types:
|
types:
|
||||||
Heat : 0.1 #per second, scales with temperature & other constants
|
Heat : 0.1 #per second, scales with temperature & other constants
|
||||||
- type: MobState
|
- type: MobState
|
||||||
|
- type: Deathgasp
|
||||||
|
- type: MobStateActions
|
||||||
|
actions:
|
||||||
|
Critical:
|
||||||
|
- CritSuccumb
|
||||||
|
- CritFakeDeath
|
||||||
|
- CritLastWords
|
||||||
- type: MobThresholds
|
- type: MobThresholds
|
||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
|
|||||||
@@ -72,6 +72,11 @@
|
|||||||
layer:
|
layer:
|
||||||
- FlyingMobLayer
|
- FlyingMobLayer
|
||||||
- type: MobState
|
- type: MobState
|
||||||
|
- type: MobStateActions
|
||||||
|
actions:
|
||||||
|
Critical:
|
||||||
|
- CritSuccumb
|
||||||
|
- CritLastWords
|
||||||
- type: MobThresholds
|
- type: MobThresholds
|
||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
|
|||||||
@@ -198,6 +198,13 @@
|
|||||||
thermalRegulationTemperatureThreshold: 25
|
thermalRegulationTemperatureThreshold: 25
|
||||||
- type: Internals
|
- type: Internals
|
||||||
- type: MobState
|
- type: MobState
|
||||||
|
- type: Deathgasp
|
||||||
|
- type: MobStateActions
|
||||||
|
actions:
|
||||||
|
Critical:
|
||||||
|
- CritSuccumb
|
||||||
|
- CritFakeDeath
|
||||||
|
- CritLastWords
|
||||||
- type: MobThresholds
|
- type: MobThresholds
|
||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
|
|||||||
@@ -184,3 +184,8 @@
|
|||||||
- salutes.
|
- salutes.
|
||||||
- salutes!
|
- salutes!
|
||||||
|
|
||||||
|
- type: emote
|
||||||
|
id: DefaultDeathgasp
|
||||||
|
chatMessages: ["emote-deathgasp"]
|
||||||
|
chatTriggers:
|
||||||
|
- deathgasp
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 514 B |
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Created by mirrorcult for SS14, derivative of other licensed sprites in the repo",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "lastwords"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fakedeath"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GD/@EntryIndexedValue">GD</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GD/@EntryIndexedValue">GD</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HW/@EntryIndexedValue">HW</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HW/@EntryIndexedValue">HW</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IC/@EntryIndexedValue">IC</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IL/@EntryIndexedValue">IL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IL/@EntryIndexedValue">IL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KHR/@EntryIndexedValue">KHR</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KHR/@EntryIndexedValue">KHR</s:String>
|
||||||
|
|||||||
Reference in New Issue
Block a user