Add antagonist OOC

This commit is contained in:
Janet Blackquill 2026-07-22 12:38:59 -04:00
parent b15639af05
commit 5dd2182d08
24 changed files with 271 additions and 7 deletions

View File

@ -9,5 +9,6 @@
<controls:ConfirmButton Name="ServerShutdownButton" Text="{Loc server-shutdown}" />
<cc:CommandButton Name="SetOocButton" Command="setooc" Text="{Loc server-ooc-toggle}" ToggleMode="True" />
<cc:CommandButton Name="SetLoocButton" Command="setlooc" Text="{Loc server-looc-toggle}" ToggleMode="True" />
<cc:CommandButton Name="SetAntagOocButton" Command="setantagooc" Text="{Loc server-antag-ooc-toggle}" ToggleMode="True" /> <!-- DeltaV: Antag OOC -->
</GridContainer>
</Control>

View File

@ -1,4 +1,5 @@
using Content.Shared.CCVar;
using Content.Shared._DV.CCVars; // DeltaV - Antagonist OOC
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
@ -20,6 +21,7 @@ namespace Content.Client.Administration.UI.Tabs
_config.OnValueChanged(CCVars.OocEnabled, OocEnabledChanged, true);
_config.OnValueChanged(CCVars.LoocEnabled, LoocEnabledChanged, true);
_config.OnValueChanged(DCCVars.AntagOOCEnabled, AntagOocEnabledChanged, true); // DeltaV - antagonist OOC
ServerShutdownButton.OnPressed += _ => _console.ExecuteCommand("shutdown");
}
@ -34,6 +36,13 @@ namespace Content.Client.Administration.UI.Tabs
SetLoocButton.Pressed = value;
}
// Begin DeltaV - Antagonist OOC
private void AntagOocEnabledChanged(bool value)
{
SetAntagOocButton.Pressed = value;
}
// End DeltaV - Antagonist OOC
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
@ -42,6 +51,7 @@ namespace Content.Client.Administration.UI.Tabs
{
_config.UnsubValueChanged(CCVars.OocEnabled, OocEnabledChanged);
_config.UnsubValueChanged(CCVars.LoocEnabled, LoocEnabledChanged);
_config.UnsubValueChanged(DCCVars.AntagOOCEnabled, AntagOocEnabledChanged); // DeltaV - Antagonist OOC
}
}
}

View File

@ -59,6 +59,12 @@ internal sealed class ChatManager : IChatManager
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
break;
// Begin DeltaV - Antag OOC
case ChatSelectChannel.AntagOOC:
_consoleHost.ExecuteCommand($"antagsay \"{CommandParsing.Escape(str)}\"");
break;
// End DeltaV - Antag OOC
case ChatSelectChannel.Emotes:
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
break;

View File

@ -23,6 +23,7 @@ using Content.Shared.Damage.ForceSay;
using Content.Shared.Decals;
using Content.Shared.Input;
using Content.Shared.Radio;
using Content.Shared.Roles; // DeltaV - Antag OOC
using Content.Shared.Roles.RoleCodeword;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@ -69,6 +70,7 @@ public sealed partial class ChatUIController : UIController
[UISystemDependency] private readonly TransformSystem? _transform = default;
[UISystemDependency] private readonly MindSystem? _mindSystem = default!;
[UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!;
[UISystemDependency] private readonly SharedRoleSystem? _roleSystem = default!; // DeltaV - Antag OOC
private static readonly ProtoId<ColorPalettePrototype> ChatNamePalette = "ChatNames";
private string[] _chatNameColors = default!;
@ -88,7 +90,8 @@ public sealed partial class ChatUIController : UIController
{SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin},
{SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio},
{SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead},
{SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //Nyano - Summary: adds the telepathic prefix =.
{SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic}, //Nyano - Summary: adds the telepathic prefix =.
{SharedChatSystem.AntagOOCPrefix, ChatSelectChannel.AntagOOC} // DeltaV - Antag OOC
};
public static readonly Dictionary<ChatSelectChannel, char> ChannelPrefixes = new()
@ -102,7 +105,8 @@ public sealed partial class ChatUIController : UIController
{ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix},
{ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix},
{ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix},
{ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //Nyano - Summary: associates telepathic with =.
{ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix }, //Nyano - Summary: associates telepathic with =.
{ChatSelectChannel.AntagOOC, SharedChatSystem.AntagOOCPrefix} // DeltaV - Antag OOC
};
/// <summary>
@ -190,6 +194,7 @@ public sealed partial class ChatUIController : UIController
_net.RegisterNetMessage<MsgChatMessage>(OnChatMessage);
_net.RegisterNetMessage<MsgDeleteChatMessagesBy>(OnDeleteChatMessagesBy);
SubscribeNetworkEvent<DamageForceSayEvent>(OnDamageForceSay);
SubscribeNetworkEvent<MindRoleTypeChangedEvent>(OnMindRoleTypeChanged); // DeltaV - Antag OOC
_config.OnValueChanged(CCVars.ChatEnableColorName, (value) => { _chatNameColorsEnabled = value; });
_chatNameColorsEnabled = _config.GetCVar(CCVars.ChatEnableColorName);
@ -560,6 +565,22 @@ public sealed partial class ChatUIController : UIController
CanSendChannels |= ChatSelectChannel.Dead;
}
// Begin DeltaV - Antag OOC
// antags and admins can see antagsay
var localUserId = _player.LocalSession?.UserId;
var antagOOCEligible = _admin.IsActive()
|| (localUserId is { } userId
&& _mindSystem != null
&& _roleSystem != null
&& _roleSystem.MindHasAntagonistOOC(_mindSystem.GetMind(userId)));
if (antagOOCEligible)
{
FilterableChannels |= ChatChannel.AntagOOC;
CanSendChannels |= ChatSelectChannel.AntagOOC;
}
// End DeltaV - Antag OOC
// only admins can see / filter asay
if (_admin.HasFlag(AdminFlags.Adminchat))
{
@ -790,6 +811,13 @@ public sealed partial class ChatUIController : UIController
_manager.SendMessage(text, prefixChannel == 0 ? channel : prefixChannel);
}
// Begin DeltaV - Antagonist OOC
private void OnMindRoleTypeChanged(MindRoleTypeChangedEvent ev, EntitySessionEventArgs _)
{
UpdateChannelPermissions();
}
// End DeltaV - Antagonist OOC
private void OnDamageForceSay(DamageForceSayEvent ev, EntitySessionEventArgs _)
{
var chatBox = UIManager.ActiveScreen?.GetWidget<ChatBox>() ?? UIManager.ActiveScreen?.GetWidget<ResizableChatBox>();

View File

@ -28,6 +28,7 @@ public sealed partial class ChannelFilterPopup : Popup
ChatChannel.Admin,
ChatChannel.AdminAlert,
ChatChannel.AdminChat,
ChatChannel.AntagOOC, // DeltaV - Antagonist OOC
ChatChannel.Server
};

View File

@ -64,6 +64,7 @@ public sealed class ChannelSelectorButton : ChatPopupButton<ChannelSelectorPopup
ChatSelectChannel.OOC => Color.LightSkyBlue,
ChatSelectChannel.Dead => Color.MediumPurple,
ChatSelectChannel.Admin => Color.HotPink,
ChatSelectChannel.AntagOOC => Color.Crimson, // DeltaV - Antagonist OOC
ChatSelectChannel.Telepathic => Color.PaleVioletRed, //Nyano - Summary: determines the color for the chat.
_ => Color.DarkGray
};

View File

@ -17,7 +17,8 @@ public sealed class ChannelSelectorPopup : Popup
ChatSelectChannel.LOOC,
ChatSelectChannel.OOC,
ChatSelectChannel.Dead,
ChatSelectChannel.Admin
ChatSelectChannel.Admin,
ChatSelectChannel.AntagOOC // DeltaV - Antagonist OOC
// NOTE: Console is not in there and it can never be permanently selected.
// You can, however, still submit commands as console by prefixing with /.
};

View File

@ -8,6 +8,7 @@ using Content.Server.Discord.DiscordLink;
using Content.Server.Ghost;
using Content.Server.Players.RateLimiting;
using Content.Server.Preferences.Managers;
using Content.Shared._DV.CCVars; // DeltaV - antagonist OOC
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Chat;
@ -15,6 +16,7 @@ using Content.Shared.Database;
using Content.Shared.Mind;
using Content.Shared.Players; // DeltaV - OOC muting
using Content.Shared.Players.RateLimiting;
using Content.Shared.Roles; // DeltaV - antagonist OOC
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Network;
@ -60,6 +62,7 @@ internal sealed partial class ChatManager : IChatManager
private bool _oocEnabled = true;
private bool _adminOocEnabled = true;
private bool _antagOocEnabled = true; // DeltaV - antagonist OOC
private readonly Dictionary<NetUserId, ChatUser> _players = new();
@ -70,6 +73,7 @@ internal sealed partial class ChatManager : IChatManager
_configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true);
_configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true);
_configurationManager.OnValueChanged(DCCVars.AntagOOCEnabled, OnAntagOocEnabledChanged, true); // DeltaV - antagonist OOC
_sawmill = _logManager.GetSawmill("SERVER");
@ -92,6 +96,16 @@ internal sealed partial class ChatManager : IChatManager
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message"));
}
// Begin DeltaV - Antagonist OOC
private void OnAntagOocEnabledChanged(bool val)
{
if (_antagOocEnabled == val) return;
_antagOocEnabled = val;
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-antag-ooc-chat-enabled-message" : "chat-manager-antag-ooc-chat-disabled-message"));
}
// End DeltaV - Antagonist OOC
public void DeleteMessagesBy(NetUserId uid)
{
if (!_players.TryGetValue(uid, out var user))
@ -266,6 +280,11 @@ internal sealed partial class ChatManager : IChatManager
case OOCChatType.Admin:
SendAdminChat(player, message);
break;
// Begin DeltaV - Antag OOC
case OOCChatType.AntagOOC:
SendAntagOOC(player, message);
break;
// End DeltaV - Antag OOC
}
}
@ -341,6 +360,41 @@ internal sealed partial class ChatManager : IChatManager
_adminLogger.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}");
}
// Begin DeltaV - Antagonist OOC
private void SendAntagOOC(ICommonSession player, string message)
{
var isAdmin = _adminManager.IsAdmin(player);
var roleSystem = _entityManager.System<SharedRoleSystem>();
var mindId = player.ContentData()?.Mind;
if (!isAdmin)
{
if (!_antagOocEnabled)
return;
if (!roleSystem.MindHasAntagonistOOC(mindId))
{
_adminLogger.Add(LogType.Chat, LogImpact.Extreme, $"{player:Player} attempted to send antag OOC message but was not admin or antagonist-OOC eligible");
return;
}
}
var wrappedMessage = Loc.GetString("chat-manager-send-antag-ooc-wrap-message",
("antagChannelName", Loc.GetString("chat-manager-antag-ooc-channel-name")),
("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel)
.Union(_player.Sessions
.Where(session => roleSystem.MindHasAntagonistOOC(session.ContentData()?.Mind))
.Select(session => session.Channel));
ChatMessageToMany(ChatChannel.AntagOOC, message, wrappedMessage, EntityUid.Invalid, false, true, clients.ToList(), author: player.UserId);
_discordLink.SendMessage(message, player.Name, ChatChannel.AntagOOC);
_adminLogger.Add(LogType.Chat, $"Antag OOC from {player:Player}: {message}");
}
// End DeltaV - Antagonist OOC
#endregion
#region Utility
@ -491,5 +545,6 @@ internal sealed partial class ChatManager : IChatManager
public enum OOCChatType : byte
{
OOC,
Admin
Admin,
AntagOOC // DeltaV - Antagonist OOC
}

View File

@ -1,4 +1,5 @@
using Content.Server.Chat.Managers;
using Content.Shared._DV.CCVars; // DeltaV - Antagonist OOC
using Content.Shared.CCVar;
using Content.Shared.Chat;
using NetCord;
@ -20,6 +21,7 @@ public sealed class DiscordChatLink : IPostInjectInit
private ulong? _oocChannelId;
private ulong? _adminChannelId;
private ulong? _antagOocChannelId; // DeltaV - antagonist OOC
public void Initialize()
{
@ -31,6 +33,7 @@ public sealed class DiscordChatLink : IPostInjectInit
_configurationManager.OnValueChanged(CCVars.OocDiscordChannelId, OnOocChannelIdChanged, true);
_configurationManager.OnValueChanged(CCVars.AdminChatDiscordChannelId, OnAdminChannelIdChanged, true);
_configurationManager.OnValueChanged(DCCVars.AntagOOCDiscordChannelId, OnAntagOocChannelIdChanged, true); // DeltaV - antagonist OOC
}
public void Shutdown()
@ -39,6 +42,7 @@ public sealed class DiscordChatLink : IPostInjectInit
_configurationManager.UnsubValueChanged(CCVars.OocDiscordChannelId, OnOocChannelIdChanged);
_configurationManager.UnsubValueChanged(CCVars.AdminChatDiscordChannelId, OnAdminChannelIdChanged);
_configurationManager.UnsubValueChanged(DCCVars.AntagOOCDiscordChannelId, OnAntagOocChannelIdChanged); // DeltaV - antagonist OOC
}
#if DEBUG
@ -71,6 +75,19 @@ public sealed class DiscordChatLink : IPostInjectInit
_adminChannelId = ulong.Parse(channelId);
}
// Begin DeltaV - Antagonist OOC
private void OnAntagOocChannelIdChanged(string channelId)
{
if (string.IsNullOrEmpty(channelId))
{
_antagOocChannelId = null;
return;
}
_antagOocChannelId = ulong.Parse(channelId);
}
// End DeltaV - Antagonist OOC
private void OnMessageReceived(Message message)
{
if (message.Author.IsBot)
@ -94,6 +111,7 @@ public sealed class DiscordChatLink : IPostInjectInit
{
ChatChannel.OOC => _oocChannelId,
ChatChannel.AdminChat => _adminChannelId,
ChatChannel.AntagOOC => _antagOocChannelId, // DeltaV - Antagonist OOC
_ => throw new InvalidOperationException("Channel not linked to Discord."),
};

View File

@ -0,0 +1,31 @@
using Content.Server.Chat.Managers;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server._DV.Chat;
[AnyCommand]
public sealed class AntagSayCommand : LocalizedCommands
{
[Dependency] private readonly IChatManager _chatManager = default!;
public override string Command => "antagsay";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player is not { } player)
{
shell.WriteError(Loc.GetString($"shell-cannot-run-command-from-server"));
return;
}
if (args.Length < 1)
return;
var message = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(message))
return;
_chatManager.TrySendOOCMessage(player, message, OOCChatType.AntagOOC);
}
}

View File

@ -0,0 +1,41 @@
using Content.Server.Administration;
using Content.Shared._DV.CCVars;
using Content.Shared.Administration;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server._DV.Chat;
[AdminCommand(AdminFlags.Admin)]
public sealed class SetAntagOOCCommand : LocalizedCommands
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
public override string Command => "setantagooc";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length > 1)
{
shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
return;
}
var antagOoc = _configManager.GetCVar(DCCVars.AntagOOCEnabled);
if (args.Length == 0)
{
antagOoc = !antagOoc;
}
if (args.Length == 1 && !bool.TryParse(args[0], out antagOoc))
{
shell.WriteError(Loc.GetString("shell-invalid-bool"));
return;
}
_configManager.SetCVar(DCCVars.AntagOOCEnabled, antagOoc);
shell.WriteLine(Loc.GetString(antagOoc ? "cmd-setantagooc-antagooc-enabled" : "cmd-setantagooc-antagooc-disabled"));
}
}

View File

@ -4,7 +4,7 @@ namespace Content.Shared.Chat
/// Represents chat channels that the player can filter chat tabs by.
/// </summary>
[Flags]
public enum ChatChannel : ushort
public enum ChatChannel : uint // DeltaV - MOAR CHANNELS
{
None = 0,
@ -90,6 +90,11 @@ namespace Content.Shared.Chat
/// </summary>
Telepathic = 1 << 15,
/// <summary>
/// DeltaV - OOC for antagonists
/// </summary>
AntagOOC = 1 << 16,
/// <summary>
/// Channels considered to be IC.
/// </summary>
@ -113,6 +118,7 @@ namespace Content.Shared.Chat
{
ChatChannel.OOC => Loc.GetString("chat-channel-humanized-ooc"),
ChatChannel.AdminChat => Loc.GetString("chat-channel-humanized-admin"),
ChatChannel.AntagOOC => Loc.GetString("chat-channel-humanized-antagooc"), // DeltaV - Antagonist OOC
_ => throw new ArgumentOutOfRangeException(nameof(channel), channel, null)
};
}

View File

@ -14,6 +14,7 @@ public static class ChatChannelExtensions
ChatChannel.Admin => Color.Red,
ChatChannel.AdminAlert => Color.Red,
ChatChannel.AdminChat => Color.HotPink,
ChatChannel.AntagOOC => Color.Crimson, // DeltaV - Antagonist OOC
ChatChannel.Whisper => Color.DarkGray,
_ => Color.LightGray
};

View File

@ -7,7 +7,7 @@
/// Maps to <see cref="ChatChannel"/>, giving better names.
/// </remarks>
[Flags]
public enum ChatSelectChannel : ushort
public enum ChatSelectChannel : uint // DeltaV - Antagonist OOC
{
None = 0,
@ -51,6 +51,11 @@
/// </summary>
Admin = ChatChannel.AdminChat,
/// <summary>
/// DeltaV - OOC for antagonists
/// </summary>
AntagOOC = ChatChannel.AntagOOC,
/// <summary>
/// Nyano - Summary:. Telepathic channel for all psionic entities.
/// </summary>

View File

@ -33,6 +33,7 @@ public abstract partial class SharedChatSystem : EntitySystem
public const char AudibleEmotePrefix = '!'; // DeltaV - You may now scream audibly!
public const char PossessiveEmotePrefix = '\''; // DeltaV - You may now be possessive of things! Whatever that means.
public const char AdminPrefix = ']';
public const char AntagOOCPrefix = '%'; // DeltaV - Antagonist OOC
public const char WhisperPrefix = ',';
public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix.
public const char DefaultChannelKey = 'h';

View File

@ -32,4 +32,10 @@ public sealed partial class RoleTypePrototype : IPrototype
/// </summary>
[DataField]
public string Symbol = FallbackSymbol;
/// <summary>
/// DeltaV - Whether minds with this roletype can access AOOC
/// </summary>
[DataField]
public bool AntagonistOOC;
}

View File

@ -631,6 +631,21 @@ public abstract class SharedRoleSystem : EntitySystem
return CheckAntagonistStatus(mindId.Value).ExclusiveAntag;
}
// Begin DeltaV - Antagonist OOC
/// <summary>
/// Does this mind's current role type grant access to the Antagonist OOC channel
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>True if the mind's RoleTypePrototype has AntagonistOOC set</returns>
public bool MindHasAntagonistOOC(EntityUid? mindId)
{
if (mindId is null || !TryComp<MindComponent>(mindId.Value, out var mind))
return false;
return _prototypes.TryIndex(mind.RoleType, out var roleType) && roleType.AntagonistOOC;
}
// End DeltaV - Antagonist OOC
private (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity<MindComponent?> mind)
{
if (!Resolve(mind.Owner, ref mind.Comp))

View File

@ -343,4 +343,21 @@ public sealed partial class DCCVars
/// </summary>
public static readonly CVarDef<bool> RoundEndIsOOCVote =
CVarDef.Create("deltav.round_end_is_ooc_vote", false, CVar.SERVER);
/**
* Antagonist OOC
*/
/// <summary>
/// Whether the Antagonist OOC channel is enabled.
/// Admins can always use it.
/// </summary>
public static readonly CVarDef<bool> AntagOOCEnabled =
CVarDef.Create("antag_ooc.enabled", true, CVar.NOTIFY);
/// <summary>
/// The discord channel ID to relay Antagonist OOC messages to.
/// </summary>
public static readonly CVarDef<string> AntagOOCDiscordChannelId =
CVarDef.Create("antag_ooc.discord_channel_id", string.Empty, CVar.SERVERONLY);
}

View File

@ -0,0 +1 @@
server-antag-ooc-toggle = Toggle Antag OOC

View File

@ -0,0 +1 @@
chat-channel-humanized-antagooc = Antagonist OOC

View File

@ -53,3 +53,10 @@ chat-manager-entity-me-audible-possessive-wrap-message = [italic]{ PROPER($entit
*[false] The {$entityName}'s {$message}[/italic]
[true] {CAPITALIZE($entityName)}'s {$message}[/italic]
}
chat-manager-antag-ooc-chat-enabled-message = Antagonist OOC chat has been enabled.
chat-manager-antag-ooc-chat-disabled-message = Antagonist OOC chat has been disabled.
chat-manager-send-antag-ooc-wrap-message = {$antagChannelName}: {$playerName}: {$message}
chat-manager-antag-ooc-channel-name = (ANTAG)

View File

@ -1,3 +1,7 @@
hud-chatbox-select-channels = Channels:
hud-chatbox-auto-highlights = Automatic Highlights:
hud-chatbox-select-channel-AntagOOC = Antag OOC
hud-chatbox-channel-AntagOOC = Antag OOC

View File

@ -0,0 +1,4 @@
cmd-setantagooc-desc = Allows you to enable or disable the Antagonist OOC channel for non-admins.
cmd-setantagooc-help = Usage: setantagooc OR setantagooc [value]
cmd-setantagooc-antagooc-enabled = Antagonist OOC chat has been enabled.
cmd-setantagooc-antagooc-disabled = Antagonist OOC chat has been disabled.

View File

@ -17,12 +17,14 @@
name: role-type-solo-antagonist-name
color: '#d82000'
symbol: "🗡"
antagonistOOC: true # DeltaV - antagonist OOC
- type: roleType
id: TeamAntagonist
name: role-type-team-antagonist-name
color: '#d82000'
symbol: "⚔"
antagonistOOC: true # DeltaV - antagonist OOC
- type: roleType
id: FreeAgent
@ -47,3 +49,4 @@
name: role-type-silicon-antagonist-name
color: '#c832e6'
symbol: "⛞"
antagonistOOC: true # DeltaV - antagonist OOC