МбКульт & Gambling^3 (#279)

* refmbcult

* fixrule

* minich

* yeee

* linter

* somefixes

* cultext&gamb

* linterfix

* fixtarot
This commit is contained in:
Zekins
2025-12-31 18:55:15 +03:00
committed by GitHub
parent 54b95f99d6
commit 99bc2edc32
232 changed files with 6602 additions and 2875 deletions

View File

@@ -82,6 +82,12 @@ internal sealed class ChatManager : IChatManager
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
break;
// Corvax-Wega-MindChat-start
case ChatSelectChannel.Mind:
_consoleHost.ExecuteCommand($"mindsay \"{CommandParsing.Escape(str)}\"");
break;
// Corvax-Wega-MindChat-end
default:
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
}

View File

@@ -21,6 +21,7 @@ using Content.Shared.Chat;
using Content.Shared.Damage.ForceSay;
using Content.Shared.Decals;
using Content.Shared.Input;
using Content.Shared.Mind; // Corvax-Wega-MindChat
using Content.Shared.Radio;
using Content.Shared.Roles.RoleCodeword;
using Robust.Client.GameObjects;
@@ -84,7 +85,8 @@ public sealed partial class ChatUIController : UIController
{SharedChatSystem.EmotesAltPrefix, ChatSelectChannel.Emotes},
{SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin},
{SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio},
{SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}
{SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead},
{SharedChatSystem.MindPrefix, ChatSelectChannel.Mind} // Corvax-Wega-MindChat
};
public static readonly Dictionary<ChatSelectChannel, char> ChannelPrefixes = new()
@@ -97,7 +99,8 @@ public sealed partial class ChatUIController : UIController
{ChatSelectChannel.Emotes, SharedChatSystem.EmotesPrefix},
{ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix},
{ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix},
{ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}
{ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix},
{ChatSelectChannel.Mind, SharedChatSystem.MindPrefix} // Corvax-Wega-MindChat
};
/// <summary>
@@ -534,6 +537,7 @@ public sealed partial class ChatUIController : UIController
FilterableChannels |= ChatChannel.Whisper;
FilterableChannels |= ChatChannel.Radio;
FilterableChannels |= ChatChannel.Emotes;
FilterableChannels |= ChatChannel.Mind; // Corvax-Wega-MindChat
FilterableChannels |= ChatChannel.Notifications;
// Can only send local / radio / emote when attached to a non-ghost entity.
@@ -545,6 +549,8 @@ public sealed partial class ChatUIController : UIController
CanSendChannels |= ChatSelectChannel.Radio;
CanSendChannels |= ChatSelectChannel.Emotes;
}
CanSendChannels |= ChatSelectChannel.Mind; // Corvax-Wega-MindChat
}
// Only ghosts and admins can send / see deadchat.
@@ -692,46 +698,66 @@ public sealed partial class ChatUIController : UIController
&& _chatSys.TryProcessRadioMessage(uid, text, out _, out radioChannel, quiet: true);
}
// Corvax-Wega-MindChat-start
private bool TryGetMindChannel(string text, out MindChannelPrototype? mindChannel)
{
mindChannel = null;
return _player.LocalEntity is EntityUid { Valid: true } uid
&& _chatSys != null
&& _chatSys.TryProcessMindMessage(uid, text, out _, out mindChannel, quiet: true);
}
// Corvax-Wega-MindChat-end
public void UpdateSelectedChannel(ChatBox box)
{
var (prefixChannel, _, radioChannel) = SplitInputContents(box.ChatInput.Input.Text.ToLower());
var (prefixChannel, text, radioChannel, mindChannel) = SplitInputContents(box.ChatInput.Input.Text.ToLower()); // Corvax-Wega-MindChat-Edit
if (prefixChannel == ChatSelectChannel.None)
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(box.SelectedChannel, null);
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(box.SelectedChannel, null, null); // Corvax-Wega-MindChat-Edit
else
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(prefixChannel, radioChannel);
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(prefixChannel, radioChannel, mindChannel); // Corvax-Wega-MindChat-Edit
}
public (ChatSelectChannel chatChannel, string text, RadioChannelPrototype? radioChannel) SplitInputContents(string text)
public (ChatSelectChannel chatChannel, string text, RadioChannelPrototype? radioChannel, MindChannelPrototype? mindChannel) SplitInputContents(string text) // Corvax-Wega-MindChat-Edit
{
text = text.Trim();
if (text.Length == 0)
return (ChatSelectChannel.None, text, null);
return (ChatSelectChannel.None, text, null, null); // Corvax-Wega-MindChat-Edit
// We only cut off prefix only if it is not a radio or local channel, which both map to the same /say command
// because ????????
// Corvax-Wega-MindChat-Edit-start
ChatSelectChannel chatChannel;
if (TryGetRadioChannel(text, out var radioChannel))
RadioChannelPrototype? radioChannel = null;
MindChannelPrototype? mindChannel = null;
if (TryGetRadioChannel(text, out radioChannel))
chatChannel = ChatSelectChannel.Radio;
else if (TryGetMindChannel(text, out mindChannel))
chatChannel = ChatSelectChannel.Mind;
else
chatChannel = PrefixToChannel.GetValueOrDefault(text[0]);
if ((CanSendChannels & chatChannel) == 0)
return (ChatSelectChannel.None, text, null);
return (ChatSelectChannel.None, text, null, null);
if (chatChannel == ChatSelectChannel.Radio)
return (chatChannel, text, radioChannel);
return (chatChannel, text, radioChannel, null);
if (chatChannel == ChatSelectChannel.Mind)
return (chatChannel, text, null, mindChannel);
if (chatChannel == ChatSelectChannel.Local)
{
if (_ghost?.IsGhost != true)
return (chatChannel, text, null);
return (chatChannel, text, null, null);
else
chatChannel = ChatSelectChannel.Dead;
}
return (chatChannel, text[1..].TrimStart(), null);
return (chatChannel, text[1..].TrimStart(), null, null);
// Corvax-Wega-MindChat-Edit-end
}
public void SendMessage(ChatBox box, ChatSelectChannel channel)
@@ -746,7 +772,7 @@ public sealed partial class ChatUIController : UIController
if (string.IsNullOrWhiteSpace(text))
return;
(var prefixChannel, text, var _) = SplitInputContents(text);
(var prefixChannel, text, var _, var _) = SplitInputContents(text); // Corvax-Wega-MindChat-Edit
// Check if message is longer than the character limit
if (text.Length > MaxMessageLength)
@@ -764,6 +790,12 @@ public sealed partial class ChatUIController : UIController
// radio must have prefix as it goes through the say command.
text = $";{text}";
}
// Corvax-Wega-MindChat-start
else if (channel == ChatSelectChannel.Mind)
{
text = $"+{text}";
}
// Corvax-Wega-MindChat-end
_manager.SendMessage(text, prefixChannel == 0 ? channel : prefixChannel);
}

View File

@@ -23,6 +23,7 @@ public sealed partial class ChannelFilterPopup : Popup
ChatChannel.LOOC,
ChatChannel.OOC,
ChatChannel.Dead,
ChatChannel.Mind, // Corvax-Wega-MindChat
ChatChannel.Admin,
ChatChannel.AdminAlert,
ChatChannel.AdminChat,

View File

@@ -63,14 +63,30 @@ public sealed class ChannelSelectorButton : ChatPopupButton<ChannelSelectorPopup
ChatSelectChannel.LOOC => Color.MediumTurquoise,
ChatSelectChannel.OOC => Color.LightSkyBlue,
ChatSelectChannel.Dead => Color.MediumPurple,
ChatSelectChannel.Mind => Color.Peru, // Corvax-Wega-MindChat
ChatSelectChannel.Admin => Color.HotPink,
_ => Color.DarkGray
};
}
public void UpdateChannelSelectButton(ChatSelectChannel channel, Shared.Radio.RadioChannelPrototype? radio)
// Corvax-Wega-MindChat-Edit-start
public void UpdateChannelSelectButton(ChatSelectChannel channel, Shared.Radio.RadioChannelPrototype? radio, Shared.Mind.MindChannelPrototype? mind)
{
Text = radio != null ? Loc.GetString(radio.Name) : ChannelSelectorName(channel);
Modulate = radio?.Color ?? ChannelSelectColor(channel);
if (radio != null)
{
Text = Loc.GetString(radio.Name);
Modulate = radio.Color;
}
else if (mind != null)
{
Text = Loc.GetString(mind.Name);
Modulate = mind.Color;
}
else
{
Text = ChannelSelectorName(channel);
Modulate = ChannelSelectColor(channel);
}
}
// Corvax-Wega-MindChat-Edit-end
}

View File

@@ -16,6 +16,7 @@ public sealed class ChannelSelectorPopup : Popup
ChatSelectChannel.LOOC,
ChatSelectChannel.OOC,
ChatSelectChannel.Dead,
ChatSelectChannel.Mind, // Corvax-Wega-MindChat
ChatSelectChannel.Admin
// 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

@@ -6,7 +6,6 @@ using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Client.Blood.Cult
{
@@ -24,9 +23,8 @@ namespace Content.Client.Blood.Cult
SubscribeLocalEvent<BloodRuneComponent, AppearanceChangeEvent>(OnRuneAppearanceChanged);
SubscribeLocalEvent<BloodRitualDimensionalRendingComponent, AppearanceChangeEvent>(OnRuneAppearanceChanged);
SubscribeLocalEvent<BloodCultistComponent, GetStatusIconsEvent>(GetCultistIcons);
SubscribeLocalEvent<PentagramDisplayComponent, ComponentStartup>(GetHalo);
SubscribeLocalEvent<PentagramDisplayComponent, ComponentShutdown>(RemoveHalo);
SubscribeLocalEvent<StoneSoulComponent, AppearanceChangeEvent>(OnSoulStoneAppearanceChanged);
SubscribeLocalEvent<BloodPentagramDisplayComponent, ComponentStartup>(GetHalo);
SubscribeLocalEvent<BloodPentagramDisplayComponent, ComponentShutdown>(RemoveHalo);
}
private void OnRuneAppearanceChanged(Entity<BloodRuneComponent> entity, ref AppearanceChangeEvent args)
@@ -51,7 +49,7 @@ namespace Content.Client.Blood.Cult
args.StatusIcons.Add(iconPrototype);
}
private void GetHalo(EntityUid uid, PentagramDisplayComponent component, ComponentStartup args)
private void GetHalo(EntityUid uid, BloodPentagramDisplayComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
@@ -77,28 +75,10 @@ namespace Content.Client.Blood.Cult
_sprite.LayerMapSet(uid, PentagramKey.Halo, layer);
}
private void RemoveHalo(EntityUid uid, PentagramDisplayComponent component, ComponentShutdown args)
private void RemoveHalo(EntityUid uid, BloodPentagramDisplayComponent component, ComponentShutdown args)
{
if (_sprite.LayerMapTryGet(uid, PentagramKey.Halo, out var layer, true))
{
_sprite.RemoveLayer(uid, layer);
}
}
private void OnSoulStoneAppearanceChanged(EntityUid uid, StoneSoulComponent component, ref AppearanceChangeEvent args)
{
if (!_appearance.TryGetData(uid, StoneSoulVisuals.HasSoul, out bool hasSoul))
hasSoul = false;
_sprite.LayerSetVisible(uid, StoneSoulVisualLayers.Soul, hasSoul);
if (!hasSoul)
{
_sprite.LayerSetVisible(uid, StoneSoulVisualLayers.Base, true);
}
else
{
_sprite.LayerSetVisible(uid, StoneSoulVisualLayers.Base, false);
}
}
private enum PentagramKey

View File

@@ -0,0 +1,30 @@
using Content.Shared.Blood.Cult;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client._Wega.BloodCult.Ui;
[UsedImplicitly]
public sealed class BloodConstructBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private BloodConstructMenu? _menu;
public BloodConstructBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<BloodConstructMenu>();
_menu.OnSelectConstruct += construct =>
{
SendMessage(new BloodConstructSelectMessage(construct));
Close();
};
_menu.OpenCentered();
}
}

View File

@@ -1,32 +1,27 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Select.Construct.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Select.Construct.UI.BloodConstructMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
VerticalExpand="True" HorizontalExpand="True">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
<!-- Juggernaut -->
<ui:RadialMenuButton Name="BloodJuggernautButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-juggernaut'}" TargetLayerControlName="BloodJuggernaut">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/juggernaut.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/juggernaut.png"/>
</ui:RadialMenuButton>
<!-- Wraith -->
<ui:RadialMenuButton Name="BloodWraithButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-wraith'}" TargetLayerControlName="BloodWraith">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/wraith.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/wraith.png"/>
</ui:RadialMenuButton>
<!-- Artificer -->
<ui:RadialMenuButton Name="BloodArtificerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-artificer'}" TargetLayerControlName="BloodArtificer">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/artificer.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/artificer.png"/>
</ui:RadialMenuButton>
<!-- Proteon -->
<ui:RadialMenuButton Name="BloodProteonButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-proteon'}" TargetLayerControlName="BloodProteon">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/proteon.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/proteon.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>

View File

@@ -1,49 +1,31 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
namespace Content.Client.Select.Construct.UI;
namespace Content.Client._Wega.BloodCult.Ui;
[GenerateTypedNameReferences]
public sealed partial class BloodConstructMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectConstruct;
private NetEntity _constructUid;
private NetEntity _mindUid;
public BloodConstructMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
BloodJuggernautButton.OnButtonUp += _ => HandleRitesSelection("MobConstructJuggernaut");
BloodWraithButton.OnButtonUp += _ => HandleRitesSelection("MobConstructWraith");
BloodArtificerButton.OnButtonUp += _ => HandleRitesSelection("MobConstructArtificer");
BloodProteonButton.OnButtonUp += _ => HandleRitesSelection("MobConstructProteon");
BloodJuggernautButton.OnButtonUp += _ => HandleConstructSelection("MobConstructJuggernaut");
BloodWraithButton.OnButtonUp += _ => HandleConstructSelection("MobConstructWraith");
BloodArtificerButton.OnButtonUp += _ => HandleConstructSelection("MobConstructArtificer");
BloodProteonButton.OnButtonUp += _ => HandleConstructSelection("MobConstructProteon");
}
public void SetData(NetEntity constructUid, NetEntity mindUid)
{
_constructUid = constructUid;
_mindUid = mindUid;
}
private void HandleRitesSelection(string constructName)
private void HandleConstructSelection(string constructName)
{
OnSelectConstruct?.Invoke(constructName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodConstructMenuClosedEvent(netEntity, _constructUid, _mindUid, constructName));
Close();
}
}

View File

@@ -1,52 +0,0 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Select.Construct.UI
{
public sealed class BloodConstructMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodConstructMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<OpenConstructMenuEvent>(OnConstructMenuReceived);
}
private void OnConstructMenuReceived(OpenConstructMenuEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodConstructMenu>();
_menu.SetData(args.ConstructUid, args.Mind);
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
Timer.Spawn(30000, () =>
{
if (_menu != null)
{
_menu.Close();
}
});
}
}
}
}

View File

@@ -0,0 +1,31 @@
using Content.Client.Eui;
using Content.Shared.Blood.Cult;
using JetBrains.Annotations;
namespace Content.Client._Wega.BloodCult.Ui;
[UsedImplicitly]
public sealed class BloodMagicEui : BaseEui
{
private readonly BloodMagicMenu _menu;
public BloodMagicEui()
{
_menu = new BloodMagicMenu();
_menu.OnSelectedSpell += spell =>
{
SendMessage(new BloodMagicSelectSpellMessage(spell));
_menu.Close();
};
}
public override void Opened()
{
_menu.OpenCentered();
}
public override void Closed()
{
_menu.Close();
}
}

View File

@@ -1,62 +1,57 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Blood.Magic.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Blood.Magic.UI.BloodMagicMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
VerticalExpand="True" HorizontalExpand="True">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
<!-- Stun -->
<ui:RadialMenuButton Name="StunButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-stun'}" TargetLayerControlName="Stun">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/stun.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/stun.png"/>
</ui:RadialMenuButton>
<!-- Teleport -->
<ui:RadialMenuButton Name="TeleportButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-teleport'}" TargetLayerControlName="Teleport">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/teleport.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/teleport.png"/>
</ui:RadialMenuButton>
<!-- Electromagnetic Pulse -->
<ui:RadialMenuButton Name="ElectromagneticPulseButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-electromagnetic-pulse'}" TargetLayerControlName="ElectromagneticPulse">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/electromagneticpulse.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/electromagneticpulse.png"/>
</ui:RadialMenuButton>
<!-- Shadow Shackles -->
<ui:RadialMenuButton Name="ShadowShacklesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-shadow-shackles'}" TargetLayerControlName="ShadowShackles">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/shadowshackles.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/shadowshackles.png"/>
</ui:RadialMenuButton>
<!-- Twisted Construction -->
<ui:RadialMenuButton Name="TwistedConstructionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-twisted-construction'}" TargetLayerControlName="TwistedConstruction">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/twistedconstruction.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/twistedconstruction.png"/>
</ui:RadialMenuButton>
<!-- Summon Equipment -->
<ui:RadialMenuButton Name="SummonEquipmentButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-equipment'}" TargetLayerControlName="SummonEquipment">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/summonequipment.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/summonequipment.png"/>
</ui:RadialMenuButton>
<!-- Summon Dagger -->
<ui:RadialMenuButton Name="SummonDaggerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-dagger'}" TargetLayerControlName="SummonDagger">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/dagger.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/dagger.png"/>
</ui:RadialMenuButton>
<!-- Hallucinations -->
<ui:RadialMenuButton Name="HallucinationsButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-hallucinations'}" TargetLayerControlName="Hallucinations">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/hallucinations.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/hallucinations.png"/>
</ui:RadialMenuButton>
<!-- Conceal Presence -->
<ui:RadialMenuButton Name="ConcealPresenceButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-conceal-presence'}" TargetLayerControlName="ConcealPresence">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/concealpresence.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/concealpresence.png"/>
</ui:RadialMenuButton>
<!-- Blood Rites -->
<ui:RadialMenuButton Name="BloodRitesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-blood-rites'}" TargetLayerControlName="BloodRites">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/blood_rites.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/blood_rites.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>

View File

@@ -1,25 +1,18 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Blood.Magic.UI;
namespace Content.Client._Wega.BloodCult.Ui;
[GenerateTypedNameReferences]
public sealed partial class BloodMagicMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectSpell;
public event Action<EntProtoId>? OnSelectedSpell;
public BloodMagicMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
@@ -37,12 +30,9 @@ public sealed partial class BloodMagicMenu : RadialMenu
BloodRitesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultBloodRites");
}
private void HandleSpellSelection(string spellName)
private void HandleSpellSelection(EntProtoId spell)
{
OnSelectSpell?.Invoke(spellName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodMagicMenuClosedEvent(netEntity, spellName));
Close();
OnSelectedSpell?.Invoke(spell);
}
}

View File

@@ -1,45 +0,0 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client.Blood.Magic.UI
{
public sealed class BloodMagicMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodMagicMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BloodMagicPressedEvent>(OnBloodMagicMenuReceived);
}
private void OnBloodMagicMenuReceived(BloodMagicPressedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodMagicMenu>();
_menu.OnClose += OnMenuClosed;
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
}
}
private void OnMenuClosed()
{
_menu = null;
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Blood.Cult;
using Robust.Client.UserInterface;
namespace Content.Client._Wega.BloodCult.Ui;
public sealed class BloodRitesBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private BloodRitesMenu? _menu;
public BloodRitesBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<BloodRitesMenu>();
_menu.OnSelectRites += rites =>
{
SendMessage(new BloodRitesSelectRitesMessage(rites));
Close();
};
_menu.OpenCentered();
}
}

View File

@@ -1,32 +1,27 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Blood.Rites.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Blood.Rites.UI.BloodRitesMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
VerticalExpand="True" HorizontalExpand="True">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
<!-- Blood Orb -->
<ui:RadialMenuButton Name="BloodOrbButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-orb'}" TargetLayerControlName="BloodOrb">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/orb.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/orb.png"/>
</ui:RadialMenuButton>
<!-- Blood Recharge -->
<ui:RadialMenuButton Name="BloodRechargeButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-recharge'}" TargetLayerControlName="BloodRecharge">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/recharge.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/recharge.png"/>
</ui:RadialMenuButton>
<!-- Blood Spear -->
<ui:RadialMenuButton Name="BloodSpearButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-spear'}" TargetLayerControlName="BloodSpear">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/spear.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/spear.png"/>
</ui:RadialMenuButton>
<!-- Blood Bolt Barrage -->
<ui:RadialMenuButton Name="BloodBoltBarrageButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-bolt-barrage'}" TargetLayerControlName="BloodBoltBarrage">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/barrage.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/barrage.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>

View File

@@ -1,25 +1,18 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Blood.Rites.UI;
namespace Content.Client._Wega.BloodCult.Ui;
[GenerateTypedNameReferences]
public sealed partial class BloodRitesMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectRites;
public event Action<EntProtoId>? OnSelectRites;
public BloodRitesMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
@@ -31,12 +24,9 @@ public sealed partial class BloodRitesMenu : RadialMenu
BloodBoltBarrageButton.OnButtonUp += _ => HandleRitesSelection("ActionBloodCultBoltBarrage");
}
private void HandleRitesSelection(string ritesName)
private void HandleRitesSelection(EntProtoId rites)
{
OnSelectRites?.Invoke(ritesName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodRitesMenuClosedEvent(netEntity, ritesName));
OnSelectRites?.Invoke(rites);
Close();
}
}

View File

@@ -1,45 +0,0 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client.Blood.Rites.UI
{
public sealed class BloodRitesMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodRitesMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BloodRitesPressedEvent>(OnBloodMagicMenuReceived);
}
private void OnBloodMagicMenuReceived(BloodRitesPressedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodRitesMenu>();
_menu.OnClose += OnMenuClosed;
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
}
}
private void OnMenuClosed()
{
_menu = null;
}
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Blood.Cult;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client._Wega.BloodCult.Ui;
[UsedImplicitly]
public sealed class BloodStructureBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private BloodStructureMenu? _menu;
public BloodStructureBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<BloodStructureMenu>();
_menu.OnSelectItem += item =>
{
SendMessage(new BloodStructureSelectMessage(item));
Close();
};
_menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is BloodStructureBoundUserInterfaceState structureState)
_menu?.InitializeButtons(structureState);
}
}

View File

@@ -1,14 +1,9 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Structure.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Structure.UI.BloodStructureMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
VerticalExpand="True" HorizontalExpand="True">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -1,48 +1,33 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Structure.UI;
namespace Content.Client._Wega.BloodCult.Ui;
[GenerateTypedNameReferences]
public sealed partial class BloodStructureMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public event Action<string>? OnSelectItem;
private NetEntity _structure;
public BloodStructureMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
IoCManager.Instance!.InjectDependencies(this);
}
public void SetData(NetEntity structure)
public void InitializeButtons(BloodStructureBoundUserInterfaceState state)
{
_structure = structure;
InitializeButtons();
}
Main.RemoveAllChildren();
private void InitializeButtons()
{
var structure = _entityManager.GetEntity(_structure);
if (!_entityManager.TryGetComponent<BloodStructureComponent>(structure, out var structureComp)
|| structureComp.StructureGear.Count == 0)
return;
foreach (var prototypeId in structureComp.StructureGear)
foreach (var prototypeId in state.Items)
{
if (!_prototypeManager.TryIndex<EntityPrototype>(prototypeId, out var prototype))
if (!_proto.TryIndex(prototypeId, out var prototype))
continue;
var button = new RadialMenuButton
@@ -75,8 +60,6 @@ public sealed partial class BloodStructureMenu : RadialMenu
private void HandleItemSelection(string name)
{
OnSelectItem?.Invoke(name);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodStructureMenuClosedEvent(netEntity, name, _structure));
Close();
}
}

View File

@@ -1,57 +0,0 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Structure.UI
{
public sealed class BloodStructureMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodStructureMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<OpenStructureMenuEvent>(OnStructureMenuReceived);
}
private void OnStructureMenuReceived(OpenStructureMenuEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodStructureMenu>();
_menu.OnClose += OnMenuClosed;
_menu.SetData(args.Structure);
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
Timer.Spawn(30000, () =>
{
if (_menu != null)
{
_menu.Close();
}
});
}
}
private void OnMenuClosed()
{
_menu = null;
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Blood.Cult;
using Robust.Client.UserInterface;
namespace Content.Client._Wega.BloodCult.Ui;
public sealed class EmpoweringRuneBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private EmpoweringRuneMenu? _menu;
public EmpoweringRuneBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<EmpoweringRuneMenu>();
_menu.OnSelectSpell += spell =>
{
SendMessage(new EmpoweringRuneSelectSpellMessage(spell));
Close();
};
_menu.OpenCentered();
}
}

View File

@@ -1,62 +1,57 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Runes.Panel.Ui;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Runes.Panel.Ui.EmpoweringRuneMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
VerticalExpand="True" HorizontalExpand="True">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
<!-- Stun -->
<ui:RadialMenuButton Name="StunButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-stun'}" TargetLayerControlName="Stun">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/stun.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/stun.png"/>
</ui:RadialMenuButton>
<!-- Teleport -->
<ui:RadialMenuButton Name="TeleportButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-teleport'}" TargetLayerControlName="Teleport">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/teleport.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/teleport.png"/>
</ui:RadialMenuButton>
<!-- Electromagnetic Pulse -->
<ui:RadialMenuButton Name="ElectromagneticPulseButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-electromagnetic-pulse'}" TargetLayerControlName="ElectromagneticPulse">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/electromagneticpulse.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/electromagneticpulse.png"/>
</ui:RadialMenuButton>
<!-- Shadow Shackles -->
<ui:RadialMenuButton Name="ShadowShacklesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-shadow-shackles'}" TargetLayerControlName="ShadowShackles">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/shadowshackles.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/shadowshackles.png"/>
</ui:RadialMenuButton>
<!-- Twisted Construction -->
<ui:RadialMenuButton Name="TwistedConstructionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-twisted-construction'}" TargetLayerControlName="TwistedConstruction">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/twistedconstruction.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/twistedconstruction.png"/>
</ui:RadialMenuButton>
<!-- Summon Equipment -->
<ui:RadialMenuButton Name="SummonEquipmentButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-equipment'}" TargetLayerControlName="SummonEquipment">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/summonequipment.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/summonequipment.png"/>
</ui:RadialMenuButton>
<!-- Summon Dagger -->
<ui:RadialMenuButton Name="SummonDaggerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-dagger'}" TargetLayerControlName="SummonDagger">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/dagger.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/dagger.png"/>
</ui:RadialMenuButton>
<!-- Hallucinations -->
<ui:RadialMenuButton Name="HallucinationsButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-hallucinations'}" TargetLayerControlName="Hallucinations">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/hallucinations.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/hallucinations.png"/>
</ui:RadialMenuButton>
<!-- Conceal Presence -->
<ui:RadialMenuButton Name="ConcealPresenceButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-conceal-presence'}" TargetLayerControlName="ConcealPresence">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/concealpresence.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/concealpresence.png"/>
</ui:RadialMenuButton>
<!-- Blood Rites -->
<ui:RadialMenuButton Name="BloodRitesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-blood-rites'}" TargetLayerControlName="BloodRites">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/blood_rites.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/actions_bloodcult.rsi/blood_rites.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>

View File

@@ -0,0 +1,38 @@
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._Wega.BloodCult.Ui;
[GenerateTypedNameReferences]
public sealed partial class EmpoweringRuneMenu : RadialMenu
{
public event Action<EntProtoId>? OnSelectSpell;
public EmpoweringRuneMenu()
{
RobustXamlLoader.Load(this);
InitializeButtons();
}
private void InitializeButtons()
{
StunButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultStun");
TeleportButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTeleport");
ElectromagneticPulseButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultElectromagneticPulse");
ShadowShacklesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultShadowShackles");
TwistedConstructionButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTwistedConstruction");
SummonEquipmentButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonEquipment");
SummonDaggerButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonDagger");
HallucinationsButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultHallucinations");
ConcealPresenceButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultConcealPresence");
BloodRitesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultBloodRites");
}
private void HandleSpellSelection(EntProtoId spell)
{
OnSelectSpell?.Invoke(spell);
Close();
}
}

View File

@@ -1,22 +1,9 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
x:Class="Content.Client.Runes.Panel.Ui.RunesPanelMenu"
Title="{Loc 'runes-ui-default-title'}"
MinSize="256 256"
SetSize="256 256">
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4">
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#80808005" />
</PanelContainer.PanelOverride>
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>
<ScrollContainer Name="Scroll" HScrollEnabled="False" VerticalExpand="True">
<BoxContainer Name="RunesContainer" Orientation="Vertical" VerticalExpand="True">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</DefaultWindow>
</ui:RadialMenu>

View File

@@ -1,162 +1,74 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Runes.Panel.Ui;
namespace Content.Client._Wega.BloodCult.Ui;
public sealed partial class RunesPanelMenu : DefaultWindow
[GenerateTypedNameReferences]
public sealed partial class RunesMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public event Action<string>? OnRuneSelected;
public BoxContainer RunesContainer => this.FindControl<BoxContainer>("RunesContainer");
public event Action<EntProtoId>? OnRuneSelected;
public RunesPanelMenu()
public RunesMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
IoCManager.Instance!.InjectDependencies(this);
InitializeRunes();
}
private void InitializeRunes()
{
AddRuneButton(Loc.GetString("offering-rune"), "BloodRuneOffering");
AddRuneButton(Loc.GetString("teleport-rune"), "BloodRuneTeleport");
AddRuneButton(Loc.GetString("empowering-rune"), "BloodRuneEmpowering");
AddRuneButton(Loc.GetString("revive-rune"), "BloodRuneRevive");
AddRuneButton(Loc.GetString("barrier-rune"), "BloodRuneBarrier");
AddRuneButton(Loc.GetString("summoning-rune"), "BloodRuneSummoning");
AddRuneButton(Loc.GetString("bloodboil-rune"), "BloodRuneBloodBoil");
AddRuneButton(Loc.GetString("spiritrealm-rune"), "BloodRuneSpiritealm");
AddRuneButton(Loc.GetString("ritual-dimensional-rending-rune"), "BloodRuneRitualDimensionalRending");
AddRuneButton("offering-rune", "BloodRuneOffering");
AddRuneButton("teleport-rune", "BloodRuneTeleport");
AddRuneButton("empowering-rune", "BloodRuneEmpowering");
AddRuneButton("revive-rune", "BloodRuneRevive");
AddRuneButton("barrier-rune", "BloodRuneBarrier");
AddRuneButton("summoning-rune", "BloodRuneSummoning");
AddRuneButton("bloodboil-rune", "BloodRuneBloodBoil");
AddRuneButton("spiritrealm-rune", "BloodRuneSpiritealm");
}
public void AddRitualButton()
{
AddRuneButton("ritual-dimensional-rending-rune", "BloodRuneRitualDimensionalRending");
}
private void AddRuneButton(string runeName, string protoId)
{
var button = new Button
if (!_proto.TryIndex(protoId, out var prototype))
return;
var button = new RadialMenuButton
{
Text = runeName,
MinSize = new Vector2(300, 32),
MaxSize = new Vector2(300, 32),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
ToolTip = Loc.GetString(runeName) + $"\n{Loc.GetString(runeName + "-desc")}",
SetSize = new Vector2(64, 64),
};
button.StyleClasses.Add("RadialMenuButton");
var entityView = new EntityPrototypeView
{
Scale = new Vector2(2, 2),
SetSize = new Vector2(64, 64),
Margin = new Thickness(4)
};
entityView.SetPrototype(prototype.ID);
button.AddChild(entityView);
button.OnPressed += _ => HandleRuneSelection(protoId);
RunesContainer.AddChild(button);
Main.AddChild(button);
}
private void HandleRuneSelection(string protoId)
private void HandleRuneSelection(EntProtoId protoId)
{
OnRuneSelected?.Invoke(protoId);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new RuneSelectEvent(netEntity, protoId));
Close();
}
public new void Close()
{
base.Close();
}
}
[GenerateTypedNameReferences]
public sealed partial class EmpoweringRuneMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectSpell;
public EmpoweringRuneMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
StunButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultStun");
TeleportButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTeleport");
ElectromagneticPulseButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultElectromagneticPulse");
ShadowShacklesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultShadowShackles");
TwistedConstructionButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTwistedConstruction");
SummonEquipmentButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonEquipment");
SummonDaggerButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonDagger");
HallucinationsButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultHallucinations");
ConcealPresenceButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultConcealPresence");
BloodRitesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultBloodRites");
}
private void HandleSpellSelection(string spellName)
{
OnSelectSpell?.Invoke(spellName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new EmpoweringRuneMenuClosedEvent(netEntity, spellName));
Close();
}
}
public sealed partial class SummoningRunePanelMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public BoxContainer CultistsContainer => this.FindControl<BoxContainer>("CultistsContainer");
public SummoningRunePanelMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
var cultistQuery = _entityManager.EntityQueryEnumerator<BloodCultistComponent, MetaDataComponent>();
while (cultistQuery.MoveNext(out var uid, out _, out var metaData))
{
var entityName = metaData.EntityName;
AddCultistButton(entityName, uid);
}
}
private void AddCultistButton(string cultistName, EntityUid cultistUid)
{
var button = new Button
{
Text = cultistName,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
MinSize = new Vector2(300, 32),
MaxSize = new Vector2(300, 32)
};
button.OnPressed += _ => HandleCultistSelection(cultistUid);
CultistsContainer.AddChild(button);
}
private void HandleCultistSelection(EntityUid cultistUid)
{
var netTargerEntity = _entityManager.GetNetEntity(cultistUid);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new SummoningSelectedEvent(netEntity, netTargerEntity));
Close();
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Blood.Cult;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client._Wega.BloodCult.Ui;
[UsedImplicitly]
public sealed class RunesMenuBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private RunesMenu? _menu;
public RunesMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<RunesMenu>();
_menu.OnRuneSelected += rune =>
{
SendMessage(new SelectBloodRuneMessage(rune));
Close();
};
_menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is BloodRitualBoundUserInterfaceState _)
_menu?.AddRitualButton();
}
}

View File

@@ -1,144 +0,0 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Runes.Panel.Ui
{
public sealed class RunesMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private RunesPanelMenu? _panel;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<RunesMenuOpenedEvent>(OnRunesMenuReceived);
}
private void OnRunesMenuReceived(RunesMenuOpenedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_panel is null)
{
_panel = _uiManager.CreateWindow<RunesPanelMenu>();
_panel.OnClose += OnMenuClosed;
_panel.OpenCentered();
}
else
{
_panel.OpenCentered();
}
}
}
private void OnMenuClosed()
{
_panel = null;
}
}
public sealed class EmpoweringRuneMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private EmpoweringRuneMenu? _menu;
private bool _menuDisposed = false;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<EmpoweringRuneMenuOpenedEvent>(OnRuneMenuReceived);
}
private void OnRuneMenuReceived(EmpoweringRuneMenuOpenedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<EmpoweringRuneMenu>();
_menu.OnClose += OnMenuClosed;
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
}
Timer.Spawn(30000, () =>
{
if (_menu != null && !_menuDisposed)
{
_menu.Close();
}
});
}
private void OnMenuClosed()
{
_menuDisposed = true;
_menu = null;
}
}
public sealed class SummoningRuneMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SummoningRunePanelMenu? _panel;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<SummoningRuneMenuOpenedEvent>(OnRuneMenuReceived);
}
private void OnRuneMenuReceived(SummoningRuneMenuOpenedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_panel is null)
{
_panel = _uiManager.CreateWindow<SummoningRunePanelMenu>();
_panel.OnClose += OnMenuClosed;
_panel.OpenCentered();
}
else
{
_panel.OpenCentered();
}
Timer.Spawn(30000, () =>
{
if (_panel != null)
{
_panel.Close();
}
});
}
}
private void OnMenuClosed()
{
_panel = null;
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Blood.Cult;
using Robust.Client.UserInterface;
namespace Content.Client._Wega.BloodCult.Ui;
public sealed class SummoningRuneBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private SummoningRuneMenu? _menu;
public SummoningRuneBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<SummoningRuneMenu>();
_menu.OnCultistSelected += cultist =>
{
SendMessage(new SummoningRuneSelectCultistMessage(cultist));
Close();
};
_menu.OpenCentered();
}
}

View File

@@ -1,8 +1,7 @@
<DefaultWindow
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
x:Class="Content.Client.Runes.Panel.Ui.SummoningRunePanelMenu"
Title="{Loc 'runes-ui-default-title'}"
MinSize="512 340"
SetSize="512 340">
@@ -19,4 +18,4 @@
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</DefaultWindow>
</controls:FancyWindow>

View File

@@ -0,0 +1,56 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Wega.BloodCult.Ui;
[GenerateTypedNameReferences]
public sealed partial class SummoningRuneMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action<NetEntity>? OnCultistSelected;
public SummoningRuneMenu()
{
RobustXamlLoader.Load(this);
IoCManager.Instance!.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
var cultistQuery = _entityManager.EntityQueryEnumerator<BloodCultistComponent, MetaDataComponent>();
while (cultistQuery.MoveNext(out var uid, out _, out var metaData))
{
var entityName = metaData.EntityName;
AddCultistButton(entityName, uid);
}
}
private void AddCultistButton(string cultistName, EntityUid cultistUid)
{
var button = new Button
{
Text = cultistName,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
MinSize = new Vector2(300, 32),
MaxSize = new Vector2(300, 32)
};
button.OnPressed += _ => HandleCultistSelection(cultistUid);
CultistsContainer.AddChild(button);
}
private void HandleCultistSelection(EntityUid cultistUid)
{
var netCultist = _entityManager.GetNetEntity(cultistUid);
OnCultistSelected?.Invoke(netCultist);
Close();
}
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.Card.Tarot;
using Content.Shared.Card.Tarot.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Card.Tarot;
public sealed class CardTarotSystem : EntitySystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CardTarotComponent, AppearanceChangeEvent>(OnAppearanceChanged);
}
private void OnAppearanceChanged(Entity<CardTarotComponent> entity, ref AppearanceChangeEvent args)
{
if (!_appearance.TryGetData(entity, CardTarotVisuals.State, out CardTarot card)
|| !_appearance.TryGetData(entity, CardTarotVisuals.Reversed, out bool reversed))
return;
var state = card.ToString().ToLower();
if (reversed) state += "-reversed";
_sprite.LayerSetRsiState(entity.Owner, 0, state);
}
}

View File

@@ -13,27 +13,27 @@
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<!-- Button 1: Hemomancer -->
<ui:RadialMenuButton Name="HemomancerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-hemomancer'}" TargetLayerControlName="Hemomancer">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/claws.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/claws.png"/>
</ui:RadialMenuButton>
<!-- Button 2: Umbrae -->
<ui:RadialMenuButton Name="UmbraeButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-umbrae'}" TargetLayerControlName="Umbrae">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/cloak.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/cloak.png"/>
</ui:RadialMenuButton>
<!-- Button 3: Gargantua -->
<ui:RadialMenuButton Name="GargantuaButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-gargantua'}" TargetLayerControlName="Gargantua">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/swell.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/swell.png"/>
</ui:RadialMenuButton>
<!-- Button 4: Dantalion -->
<ui:RadialMenuButton Name="DantalionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-dantalion'}" TargetLayerControlName="Dantalion">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/enthrall.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/enthrall.png"/>
</ui:RadialMenuButton>
<!-- Button 5: Bestia -->
<!-- <ui:RadialMenuButton Name="BestiaButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-bestia'}" TargetLayerControlName="Bestia">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/rush.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/rush.png"/>
</ui:RadialMenuButton> -->
</ui:RadialContainer>

View File

@@ -36,6 +36,8 @@ using Robust.Shared.Replays;
using Robust.Shared.Utility;
using Content.Shared.Strangulation; // Corvax-Wega-Strangulation
using Content.Shared.SoundInsolation; // Corvax-Wega-SoundInsolation
using Content.Shared.Mind; // Corvax-Wega-MindChat
using Content.Shared.Blood.Cult.Components; // Corvax-Wega-Blood-Cult
namespace Content.Server.Chat.Systems;
@@ -235,6 +237,14 @@ public sealed partial class ChatSystem : SharedChatSystem
SendEntityWhisper(source, modMessage, range, channel, nameOverride, hideLog, ignoreActionBlocker);
return;
}
// Corvax-Wega-MindChat-start
if (TryProcessMindMessage(source, message, out var mindMessage, out var mindChannel) && mindChannel != null)
{
SendMindMessage(source, mindMessage, mindChannel, ignoreActionBlocker);
return;
}
// Corvax-Wega-MindChat-end
}
// Otherwise, send whatever type.
@@ -384,6 +394,69 @@ public sealed partial class ChatSystem : SharedChatSystem
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement on {station} from {sender}: {message}");
}
// Corvax-Wega-MindChat-start
/// <inheritdoc/>
public override void SendMindMessage(
EntityUid source,
string message,
MindChannelPrototype channel,
bool ignoreActionBlocker = false)
{
if (string.IsNullOrWhiteSpace(message))
return;
if (!ignoreActionBlocker && !_actionBlocker.CanSpeak(source))
return;
var name = MetaData(source).EntityName;
name = FormattedMessage.EscapeText(name);
var wrappedMessage = Loc.GetString("chat-mind-message-wrap",
("color", channel.Color),
("channel", $"\\[{channel.LocalizedName}\\]"),
("name", name),
("message", message));
// Send to all entities with the same mind channel
foreach (var (session, _) in GetRecipients(source, MindChatRange))
{
if ((!TryComp<MindLinkComponent>(session.AttachedEntity, out var mindLink) || !mindLink.Channels.Contains(channel.ID))
&& !HasComp<AdminMindLinkListenerComponent>(session.AttachedEntity))
continue;
_chatManager.ChatMessageToOne(
ChatChannel.Mind,
message,
wrappedMessage,
source,
false,
session.Channel);
}
// Also send a whisper
TrySendInGameICMessage(
source,
message,
InGameICChatType.Whisper,
ChatTransmitRange.Normal,
nameOverride: name,
ignoreActionBlocker: true);
// Log to admin logs
_adminLogger.Add(LogType.Chat, LogImpact.Low,
$"Mind message from {ToPrettyString(source):user} on {channel.LocalizedName}: {message}");
// Record for replay
var chat = new ChatMessage(
ChatChannel.Mind,
message,
wrappedMessage,
GetNetEntity(source),
null);
_replay.RecordServerMessage(chat);
}
// Corvax-Wega-MindChat-end
#endregion
#region Private API
@@ -634,6 +707,9 @@ public sealed partial class ChatSystem : SharedChatSystem
private void SendDeadChat(EntityUid source, ICommonSession player, string message, bool hideChat)
{
if (HasComp<BloodCultGhostComponent>(source)) // Corvax-Wega-Blood-Cult-Add
return; // Corvax-Wega-Blood-Cult-Add
var clients = GetDeadChatClients();
var playerName = Name(source);
string wrappedMessage;
@@ -814,6 +890,7 @@ public sealed partial class ChatSystem : SharedChatSystem
.AddWhereAttachedEntity(HasComp<GhostComponent>)
.Recipients
.Union(_adminManager.ActiveAdmins)
.Where(d => !HasComp<BloodCultGhostComponent>(d.AttachedEntity)) // Corvax-Wega-Blood-Cult-Add
.Select(p => p.Channel);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,39 @@
using System.Linq;
using System.Numerics;
using Content.Server.Bed.Cryostorage;
using Content.Server.Body.Components;
using Content.Server.Audio;
using Content.Server.Body.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Prayer;
using Content.Server.GameTicking.Rules;
using Content.Server.RoundEnd;
using Content.Shared.Actions;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.EnergyShield;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.Events;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Standing;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Events;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@@ -44,37 +42,37 @@ namespace Content.Server.Blood.Cult;
public sealed partial class BloodCultSystem : SharedBloodCultSystem
{
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly BloodCultRuleSystem _bloodCult = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly ServerGlobalSoundSystem _sound = default!;
[Dependency] private readonly SharedActionsSystem _action = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
private readonly List<EntityUid> _selectedTargets = new();
private bool _firstTriggered = false;
private bool _secondTriggered = false;
private bool _conductedComplete = false;
private bool _ritualStage = false;
private int _curses = 2;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
SubscribeLocalEvent<BloodCultRuleComponent, ComponentShutdown>(OnRuleShutdown);
SubscribeLocalEvent<BloodCultistComponent, BloodCultObjectiveActionEvent>(OnCheckObjective);
SubscribeLocalEvent<BloodCultistComponent, ComponentStartup>(OnComponentStartup);
base.Initialize();
InitializeRunes();
InitializeBloodAbilities();
SubscribeLocalEvent<BloodCultistEyesComponent, ExaminedEvent>(OnCultistEyesExamined);
SubscribeLocalEvent<BloodCultistComponent, ShotAttemptedEvent>(OnShotAttempted); // Corvax-Wega-Testing
SubscribeLocalEvent<BloodCultConstructComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<BloodCultObjectComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<BloodCultObjectComponent, CryostorageEnterEvent>(OnCryostorageEnter);
SubscribeLocalEvent<BloodCultWeaponComponent, AttemptMeleeEvent>(OnAttemptMelee);
SubscribeLocalEvent<BloodDaggerComponent, AfterInteractEvent>(OnInteract);
SubscribeLocalEvent<AttackAttemptEvent>(OnAttackAttempt);
SubscribeLocalEvent<StoneSoulComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<StoneSoulComponent, ComponentShutdown>(OnShutdown);
@@ -84,16 +82,17 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
SubscribeLocalEvent<BloodShuttleCurseComponent, UseInHandEvent>(OnShuttleCurse);
SubscribeLocalEvent<VeilShifterComponent, ExaminedEvent>(OnVeilShifterExamined);
SubscribeLocalEvent<VeilShifterComponent, UseInHandEvent>(OnVeilShifter);
SubscribeLocalEvent<BloodShieldActivaebleComponent, GotUnequippedEvent>(OnShieldGotUnequipped);
SubscribeLocalEvent<ConstructComponent, InteractHandEvent>(OnConstructInteract);
SubscribeNetworkEvent<BloodConstructMenuClosedEvent>(OnConstructSelect);
SubscribeLocalEvent<ConstructComponent, BloodConstructSelectMessage>(OnConstructSelect);
SubscribeLocalEvent<BloodStructureComponent, MapInitEvent>(OnStructureMapInit);
SubscribeLocalEvent<BloodStructureComponent, InteractHandEvent>(OnStructureInteract);
SubscribeNetworkEvent<BloodStructureMenuClosedEvent>(OnStructureItemSelect);
InitializeRunes();
InitializeBloodAbilities();
SubscribeLocalEvent<BloodStructureComponent, BloodStructureSelectMessage>(OnStructureItemSelect);
}
public override void Update(float frameTime)
@@ -107,8 +106,7 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
{
pylonQueryComponent.NextTimeTick = 3;
var nearbyCultists = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(Transform(pylon).Coordinates, 11f)
.Where(cultist => !_entityManager.TryGetComponent<MobThresholdsComponent>(cultist.Owner, out var thresholds)
|| thresholds.CurrentThresholdState != MobState.Dead)
.Where(cultist => !_mobState.IsDead(cultist))
.ToList();
foreach (var target in nearbyCultists)
@@ -116,35 +114,44 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
var heal = new DamageSpecifier { DamageDict = { { "Blunt", -1 }, { "Slash", -1 } } };
_damage.TryChangeDamage(target.Owner, heal, true);
if (TryComp<BloodstreamComponent>(target, out var blood))
_blood.TryModifyBloodLevel(target.Owner, +1);
_blood.TryModifyBloodLevel(target.Owner, +1);
}
}
pylonQueryComponent.NextTimeTick -= frameTime;
}
var ritualQuery = EntityQueryEnumerator<BloodRitualDimensionalRendingComponent>();
while (ritualQuery.MoveNext(out var rune, out var ritualQueryComponent))
while (ritualQuery.MoveNext(out var rune, out var ritualDimensional))
{
if (ritualQueryComponent.Activate)
if (ritualDimensional.Activate)
{
if (!_ritualStage)
if (ritualDimensional.NextTimeTick <= 0)
{
_ritualStage = true;
CheckStage();
}
if (ritualQueryComponent.NextTimeTick <= 0)
{
ritualQueryComponent.NextTimeTick = 1;
ritualDimensional.NextTimeTick = 1;
if (!CheckRitual(_transform.GetMapCoordinates(rune), 9))
ritualQueryComponent.Activate = false;
ritualDimensional.Activate = false;
if (!ritualDimensional.SoundPlayed && _gameTiming.CurTime > ritualDimensional.ActivateTime + TimeSpan.FromSeconds(30))
{
_sound.PlayGlobalOnStation(rune, _audio.ResolveSound(ritualDimensional.RitualMusic));
ritualDimensional.SoundPlayed = true;
}
}
ritualQueryComponent.NextTimeTick -= frameTime;
ritualDimensional.NextTimeTick -= frameTime;
}
}
}
private void OnCultistEyesExamined(EntityUid uid, BloodCultistEyesComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
var name = Identity.Name(uid, EntityManager, args.Examiner);
if (Name(uid) == name)
args.PushMarkup(Loc.GetString("blood-cultist-eyes-glow-examined", ("name", name)));
}
// Corvax-Wega-Testing-start
// Да я пометил тегами чтобы банально не забыть про это и чо?
private void OnShotAttempted(Entity<BloodCultistComponent> ent, ref ShotAttemptedEvent args)
@@ -157,308 +164,20 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
}
// Corvax-Wega-Testing-end
#region Stages Update
private void OnRuleShutdown(EntityUid uid, BloodCultRuleComponent component, ComponentShutdown args)
#region Dagger & Weapon
private void OnAttemptMelee(Entity<BloodCultWeaponComponent> entity, ref AttemptMeleeEvent args)
{
_selectedTargets.Clear();
_firstTriggered = false;
_secondTriggered = false;
_conductedComplete = false;
_curses = 2;
_offerings = 3;
_isRitualRuneUnlocked = false;
}
public void SelectRandomTargets()
{
_selectedTargets.Clear();
var candidates = new List<EntityUid>();
var enumerator = EntityQueryEnumerator<MindShieldComponent>();
while (enumerator.MoveNext(out var uid, out _))
var user = Transform(entity.Owner).ParentUid;
if (!HasComp<BloodCultistComponent>(user))
{
candidates.Add(uid);
}
_popup.PopupEntity(Loc.GetString("blood-cult-failed-attack"), user, user, PopupType.SmallCaution);
if (candidates.Count >= 2)
{
var selectedIndices = new HashSet<int>();
while (selectedIndices.Count < 2)
{
var index = _random.Next(0, candidates.Count);
selectedIndices.Add(index);
}
foreach (var index in selectedIndices)
{
var target = candidates[index];
_selectedTargets.Add(target);
EnsureComp<BloodCultObjectComponent>(target);
}
return;
}
_selectedTargets.AddRange(candidates);
foreach (var target in candidates)
{
EnsureComp<BloodCultObjectComponent>(target);
}
var globalCandidates = new List<EntityUid>();
var globalEnumerator = EntityQueryEnumerator<HumanoidAppearanceComponent, ActorComponent, MobStateComponent>();
while (globalEnumerator.MoveNext(out var uid, out _, out _, out _))
{
if (_selectedTargets.Contains(uid) || HasComp<BloodCultistComponent>(uid))
{
continue;
}
globalCandidates.Add(uid);
}
while (_selectedTargets.Count < 2 && globalCandidates.Count > 0)
{
var index = _random.Next(0, globalCandidates.Count);
var target = globalCandidates[index];
_selectedTargets.Add(target);
EnsureComp<BloodCultObjectComponent>(target);
globalCandidates.RemoveAt(index);
var dropEvent = new DropHandItemsEvent();
RaiseLocalEvent(user, ref dropEvent);
args.Cancelled = true;
}
}
private EntityUid? FindNewRandomTarget(Entity<BloodCultObjectComponent> excludedEntity)
{
var candidates = new List<EntityUid>();
var query = EntityQueryEnumerator<HumanoidAppearanceComponent, ActorComponent, MobStateComponent>();
while (query.MoveNext(out var uid, out _, out _, out _))
{
if (uid == excludedEntity.Owner || HasComp<BloodCultistComponent>(uid)
|| HasComp<BloodCultObjectComponent>(uid))
{
continue;
}
candidates.Add(uid);
}
if (candidates.Count == 0)
return null;
var index = _random.Next(0, candidates.Count);
return candidates[index];
}
private void CheckTargetsConducted(EntityUid eliminatedTarget)
{
if (_selectedTargets.Contains(eliminatedTarget))
_selectedTargets.Remove(eliminatedTarget);
if (_selectedTargets.Count == 0 || !_selectedTargets.Any(IsTargetValid))
{
_conductedComplete = true;
RaiseLocalEvent(new RitualConductedEvent());
}
}
private bool IsTargetValid(EntityUid target)
{
return _entityManager.EntityExists(target);
}
private void OnCheckObjective(EntityUid uid, BloodCultistComponent component, BloodCultObjectiveActionEvent args)
{
if (!TryComp<ActorComponent>(uid, out var playerActor))
return;
string msg;
if (_selectedTargets.Count == 0 && !_conductedComplete || !_selectedTargets.Any(IsTargetValid) && !_conductedComplete)
{
msg = Loc.GetString("blood-cult-targets-no-select");
}
else if (_selectedTargets.Count == 0 && IsRitualConducted())
{
msg = Loc.GetString("blood-cult-ritual-completed-next-objective");
}
else if (IsGodCalled())
{
msg = Loc.GetString("blood-cult-objective-complete");
}
else
{
var targetNames = _selectedTargets
.Where(IsTargetValid)
.Select(target => Name(target))
.ToList();
if (targetNames.Count > 0)
{
msg = Loc.GetString("blood-cult-current-targets", ("targets", string.Join(", ", targetNames)));
}
else
{
msg = Loc.GetString("blood-cult-no-valid-targets");
}
}
_prayerSystem.SendSubtleMessage(playerActor.PlayerSession, playerActor.PlayerSession, string.Empty, msg);
args.Handled = true;
}
private bool IsRitualConducted()
{
var query = EntityManager.EntityQuery<BloodCultRuleComponent>();
foreach (var cult in query)
{
var winConditions = cult.BloodCultWinCondition.ToList();
if (winConditions.Contains(BloodCultWinType.RitualConducted))
return true;
}
return false;
}
private bool IsGodCalled()
{
var query = EntityManager.EntityQuery<BloodCultRuleComponent>();
foreach (var cult in query)
{
var winConditions = cult.BloodCultWinCondition.ToList();
if (winConditions.Contains(BloodCultWinType.GodCalled))
return true;
}
return false;
}
private void OnComponentStartup(Entity<BloodCultistComponent> entity, ref ComponentStartup args)
{
CheckStage();
}
private void OnComponentStartup(Entity<BloodCultConstructComponent> entity, ref ComponentStartup args)
{
CheckStage();
}
private void OnComponentShutdown(Entity<BloodCultObjectComponent> entity, ref ComponentShutdown args)
{
CheckStage();
}
private void OnCryostorageEnter(Entity<BloodCultObjectComponent> entity, ref CryostorageEnterEvent args)
{
if (!TryComp<BloodCultObjectComponent>(args.Uid, out var objectComponent))
return;
var newTarget = FindNewRandomTarget((args.Uid, objectComponent));
if (newTarget != null)
{
_selectedTargets.Add(newTarget.Value);
EnsureComp<BloodCultObjectComponent>(newTarget.Value);
}
_selectedTargets.Remove(args.Uid);
RemComp<BloodCultObjectComponent>(args.Uid);
}
private void CheckStage()
{
var totalCultEntities = GetCultEntities();
var playerCount = GetPlayerCount();
// Second
if (playerCount >= 100 && totalCultEntities >= playerCount * 0.1f || playerCount < 100 && totalCultEntities >= playerCount * 0.2f || _ritualStage)
{
foreach (var cultist in GetAllCultists())
{
if (!HasComp<CultistEyesComponent>(cultist))
{
UpdateCultistEyes(cultist);
AddComp<CultistEyesComponent>(cultist);
}
}
if (!_firstTriggered)
{
var actorFilter = Filter.Empty();
var actorQuery = EntityQueryEnumerator<ActorComponent, BloodCultistComponent>();
while (actorQuery.MoveNext(out var actorUid, out var actor, out _))
{
if (actorUid != EntityUid.Invalid)
{
actorFilter.AddPlayer(actor.PlayerSession);
_popup.PopupEntity(Loc.GetString("blood-cult-first-warning"), actorUid, actorUid, PopupType.SmallCaution);
}
}
_audio.PlayGlobal(new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/bloodcult_eyes.ogg"), actorFilter, true);
_firstTriggered = true;
}
}
// Third
if (playerCount >= 100 && totalCultEntities >= playerCount * 0.2f || playerCount < 100 && totalCultEntities >= playerCount * 0.3f || _ritualStage)
{
foreach (var cultist in GetAllCultists())
{
if (!HasComp<PentagramDisplayComponent>(cultist))
{
AddComp<PentagramDisplayComponent>(cultist);
}
}
if (!_secondTriggered)
{
var actorFilter = Filter.Empty();
var actorQuery = EntityQueryEnumerator<ActorComponent, BloodCultistComponent>();
while (actorQuery.MoveNext(out var actorUid, out var actor, out _))
{
if (actorUid != EntityUid.Invalid)
{
actorFilter.AddPlayer(actor.PlayerSession);
_popup.PopupEntity(Loc.GetString("blood-cult-second-warning"), actorUid, actorUid, PopupType.SmallCaution);
}
}
_audio.PlayGlobal(new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/bloodcult_halos.ogg"), actorFilter, true);
_secondTriggered = true;
}
}
}
private void UpdateCultistEyes(EntityUid cultist)
{
if (TryComp<HumanoidAppearanceComponent>(cultist, out var appearanceComponent))
{
appearanceComponent.EyeColor = Color.FromHex("#E22218FF");
Dirty(cultist, appearanceComponent);
}
}
private int GetCultEntities()
{
var totalCultists = GetAllCultists().Count;
var totalConstructs = EntityQuery<BloodCultConstructComponent>().Count();
return totalCultists + totalConstructs;
}
private int GetPlayerCount()
{
var players = AllEntityQuery<HumanoidAppearanceComponent, ActorComponent, MobStateComponent, TransformComponent>();
int count = 0;
while (players.MoveNext(out _, out _, out _, out _, out _))
{
count++;
}
return count;
}
private List<EntityUid> GetAllCultists()
{
var cultists = new List<EntityUid>();
var enumerator = EntityQueryEnumerator<BloodCultistComponent>();
while (enumerator.MoveNext(out var uid, out _))
{
cultists.Add(uid);
}
return cultists;
}
#endregion
#region Dagger
private void OnInteract(EntityUid uid, BloodDaggerComponent component, AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target is not { Valid: true } target)
@@ -472,6 +191,7 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
var damage = new DamageSpecifier { DamageDict = { { "Slash", 5 } } };
_damage.TryChangeDamage(user, damage, true);
_popup.PopupEntity(Loc.GetString("blood-dagger-failed-interact"), user, user, PopupType.SmallCaution);
args.Handled = true;
return;
}
@@ -496,30 +216,25 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
private void HandleCultistInteraction(AfterInteractEvent args)
{
if (!TryComp<BodyComponent>(args.Target, out var bodyComponent))
if (!HasComp<BodyComponent>(args.Target) || !TryComp<BloodstreamComponent>(args.Target, out var bloodstream))
return;
foreach (var organ in _body.GetBodyOrgans(args.Target.Value, bodyComponent))
{
if (!HasComp<MetabolizerComponent>(organ.Id)
|| !TryComp<StomachComponent>(organ.Id, out var stomachComponent) || stomachComponent.Solution == null
|| !TryComp<SolutionContainerManagerComponent>(stomachComponent.Solution.Value, out var solutionContainer)
|| !_solution.TryGetSolution((stomachComponent.Solution.Value, solutionContainer), null, out var solutionEntity, out var solution))
continue;
var solution = bloodstream.BloodSolution;
if (solution == null)
return;
var holywaterReagentId = new ReagentId("Holywater", new List<ReagentData>());
var holywater = solution.GetReagentQuantity(holywaterReagentId);
var holywaterReagentId = new ReagentId("Holywater", new List<ReagentData>());
var holywater = solution.Value.Comp.Solution.GetReagentQuantity(holywaterReagentId);
if (holywater <= 0)
continue;
if (holywater <= 0)
return;
solution.RemoveReagent(holywaterReagentId, holywater);
solution.Value.Comp.Solution.RemoveReagent(holywaterReagentId, holywater);
var unholywaterReagentId = new ReagentId("Unholywater", new List<ReagentData>());
var unholywaterQuantity = new ReagentQuantity(unholywaterReagentId, holywater);
if (solutionEntity != null && _solution.TryAddReagent(solutionEntity.Value, unholywaterQuantity, out var addedQuantity) && addedQuantity > 0)
args.Handled = true;
}
var unholywaterReagentId = new ReagentId("Unholywater", new List<ReagentData>());
var unholywaterQuantity = new ReagentQuantity(unholywaterReagentId, holywater);
args.Handled = _solution.TryAddReagent(solution.Value, unholywaterQuantity, out var addedQuantity) && addedQuantity > 0;
}
private void HandleRuneInteraction(AfterInteractEvent args)
@@ -548,8 +263,8 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
meleeWeaponComponent.Damage.DamageDict["Slash"] = FixedPoint2.New(4);
component.IsSharpered = true;
_entityManager.DeleteEntity(args.Target);
_entityManager.SpawnEntity("Ash", Transform(user).Coordinates);
QueueDel(args.Target);
Spawn("Ash", Transform(user).Coordinates);
_popup.PopupEntity(Loc.GetString("blood-sharpener-success"), user, user, PopupType.Small);
}
else
@@ -557,19 +272,6 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
_popup.PopupEntity(Loc.GetString("blood-sharpener-failed"), user, user, PopupType.Small);
}
}
private void OnAttackAttempt(AttackAttemptEvent args)
{
if (args.Weapon == null || !HasComp<BloodDaggerComponent>(args.Weapon))
return;
var user = args.Uid;
if (!HasComp<BloodCultistComponent>(user))
{
_popup.PopupEntity(Loc.GetString("blood-cult-failed-attack"), user, user, PopupType.SmallCaution);
args.Cancel();
}
}
#endregion
#region Soul Stone
@@ -580,10 +282,8 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
private void OnShutdown(EntityUid uid, StoneSoulComponent component, ComponentShutdown args)
{
if (component.SoulEntity != null && _entityManager.EntityExists(component.SoulEntity.Value))
{
if (component.SoulEntity != null && Exists(component.SoulEntity.Value))
QueueDel(component.SoulEntity.Value);
}
}
private void OnUseInHand(EntityUid uid, StoneSoulComponent component, UseInHandEvent args)
@@ -612,9 +312,6 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
return;
}
var transformSystem = _entityManager.System<TransformSystem>();
var metaDataSystem = _entityManager.System<MetaDataSystem>();
if (!_mind.TryGetMind(stone, out var mindId, out var mind))
{
_popup.PopupEntity(Loc.GetString("stone-soul-empty"), user, user);
@@ -629,10 +326,10 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
var stoneTransform = Transform(stone).Coordinates;
var soul = Spawn(component.SoulProto, stoneTransform);
transformSystem.AttachToGridOrMap(soul, Transform(soul));
_transform.AttachToGridOrMap(soul, Transform(soul));
if (!string.IsNullOrWhiteSpace(mind.CharacterName))
metaDataSystem.SetEntityName(soul, mind.CharacterName);
_meta.SetEntityName(soul, mind.CharacterName);
_mind.Visit(mindId, soul, mind);
component.SoulEntity = soul;
@@ -643,7 +340,7 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
private void RetractSoul(EntityUid stone, StoneSoulComponent component, EntityUid user)
{
if (component.SoulEntity == null || !_entityManager.EntityExists(component.SoulEntity.Value))
if (component.SoulEntity == null || !Exists(component.SoulEntity.Value))
{
_popup.PopupEntity(Loc.GetString("stone-soul-empty"), user, user);
return;
@@ -681,11 +378,12 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
if (args.Handled || !HasComp<BloodCultistComponent>(user))
return;
if (_curses > 0)
var cult = _bloodCult.GetActiveRule();
if (cult != null && cult.Curses > 0)
{
_roundEndSystem.CancelRoundEndCountdown(user);
_entityManager.DeleteEntity(entity);
_curses--;
_roundEndSystem.CancelRoundEndCountdown(user, true);
QueueDel(entity);
cult.Curses--;
}
else
{
@@ -696,6 +394,14 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
#endregion
#region Veil Shifter
private void OnVeilShifterExamined(EntityUid uid, VeilShifterComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange || !HasComp<BloodCultistComponent>(args.Examiner))
return;
args.PushMarkup(Loc.GetString("veil-shifter-examined", ("count", component.ActivationsCount)));
}
private void OnVeilShifter(EntityUid uid, VeilShifterComponent component, UseInHandEvent args)
{
var user = args.User;
@@ -715,11 +421,14 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
var transform = Transform(user);
var targetPosition = transform.Coordinates.Offset(alignedDirection * randomDistance);
_transform.SetCoordinates(user, targetPosition);
_appearance.SetData(uid, VeilShifterVisuals.Charged, component.ActivationsCount > 0);
}
else
{
_popup.PopupEntity(Loc.GetString("blood-veil-shifter-failed"), user, user, PopupType.SmallCaution);
}
args.Handled = true;
}
@@ -738,131 +447,123 @@ public sealed partial class BloodCultSystem : SharedBloodCultSystem
}
#endregion
#region Shield
private void OnShieldGotUnequipped(Entity<BloodShieldActivaebleComponent> ent, ref GotUnequippedEvent args)
{
if (!TryComp<EnergyShieldOwnerComponent>(args.Equipee, out var energyShield))
return;
QueueDel(energyShield.ShieldEntity);
RemComp(args.Equipee, energyShield);
}
#endregion
#region Construct
private void OnConstructInteract(Entity<ConstructComponent> cosntruct, ref InteractHandEvent args)
private void OnConstructInteract(Entity<ConstructComponent> construct, ref InteractHandEvent args)
{
var user = args.User;
if (args.Handled || !HasComp<BloodCultistComponent>(user))
return;
if (TryComp<ItemSlotsComponent>(cosntruct, out var itemSlotsComponent))
if (TryComp<ItemSlotsComponent>(construct, out var itemSlotsComponent))
{
foreach (var slot in itemSlotsComponent.Slots.Values)
EntityUid? item = itemSlotsComponent.Slots.First().Value.Item;
if (item != null)
{
if (slot.HasItem)
if (_mind.TryGetMind(item.Value, out _, out _))
{
var containedEntity = slot.Item;
if (containedEntity != null)
{
if (TryComp<MindContainerComponent>(containedEntity.Value, out var mindContainer) && mindContainer.Mind != null)
{
var netEntity = _entityManager.GetNetEntity(user);
var netCosntruct = _entityManager.GetNetEntity(cosntruct);
var mind = _entityManager.GetNetEntity(mindContainer.Mind.Value);
RaiseNetworkEvent(new OpenConstructMenuEvent(netEntity, netCosntruct, mind));
}
else
{
_popup.PopupEntity(Loc.GetString("blood-construct-no-mind"), user, user, PopupType.SmallCaution);
}
}
_ui.OpenUi(construct.Owner, BloodConstructUiKey.Key, user);
}
else
{
_popup.PopupEntity(Loc.GetString("blood-construct-failed"), user, user, PopupType.SmallCaution);
_popup.PopupEntity(Loc.GetString("blood-construct-no-mind"), user, user, PopupType.SmallCaution);
}
}
}
else
{
_popup.PopupEntity(Loc.GetString("blood-construct-failed"), user, user, PopupType.SmallCaution);
else
{
_popup.PopupEntity(Loc.GetString("blood-construct-failed"), user, user, PopupType.SmallCaution);
}
}
}
private void OnConstructSelect(BloodConstructMenuClosedEvent args)
private void OnConstructSelect(Entity<ConstructComponent> construct, ref BloodConstructSelectMessage args)
{
var user = _entityManager.GetEntity(args.Uid);
var construct = _entityManager.GetEntity(args.ConstructUid);
var mind = _entityManager.GetEntity(args.Mind);
EntityUid? mindUid = null;
if (TryComp<ItemSlotsComponent>(construct, out var itemSlotsComponent))
mindUid = itemSlotsComponent.Slots.First().Value.Item;
if (mind == EntityUid.Invalid)
if (mindUid == null || !_mind.TryGetMind(mindUid.Value, out var mind, out _))
{
_popup.PopupEntity(Loc.GetString("blood-construct-no-mind"), user, user, PopupType.SmallCaution);
_popup.PopupEntity(Loc.GetString("blood-construct-no-mind"), args.Actor, args.Actor, PopupType.SmallCaution);
return;
}
var constructMobe = _entityManager.SpawnEntity(args.ConstructProto, Transform(construct).Coordinates);
var constructMobe = Spawn(args.Construct, Transform(construct).Coordinates);
_mind.TransferTo(mind, constructMobe);
_entityManager.DeleteEntity(construct);
QueueDel(construct);
_popup.PopupEntity(Loc.GetString("blood-construct-succses"), user, user);
_popup.PopupEntity(Loc.GetString("blood-construct-succses"), args.Actor, args.Actor);
}
#endregion
#region Structures
private void OnStructureMapInit(EntityUid structure, BloodStructureComponent component, MapInitEvent args)
{
component.ActivateTime = _gameTiming.CurTime + TimeSpan.FromMinutes(4);
}
private void OnStructureInteract(EntityUid structure, BloodStructureComponent component, InteractHandEvent args)
{
var user = args.User;
if (args.Handled || !HasComp<BloodCultistComponent>(user))
return;
if (structure is not { Valid: true } target || !component.CanInteract)
return;
var currentTime = _gameTiming.RealTime;
if (currentTime < component.ActivateTime)
var currentTime = _gameTiming.CurTime;
var nextActivateTime = component.ActivateTime + TimeSpan.FromMinutes(4);
if (currentTime < nextActivateTime)
{
var remainingTime = (component.ActivateTime - currentTime).TotalSeconds;
var remainingTime = (nextActivateTime - currentTime).TotalSeconds;
_popup.PopupEntity(Loc.GetString("blood-structure-failed", ("time", Math.Ceiling(remainingTime))), user, user, PopupType.Small);
return;
}
var netEntity = _entityManager.GetNetEntity(user);
var netStructureEntity = _entityManager.GetNetEntity(target);
RaiseNetworkEvent(new OpenStructureMenuEvent(netEntity, netStructureEntity));
_ui.OpenUi(structure, BloodStructureUiKey.Key, user);
var state = new BloodStructureBoundUserInterfaceState(component.StructureGear);
_ui.SetUiState(structure, BloodStructureUiKey.Key, state);
}
private void OnStructureItemSelect(BloodStructureMenuClosedEvent args)
private void OnStructureItemSelect(Entity<BloodStructureComponent> structure, ref BloodStructureSelectMessage args)
{
var user = _entityManager.GetEntity(args.Uid);
var structure = _entityManager.GetEntity(args.Structure);
if (!TryComp<BloodStructureComponent>(structure, out var structureComp))
return;
var currentTime = _gameTiming.RealTime;
if (currentTime < structureComp.ActivateTime)
var currentTime = _gameTiming.CurTime;
var nextActivateTime = structure.Comp.ActivateTime + TimeSpan.FromMinutes(4);
if (currentTime < nextActivateTime)
{
var remainingTime = (structureComp.ActivateTime - currentTime).TotalSeconds;
_popup.PopupEntity(Loc.GetString("blood-structure-failed", ("time", Math.Ceiling(remainingTime))), user, user, PopupType.Small);
var remainingTime = (nextActivateTime - currentTime).TotalSeconds;
_popup.PopupEntity(Loc.GetString("blood-structure-failed", ("time", Math.Ceiling(remainingTime))), args.Actor, args.Actor, PopupType.Small);
return;
}
structureComp.ActivateTime = currentTime + TimeSpan.FromMinutes(4);
structure.Comp.ActivateTime = currentTime;
var item = _entityManager.SpawnEntity(args.Item, Transform(structure).Coordinates);
_audio.PlayPvs(structureComp.Sound, structure);
var item = Spawn(args.Item, Transform(structure).Coordinates);
_audio.PlayPvs(structure.Comp.Sound, structure);
var cultistPosition = _transform.GetWorldPosition(user);
var cultistPosition = _transform.GetWorldPosition(args.Actor);
var structurePosition = _transform.GetWorldPosition(structure);
var distance = (structurePosition - cultistPosition).Length();
if (distance < 3f)
_hands.TryPickupAnyHand(user, item);
if (distance < 3f) _hands.TryPickupAnyHand(args.Actor, item);
}
#endregion
#region God Check
private string GetCurrentGod()
private BloodCultGod GetCurrentGod()
{
var query = EntityQueryEnumerator<BloodCultRuleComponent>();
while (query.MoveNext(out var cult))
{
if (cult.SelectedGod == null)
{
return "Narsie";
}
return cult.SelectedGod;
}
return "Narsie";
var cult = _bloodCult.GetActiveRule();
if (cult != null && cult.SelectedGod != null)
return cult.SelectedGod.Value;
return BloodCultGod.NarSi;
}
#endregion
}

View File

@@ -0,0 +1,24 @@
using Content.Server.EUI;
using Content.Shared.Blood.Cult;
using Content.Shared.Eui;
namespace Content.Server.Blood.Cult.UI;
/// <summary>
/// Logic for the blood magic window
/// </summary>
public sealed class BloodMagicEui(EntityUid cultist, BloodCultSystem bloodCult) : BaseEui
{
public override EuiStateBase GetNewState()
=> new BloodMagicState();
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
if (msg is BloodMagicSelectSpellMessage msgSpell)
bloodCult.AfterSpellSelect(cultist, msgSpell.Spell);
Close();
}
}

View File

@@ -1,880 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Bible.Components;
using Content.Server.GameTicking;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Pinpointer;
using Content.Server.Station.Components;
using Content.Shared.Administration.Systems;
using Content.Shared.Atmos.Components;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Content.Shared.Body.Components;
using Content.Shared.Chat;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.NullRod.Components;
using Content.Shared.Popups;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Standing;
using Content.Shared.Surgery.Components;
using Content.Shared.Timing;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Blood.Cult;
public sealed partial class BloodCultSystem
{
[Dependency] private readonly BloodCultSystem _bloodCult = default!;
[Dependency] private readonly IConsoleHost _consoleHost = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
[Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
[Dependency] private readonly SharedGhostSystem _ghost = default!;
private static readonly EntProtoId ActionComms = "ActionBloodCultComms";
private const string BloodCultObserver = "MobObserverIfrit";
private static int _offerings = 3;
private bool _isRitualRuneUnlocked = false;
private void InitializeRunes()
{
base.Initialize();
SubscribeLocalEvent<RitualConductedEvent>(UnlockRitual);
SubscribeNetworkEvent<RuneSelectEvent>(AfterRuneSelect);
SubscribeLocalEvent<BloodCultistComponent, BloodRuneDoAfterEvent>(DoAfterRuneSelect);
SubscribeLocalEvent<BloodDaggerComponent, UseInHandEvent>(OnDaggerInteract);
SubscribeLocalEvent<BloodRuneComponent, InteractHandEvent>(OnRuneInteract);
SubscribeLocalEvent<BloodRitualDimensionalRendingComponent, InteractHandEvent>(OnRitualInteract);
SubscribeLocalEvent<BloodRitualDimensionalRendingComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeNetworkEvent<EmpoweringRuneMenuClosedEvent>(OnEmpoweringSelected);
SubscribeLocalEvent<BloodCultistComponent, EmpoweringDoAfterEvent>(OnEmpoweringDoAfter);
SubscribeNetworkEvent<SummoningSelectedEvent>(OnSummoningSelected);
SubscribeLocalEvent<BloodRuneCleaningDoAfterEvent>(DoAfterInteractRune);
SubscribeLocalEvent<BloodCultistComponent, BloodRuneCleaningDoAfterEvent>(DoAfterInteractRune);
}
#region Runes
private void UnlockRitual(RitualConductedEvent ev)
{
_isRitualRuneUnlocked = true;
}
private void OnComponentShutdown(EntityUid uid, BloodRitualDimensionalRendingComponent component, ComponentShutdown args)
{
_isRitualRuneUnlocked = false;
}
private void AfterRuneSelect(RuneSelectEvent args, EntitySessionEventArgs eventArgs)
{
var uid = _entityManager.GetEntity(args.Uid);
if (!HasComp<BloodCultistComponent>(uid) || IsInSpace(uid))
return;
var selectedRune = args.RuneProto;
if (selectedRune == "BloodRuneRitualDimensionalRending" && !_isRitualRuneUnlocked)
{
_popup.PopupEntity(Loc.GetString("rune-ritual-failed"), uid, uid, PopupType.MediumCaution);
return;
}
else if (selectedRune == "BloodRuneRitualDimensionalRending" && _isRitualRuneUnlocked)
{
var xform = Transform(uid);
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid) || !HasComp<BecomesStationComponent>(xform.GridUid.Value))
{
_popup.PopupEntity(Loc.GetString("rune-ritual-failed"), uid, uid, PopupType.MediumCaution);
return;
}
bool isValidSurface = true;
var cultistPosition = _transform.GetMapCoordinates(Transform(uid));
if (!_mapMan.TryFindGridAt(cultistPosition, out _, out _))
isValidSurface = false;
if (isValidSurface)
{
var ritualRune = _entityManager.SpawnEntity(TrySelectRuneEffect(selectedRune), Transform(uid).Coordinates);
_appearance.SetData(ritualRune, RuneColorVisuals.Color, TryFindColor(uid));
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(9.75f),
new BloodRuneDoAfterEvent(selectedRune, GetNetEntity(ritualRune)), uid)
{
BreakOnMove = true,
BreakOnDamage = true,
MovementThreshold = 0.01f,
NeedHand = false
});
}
else
{
_popup.PopupEntity(Loc.GetString("rune-ritual-failed"), uid, uid, PopupType.MediumCaution);
}
return;
}
var rune = _entityManager.SpawnEntity(TrySelectRuneEffect(selectedRune), Transform(uid).Coordinates);
_appearance.SetData(rune, RuneColorVisuals.Color, TryFindColor(uid));
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(4f),
new BloodRuneDoAfterEvent(selectedRune, GetNetEntity(rune)), uid)
{
BreakOnMove = true,
BreakOnDamage = true,
MovementThreshold = 0.01f,
NeedHand = false
});
}
private void DoAfterRuneSelect(EntityUid cultist, BloodCultistComponent component, BloodRuneDoAfterEvent args)
{
if (args.Cancelled)
{
_entityManager.DeleteEntity(GetEntity(args.Rune));
return;
}
var rune = _entityManager.SpawnEntity(args.SelectedRune, Transform(cultist).Coordinates);
_appearance.SetData(rune, RuneColorVisuals.Color, TryFindColor(cultist));
if (args.SelectedRune == "BloodRuneRitualDimensionalRending")
{
var xform = _entityManager.GetComponent<TransformComponent>(rune);
var msg = Loc.GetString("blood-ritual-warning",
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((rune, xform)))));
_chat.DispatchGlobalAnnouncement(msg, colorOverride: Color.Red);
_isRitualRuneUnlocked = false;
}
if (TryComp<BloodstreamComponent>(cultist, out var blood) && _blood.GetBloodLevel(cultist) > 0)
_blood.TryModifyBloodLevel(cultist, -5);
else
{
var damage = new DamageSpecifier { DamageDict = { { "Slash", 5 } } };
_damage.TryChangeDamage(cultist, damage, true);
}
_popup.PopupEntity(Loc.GetString("rune-select-complete"), cultist, cultist, PopupType.SmallCaution);
args.Handled = true;
}
private void OnRuneInteract(EntityUid rune, BloodRuneComponent component, InteractHandEvent args)
{
if (args.Handled || !HasComp<BloodCultistComponent>(args.User))
return;
if (rune is not { Valid: true } target)
return;
if (component.Prototype is null)
return;
OnRuneAfterInteract(target, component, args.User);
args.Handled = true;
}
private void OnRitualInteract(EntityUid rune, BloodRitualDimensionalRendingComponent component, InteractHandEvent args)
{
if (args.Handled || !HasComp<BloodCultistComponent>(args.User))
return;
var currentTime = _gameTiming.RealTime;
if (currentTime < component.ActivateTime)
{
var remainingTime = component.ActivateTime - currentTime;
_popup.PopupEntity(Loc.GetString("ritual-activate-too-soon", ("time", remainingTime.TotalSeconds)), args.User, args.User, PopupType.LargeCaution);
return;
}
if (rune is not { Valid: true } target || !CheckRitual(_transform.GetMapCoordinates(target), 9))
{
_popup.PopupEntity(Loc.GetString("ritual-activate-failed"), args.User, args.User, PopupType.LargeCaution);
return;
}
component.ActivateTime = currentTime + TimeSpan.FromSeconds(120);
component.Activate = true;
OnRitualAfterInteract(target, component);
var cultistEntities = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(_transform.GetMapCoordinates(target), 6f);
foreach (var cultistEntity in cultistEntities)
{
SendCultistMessage(cultistEntity.Owner, "ritual");
}
args.Handled = true;
}
private void OnRuneAfterInteract(EntityUid rune, BloodRuneComponent runeComp, EntityUid cultist)
{
var coords = _transform.GetMapCoordinates(rune);
if (!TryComp<UseDelayComponent>(rune, out var useDelay) || _useDelay.IsDelayed((rune, useDelay)))
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
return;
}
switch (runeComp.Prototype)
{
case "offering":
var targets = _entityLookup.GetEntitiesInRange<HumanoidAppearanceComponent>(coords, 1f);
foreach (var targetEntity in targets)
{
var target = targetEntity.Owner;
if (HasComp<BloodCultistComponent>(target) || HasComp<BloodCultConstructComponent>(target)
|| HasComp<NullRodOwnerComponent>(target))
continue;
if (!_entityManager.TryGetComponent<MobThresholdsComponent>(target, out var targetThresholds))
continue;
var currentState = targetThresholds.CurrentThresholdState;
if (currentState is MobState.Dead && (HasComp<MindShieldComponent>(target) || HasComp<BibleUserComponent>(target)
|| HasComp<BloodCultObjectComponent>(target)) && !HasComp<SyntheticOperatedComponent>(target))
{
if (CheckRuneActivate(coords, 3))
{
var cultistEntities = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 2f);
foreach (var cultistEntity in cultistEntities)
{
SendCultistMessage(cultistEntity.Owner, "offering");
}
var soulStone = _entityManager.SpawnEntity("BloodCultSoulStone", Transform(target).Coordinates);
if (TryComp<MindContainerComponent>(target, out var mindContainer) && mindContainer.Mind != null)
_mind.TransferTo(mindContainer.Mind.Value, soulStone);
// Gib
if (HasComp<BloodCultObjectComponent>(target))
{
_bloodCult.CheckTargetsConducted(target);
RemComp<BloodCultObjectComponent>(target);
}
var damage = new DamageSpecifier { DamageDict = { { "Blunt", 1000 } } };
_damage.TryChangeDamage(target, damage, true);
IncrementOfferingsCount();
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
}
else if (currentState != MobState.Dead && !HasComp<MindShieldComponent>(target) && !HasComp<BibleUserComponent>(target)
&& !HasComp<SyntheticOperatedComponent>(target))
{
if (CheckRuneActivate(coords, 2))
{
var cultistEntities = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 2f);
foreach (var cultistEntity in cultistEntities)
{
SendCultistMessage(cultistEntity.Owner, "offering");
}
_rejuvenate.PerformRejuvenate(target);
EnsureComp<AutoCultistComponent>(target);
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
}
else if (currentState is MobState.Dead && !HasComp<MindShieldComponent>(target) && !HasComp<BibleUserComponent>(target)
&& !HasComp<SyntheticOperatedComponent>(target))
{
if (CheckRuneActivate(coords, 1))
{
var cultistEntities = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 2f);
foreach (var cultistEntity in cultistEntities)
{
SendCultistMessage(cultistEntity.Owner, "offering");
}
var soulStone = _entityManager.SpawnEntity("BloodCultSoulStone", Transform(target).Coordinates);
if (TryComp<MindContainerComponent>(target, out var mindContainer) && mindContainer.Mind != null)
_mind.TransferTo(mindContainer.Mind.Value, soulStone);
// Gib
var damage = new DamageSpecifier { DamageDict = { { "Blunt", 1000 } } };
_damage.TryChangeDamage(target, damage, true);
IncrementOfferingsCount();
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
}
break;
case "teleport":
var runes = new List<EntityUid>();
var runeQuery = EntityQueryEnumerator<BloodRuneComponent>();
while (runeQuery.MoveNext(out var runeUid, out var runeCompQ))
{
if (runeCompQ.Prototype == "teleport" && runeUid != rune)
runes.Add(runeUid);
}
if (runes.Any() && CheckRuneActivate(coords, 1))
{
var randomRuneEntity = runes[_random.Next(runes.Count)];
var runeTransform = _entityManager.GetComponent<TransformComponent>(randomRuneEntity);
var runeCoords = runeTransform.Coordinates;
SendCultistMessage(cultist, "teleport");
_entityManager.SpawnEntity("BloodCultOutEffect", Transform(cultist).Coordinates);
_transform.SetCoordinates(cultist, runeCoords);
_entityManager.SpawnEntity("BloodCultInEffect", runeCoords);
_entityManager.DeleteEntity(randomRuneEntity);
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
case "empowering":
if (CheckRuneActivate(coords, 1))
{
if (TryComp<BloodCultistComponent>(cultist, out var comp) && comp.Empowering < 4)
{
SendCultistMessage(cultist, "empowering");
var netEntity = _entityManager.GetNetEntity(cultist);
RaiseNetworkEvent(new EmpoweringRuneMenuOpenedEvent(netEntity));
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
}
break;
case "revive":
if (CheckRuneActivate(coords, 1))
{
var revivetarget = _entityLookup.GetEntitiesInRange<BodyComponent>(coords, 1f);
foreach (var targetEntity in revivetarget)
{
var target = targetEntity.Owner;
if (!_entityManager.TryGetComponent<MobThresholdsComponent>(target, out var targetThresholds) || target == cultist)
continue;
var currentState = targetThresholds.CurrentThresholdState;
if (HasComp<BloodCultistComponent>(target) && HasComp<HumanoidAppearanceComponent>(target)
&& currentState is MobState.Dead)
{
if (GetOfferingsCount() >= 3)
{
SendCultistMessage(cultist, "revive");
_rejuvenate.PerformRejuvenate(target);
SubtractOfferingsCount();
if (TryComp<MindContainerComponent>(target, out var mind) && mind.Mind is null
&& !HasComp<GhostRoleComponent>(target))
{
var formattedCommand = string.Format(
"makeghostrole {0} {1} {2} {3}",
target,
Loc.GetString("ghost-role-information-cultist"),
Loc.GetString("ghost-role-information-cultist-desc"),
Loc.GetString("ghost-role-information-cultist-rules")
);
_consoleHost.ExecuteCommand(formattedCommand);
}
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
}
else if (HasComp<BloodCultistComponent>(target) && HasComp<HumanoidAppearanceComponent>(target)
&& TryComp<MindContainerComponent>(target, out var mind) && mind.Mind is null && !HasComp<GhostRoleComponent>(target))
{
SendCultistMessage(cultist, "revive");
var formattedCommand = string.Format(
"makeghostrole {0} {1} {2} {3}",
target,
Loc.GetString("ghost-role-information-cultist"),
Loc.GetString("ghost-role-information-cultist-desc"),
Loc.GetString("ghost-role-information-cultist-rules")
);
_consoleHost.ExecuteCommand(formattedCommand);
}
else if (HasComp<BodyComponent>(target) && !HasComp<BloodCultistComponent>(target) && currentState is MobState.Dead
&& !HasComp<BorgChassisComponent>(target) && !HasComp<BloodCultObjectComponent>(target)
&& !HasComp<HumanoidAppearanceComponent>(target)/*Stop killing humanoid this way*/)
{
SendCultistMessage(cultist, "revive");
// Gib
var damage = new DamageSpecifier { DamageDict = { { "Blunt", 1000 } } };
_damage.TryChangeDamage(target, damage, true);
IncrementOfferingsCount();
break;
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
}
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
case "barrier":
if (CheckRuneActivate(coords, 1))
{
if (!runeComp.BarrierActive)
{
runeComp.BarrierActive = true;
SendCultistMessage(cultist, "barrier");
var nearbyRunes = _entityLookup.GetEntitiesInRange<BloodRuneComponent>(coords, 1f)
.Where(r => EntityManager.TryGetComponent(r, out BloodRuneComponent? nearbyRuneComp)
&& nearbyRuneComp.Prototype == "barrier" && r.Owner != rune)
.ToList();
Entity<BloodRuneComponent>? randomRune = nearbyRunes.Any()
? nearbyRunes[new Random().Next(nearbyRunes.Count)]
: null;
if (randomRune != null)
{
var randomRuneUid = randomRune.Value;
if (TryComp<BloodRuneComponent>(randomRuneUid, out var randomRuneComp) && !randomRuneComp.BarrierActive)
{
randomRuneComp.BarrierActive = true;
if (EntityManager.TryGetComponent(randomRuneUid, out PhysicsComponent? randomPhysicsComp))
{
var fixture = _fixtures.GetFixtureOrNull(randomRuneUid, "barrier");
if (fixture != null)
{
_physics.SetHard(randomRuneUid, fixture, randomRuneComp.BarrierActive);
}
}
}
}
if (EntityManager.TryGetComponent(rune, out PhysicsComponent? physicsComp))
{
var fixture = _fixtures.GetFixtureOrNull(rune, "barrier");
if (fixture != null)
{
_physics.SetHard(rune, fixture, runeComp.BarrierActive);
}
}
var barrierRunes = new List<EntityUid>();
var barrierRuneQuery = EntityQueryEnumerator<BloodRuneComponent>();
while (barrierRuneQuery.MoveNext(out var runeUid, out var runeCompQ))
{
if (runeCompQ.Prototype == "barrier")
barrierRunes.Add(runeUid);
}
var damageFormula = 2 * barrierRunes.Count;
var damage = new DamageSpecifier { DamageDict = { { "Slash", damageFormula } } };
_damage.TryChangeDamage(cultist, damage, true);
}
else
{
runeComp.BarrierActive = false;
SendCultistMessage(cultist, "barrier");
if (EntityManager.TryGetComponent(rune, out PhysicsComponent? physicsComp))
{
var fixture = _fixtures.GetFixtureOrNull(rune, "barrier");
if (fixture != null)
{
_physics.SetHard(rune, fixture, runeComp.BarrierActive);
}
}
}
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
case "summoning":
if (CheckRuneActivate(coords, 2))
{
var cultistEntities = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 2f);
foreach (var cultistEntity in cultistEntities)
{
SendCultistMessage(cultist, "summoning");
}
var netEntity = _entityManager.GetNetEntity(cultist);
RaiseNetworkEvent(new SummoningRuneMenuOpenedEvent(netEntity));
_entityManager.DeleteEntity(rune);
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
case "bloodboil":
if (CheckRuneActivate(coords, 2))
{
RemComp<BloodRuneComponent>(rune);
var cultistEntities = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 2f);
foreach (var cultistEntity in cultistEntities)
{
SendCultistMessage(cultistEntity.Owner, "bloodboil");
}
Task.Run(async () =>
{
var damageValues = new[] { 5, 10, 10 };
for (int i = 0; i < 3; i++)
{
var targetsFlammable = _entityLookup.GetEntitiesInRange<FlammableComponent>(coords, 10f)
.Where(flammableEntity =>
!HasComp<BloodCultistComponent>(flammableEntity.Owner))
.ToList();
foreach (var targetFlammable in targetsFlammable)
{
if (HasComp<NullRodOwnerComponent>(targetFlammable.Owner))
continue;
if (TryComp<FlammableComponent>(targetFlammable.Owner, out var flammable))
{
flammable.FireStacks = 3f;
_flammable.Ignite(targetFlammable.Owner, rune);
var damage = new DamageSpecifier { DamageDict = { { "Heat", damageValues[i] } } };
_damage.TryChangeDamage(cultist, damage, false);
}
}
if (i < 2)
{
await Task.Delay(5000);
}
}
_entityManager.DeleteEntity(rune);
});
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
break;
case "spiritrealm":
if (CheckRuneActivate(coords, 1))
{
SendCultistMessage(cultist, "spiritrealm");
if (TryComp<MindContainerComponent>(cultist, out var mindContainer) && mindContainer.Mind != null)
{
var mindSystem = _entityManager.System<SharedMindSystem>();
var metaDataSystem = _entityManager.System<MetaDataSystem>();
var transformSystem = _entityManager.System<TransformSystem>();
var gameTicker = _entityManager.System<GameTicker>();
if (!mindSystem.TryGetMind(cultist, out var mindId, out var mind))
return;
if (mind.VisitingEntity != default && _entityManager.TryGetComponent<GhostComponent>(mind.VisitingEntity, out var oldGhostComponent))
{
mindSystem.UnVisit(mindId, mind);
if (oldGhostComponent.CanGhostInteract)
return;
}
var canReturn = mind.CurrentEntity != null
&& !_entityManager.HasComponent<GhostComponent>(mind.CurrentEntity);
var ghost = _entityManager.SpawnEntity(BloodCultObserver, coords);
transformSystem.AttachToGridOrMap(ghost, _entityManager.GetComponent<TransformComponent>(ghost));
if (canReturn)
{
if (!string.IsNullOrWhiteSpace(mind.CharacterName))
metaDataSystem.SetEntityName(ghost, mind.CharacterName);
mindSystem.Visit(mindId, ghost, mind);
}
else
{
metaDataSystem.SetEntityName(ghost, Name(cultist));
mindSystem.TransferTo(mindId, ghost, mind: mind);
}
var comp = _entityManager.GetComponent<GhostComponent>(ghost);
_action.RemoveAction(ghost, comp.ToggleGhostBarActionEntity); // Ghost-Bar-Block
_action.AddAction(ghost, ActionComms);
_ghost.SetCanReturnToBody((ghost, comp), canReturn);
break;
}
else
{
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
}
}
break;
default:
_popup.PopupEntity(Loc.GetString("rune-activate-failed"), cultist, cultist, PopupType.MediumCaution);
break;
}
if (_entityManager.EntityExists(rune))
_useDelay.TryResetDelay((rune, useDelay));
}
private void OnRitualAfterInteract(EntityUid rune, BloodRitualDimensionalRendingComponent runeComp)
{
var xform = _entityManager.GetComponent<TransformComponent>(rune);
var msg = Loc.GetString("blood-ritual-activate-warning",
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((rune, xform)))));
_chat.DispatchGlobalAnnouncement(msg, playSound: false, colorOverride: Color.Red);
_audio.PlayGlobal(new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/bloodcult_scribe.ogg"), Filter.Broadcast(), true);
Timer.Spawn(TimeSpan.FromSeconds(45), () =>
{
if (runeComp.Activate)
{
var coords = Transform(rune).Coordinates;
_entityManager.DeleteEntity(rune);
_entityManager.SpawnEntity("BloodCultDistortedEffect", coords);
string currentGod = GetCurrentGod() switch
{
"Narsie" => "MobNarsieSpawn",
"Reaper" => "MobReaperSpawn",
"Kharin" => "MobKharinSpawn",
_ => "MobNarsieSpawn"
};
_entityManager.SpawnEntity(currentGod, coords);
RaiseLocalEvent(new GodCalledEvent());
var nearbyCultists = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 6f)
.ToList();
foreach (var target in nearbyCultists)
{
var harvester = _entityManager.SpawnEntity("MobConstructHarvester", Transform(target).Coordinates);
if (TryComp<MindContainerComponent>(target, out var mindContainer) && mindContainer.Mind != null)
_mind.TransferTo(mindContainer.Mind.Value, harvester);
var damage = new DamageSpecifier { DamageDict = { { "Blunt", 1000 } } };
_damage.TryChangeDamage(target.Owner, damage, true);
}
}
else
{
var cultists = EntityQueryEnumerator<BloodCultistComponent>();
while (cultists.MoveNext(out var cultist, out _))
{
_popup.PopupEntity(Loc.GetString("ritual-failed"), cultist, cultist, PopupType.LargeCaution);
}
}
});
}
#endregion
#region Other
private void OnEmpoweringSelected(EmpoweringRuneMenuClosedEvent args)
{
var cultist = _entityManager.GetEntity(args.Uid);
if (!HasComp<BloodCultistComponent>(cultist))
return;
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, cultist, TimeSpan.FromSeconds(4f), new EmpoweringDoAfterEvent(args.SelectedSpell), cultist)
{
BreakOnMove = true,
BreakOnDamage = true,
MovementThreshold = 0.01f,
NeedHand = true
});
}
private void OnEmpoweringDoAfter(EntityUid cultist, BloodCultistComponent component, EmpoweringDoAfterEvent args)
{
if (args.Cancelled) return;
var actionEntityUid = _action.AddAction(cultist, args.SelectedSpell);
component.SelectedEmpoweringSpells.Add(actionEntityUid);
component.Empowering++;
if (TryComp<BloodstreamComponent>(cultist, out var blood) && _blood.GetBloodLevel(cultist) > 0)
_blood.TryModifyBloodLevel(cultist, -5);
else
{
var damage = new DamageSpecifier { DamageDict = { { "Slash", 2 } } };
_damage.TryChangeDamage(cultist, damage, true);
}
}
private void OnSummoningSelected(SummoningSelectedEvent args)
{
var user = _entityManager.GetEntity(args.User);
var target = _entityManager.GetEntity(args.Target);
_entityManager.SpawnEntity("BloodCultOutEffect", Transform(target).Coordinates);
_transform.SetCoordinates(target, Transform(user).Coordinates);
_entityManager.SpawnEntity("BloodCultInEffect", Transform(user).Coordinates);
}
private bool CheckRuneActivate(MapCoordinates coords, int needCount)
{
var constructsCount = _entityLookup.GetEntitiesInRange<BloodCultConstructComponent>(coords, 2f).Count();
var aliveCultistsCount = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 2f)
.Count(cultist => !_entityManager.TryGetComponent<MobThresholdsComponent>(cultist.Owner, out var thresholds)
|| thresholds.CurrentThresholdState != MobState.Dead);
return aliveCultistsCount + constructsCount >= needCount;
}
private bool CheckRitual(MapCoordinates coords, int needCount)
{
var aliveCultistsCount = _entityLookup.GetEntitiesInRange<BloodCultistComponent>(coords, 6f)
.Count(cultist => !_entityManager.TryGetComponent<MobThresholdsComponent>(cultist.Owner, out var thresholds)
|| thresholds.CurrentThresholdState != MobState.Dead);
return aliveCultistsCount >= needCount;
}
private void SendCultistMessage(EntityUid cultist, string messageType)
{
string message = messageType switch
{
"offering" => Loc.GetString("blood-cultist-offering-message"),
"teleport" => Loc.GetString("blood-cultist-teleport-message"),
"empowering" => Loc.GetString("blood-cultist-empowering-message"),
"revive" => Loc.GetString("blood-cultist-revive-message"),
"barrier" => Loc.GetString("blood-cultist-barrier-message"),
"summoning" => Loc.GetString("blood-cultist-summoning-message"),
"bloodboil" => Loc.GetString("blood-cultist-bloodboil-message"),
"spiritrealm" => Loc.GetString("blood-cultist-spiritrealm-message"),
"ritual" => Loc.GetString("blood-cultist-ritual-message"),
_ => Loc.GetString("blood-cultist-default-message")
};
_chat.TrySendInGameICMessage(cultist, message, InGameICChatType.Whisper, ChatTransmitRange.Normal, checkRadioPrefix: false);
}
private string TrySelectRuneEffect(string messageType)
{
string message = messageType switch
{
"BloodRuneOffering" => "BloodRuneOfferingEffect",
"BloodRuneTeleport" => "BloodRuneTeleportEffect",
"BloodRuneEmpowering" => "BloodRuneEmpoweringEffect",
"BloodRuneRevive" => "BloodRuneReviveEffect",
"BloodRuneBarrier" => "BloodRuneBarrierEffect",
"BloodRuneSummoning" => "BloodRuneSummoningEffect",
"BloodRuneBloodBoil" => "BloodRuneBloodBoilEffect",
"BloodRuneSpiritealm" => "BloodRuneSpiritealmEffect",
"BloodRuneRitualDimensionalRending" => "BloodRuneRitualDimensionalRendingEffect",
_ => "BloodRuneOfferingEffect"
};
return message;
}
private void OnDaggerInteract(Entity<BloodDaggerComponent> ent, ref UseInHandEvent args)
{
var user = args.User;
if (!HasComp<BloodCultistComponent>(user))
{
var dropEvent = new DropHandItemsEvent();
RaiseLocalEvent(user, ref dropEvent);
var damage = new DamageSpecifier { DamageDict = { { "Slash", 5 } } };
_damage.TryChangeDamage(user, damage, true);
_popup.PopupEntity(Loc.GetString("blood-dagger-failed-interact"), user, user, PopupType.SmallCaution);
return;
}
var netEntity = _entityManager.GetNetEntity(args.User);
RaiseNetworkEvent(new RunesMenuOpenedEvent(netEntity));
args.Handled = true;
}
private bool IsInSpace(EntityUid cultist)
{
var cultistPosition = _transform.GetMapCoordinates(Transform(cultist));
if (!_mapMan.TryFindGridAt(cultistPosition, out _, out _))
return true;
return false;
}
private Color TryFindColor(EntityUid cultist)
{
if (!TryComp<BloodstreamComponent>(cultist, out var bloodStreamComponent))
return Color.White;
string? bloodReagentPrototypeId = null;
if (bloodStreamComponent.BloodReferenceSolution.Contents.Count > 0)
{
var reagentQuantity = bloodStreamComponent.BloodReferenceSolution.Contents[0];
bloodReagentPrototypeId = reagentQuantity.Reagent.Prototype;
}
if (bloodReagentPrototypeId == null)
return Color.White;
if (!_prototypeManager.TryIndex(bloodReagentPrototypeId, out ReagentPrototype? reagentPrototype))
return Color.White;
return reagentPrototype.SubstanceColor;
}
private void DoAfterInteractRune(BloodRuneCleaningDoAfterEvent args)
{
if (args.Cancelled) return;
_entityManager.DeleteEntity(args.Target);
}
private void DoAfterInteractRune(EntityUid cultist, BloodCultistComponent component, BloodRuneCleaningDoAfterEvent args)
{
if (args.Cancelled) return;
_entityManager.DeleteEntity(args.Target);
}
private static void IncrementOfferingsCount()
{
_offerings++;
}
private static void SubtractOfferingsCount()
{
_offerings -= 3;
}
private static int GetOfferingsCount()
{
return _offerings;
}
#endregion
}

View File

@@ -0,0 +1,920 @@
using System.Linq;
using System.Numerics;
using Content.Server.Body.Systems;
using Content.Server.Cargo.Components;
using Content.Server.Chat.Systems;
using Content.Server.Dice;
using Content.Server.Economy.SlotMachine;
using Content.Server.Guardian;
using Content.Server.Hallucinations;
using Content.Server.Polymorph.Systems;
using Content.Server.Revolutionary.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Stack;
using Content.Server.Station.Components;
using Content.Server.Surgery;
using Content.Shared.Administration.Systems;
using Content.Shared.Armor;
using Content.Shared.Blood.Cult.Components;
using Content.Shared.Body.Components;
using Content.Shared.Card.Tarot;
using Content.Shared.Card.Tarot.Components;
using Content.Shared.Chat;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
using Content.Shared.Disease.Components;
using Content.Shared.EnergyShield;
using Content.Shared.FixedPoint;
using Content.Shared.Ghost;
using Content.Shared.Gravity;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Lock;
using Content.Shared.Mobs.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Pinpointer;
using Content.Shared.Polymorph;
using Content.Shared.Popups;
using Content.Shared.Stacks;
using Content.Shared.StatusEffectNew;
using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tag;
using Content.Shared.Throwing;
using Content.Shared.Tiles;
using Content.Shared.Traits.Assorted;
using Content.Shared.Trigger.Components.Triggers;
using Content.Shared.Trigger.Systems;
using Content.Shared.VendingMachines;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Card.Tarot;
public sealed class CardTarotSystem : EntitySystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly BloodstreamSystem _blood = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly DamageableSystem _damage = default!;
[Dependency] private readonly DiceOfFateSystem _dice = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly HallucinationsSystem _hallucinations = default!;
[Dependency] private readonly IngestionSystem _ingestion = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly PolymorphSystem _polymorph = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
[Dependency] private readonly SlotMachineSystem _slotMachine = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly SurgerySystem _surgery = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly TriggerSystem _trigger = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
// 200,000 static variables are ready, and another million is on the way
private static readonly EntProtoId Ash = "Ash";
private static readonly EntProtoId ClusterBang = "ClusterBangFull";
private static readonly EntProtoId ClusterGrenade = "ClusterGrenade";
private static readonly EntProtoId CursedSlotMachine = "CursedSlotMachine";
private static readonly EntProtoId Drunk = "StatusEffectDrunk";
private static readonly EntProtoId EmptyCardTarot = "CardTarotNotEnchanted";
private static readonly EntProtoId Pill = "StrangePill";
private static readonly EntProtoId RandomContainer = "RandomContainerBlank";
private static readonly EntProtoId RandomVending = "RandomVending";
private static readonly EntProtoId Rock = "WallRock";
private static readonly EntProtoId Smoke = "AdminInstantEffectSmoke30";
private static readonly EntProtoId SpaceCash = "SpaceCash";
private static readonly EntProtoId Stand = "MobHoloparasiteGuardian";
private static readonly List<EntProtoId> DeathEnt = new() {
"BloodCultConstruct", "BloodCultSoulStone"
};
private static readonly List<EntProtoId> JusticeEnt = new() {
"MedkitFilled", "TearGasGrenade", "WeaponWandPolymorphDoor", "SpaceCash100"
};
private static readonly ProtoId<DamageTypePrototype> BluntDamage = "Blunt";
private static readonly ProtoId<DamageTypePrototype> CellularDamage = "Cellular";
private static readonly ProtoId<DamageTypePrototype> HeatDamage = "Heat";
private static readonly ProtoId<DamageTypePrototype> PoisonDamage = "Poison";
private static readonly ProtoId<DamageTypePrototype> RadiationDamage = "Blunt";
private static readonly ProtoId<PolymorphPrototype> ChariotStatue = "ChariotStatue";
private static readonly ProtoId<TagPrototype> Grenade = "HandGrenade";
private static readonly ProtoId<TagPrototype> SlowImmune = "SlowImmune";
private static readonly ProtoId<TagPrototype> StunImmune = "StunImmune";
private static readonly string Drug = "Desoxyephedrine";
private static readonly string NotHeal = "Puncturase"; // LoL
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CardTarotComponent, AfterInteractEvent>(OnTarotInteract);
SubscribeLocalEvent<CardTarotComponent, UseInHandEvent>(OnUseTarot);
}
#region Card Tarot
private void OnTarotInteract(Entity<CardTarotComponent> ent, ref AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target is not { Valid: true } target)
return;
if (ent.Comp.Card == CardTarot.NotEnchanted)
{
_popup.PopupEntity("tarot-card-not-enchanted", args.User, args.User);
return;
}
PerformTarotEffect(target, args.User, ent);
args.Handled = true;
}
private void OnUseTarot(Entity<CardTarotComponent> ent, ref UseInHandEvent args)
{
if (args.Handled)
return;
if (ent.Comp.Card == CardTarot.NotEnchanted)
{
_popup.PopupEntity("tarot-card-not-enchanted", args.User, args.User);
return;
}
PerformTarotEffect(args.User, args.User, ent);
args.Handled = true;
}
private void PerformTarotEffect(EntityUid target, EntityUid user, Entity<CardTarotComponent> card)
{
var isReversed = card.Comp.CardType == CardTarotType.Reversed;
switch (card.Comp.Card)
{
case CardTarot.NotEnchanted:
_popup.PopupEntity("tarot-card-not-enchanted", user, user);
return;
case CardTarot.Fool:
PerformFool(target, isReversed);
break;
case CardTarot.Magician:
PerformMagician(target, isReversed);
break;
case CardTarot.HighPriestess:
PerformHighPriestess(target, isReversed);
break;
case CardTarot.Empress:
PerformEmpress(target, isReversed);
break;
case CardTarot.Emperor:
PerformEmperor(target, isReversed);
break;
case CardTarot.Hierophant:
PerformHierophant(target, isReversed);
break;
case CardTarot.Lovers:
PerformLovers(target, isReversed);
break;
case CardTarot.Chariot:
PerformChariot(target, isReversed);
break;
case CardTarot.Justice:
PerformJustice(target, isReversed);
break;
case CardTarot.Hermit:
PerformHermit(target, isReversed);
break;
case CardTarot.WheelOfFortune:
PerformWheelOfFortune(target, isReversed);
break;
case CardTarot.Strength:
PerformStrength(target, isReversed);
break;
case CardTarot.HangedMan:
PerformHangedMan(target, isReversed);
break;
case CardTarot.Death:
PerformDeath(user, target, isReversed);
break;
case CardTarot.Temperance:
PerformTemperance(target, isReversed);
break;
case CardTarot.Devil:
PerformDevil(target, isReversed);
break;
case CardTarot.Tower:
PerformTower(target, isReversed);
break;
case CardTarot.Stars:
PerformStars(target, isReversed);
break;
case CardTarot.Moon:
PerformMoon(target, isReversed);
break;
case CardTarot.Sun:
PerformSun(target, isReversed);
break;
case CardTarot.Judgement:
PerformJudgement(target, isReversed);
break;
case CardTarot.TheWorld:
PerformTheWorld(target, isReversed);
break;
default: break;
}
var coords = Transform(user).Coordinates;
var ash = Spawn(Ash, coords);
_throwing.TryThrow(ash, _random.NextVector2());
_audio.PlayPredicted(card.Comp.UseSound, coords, user);
_popup.PopupEntity(Loc.GetString("tarot-used", ("name", Identity.Name(user, EntityManager)),
("type", Loc.GetString($"tarot-card-{card.Comp.Card.ToString().ToLower()}"))),
user, PopupType.Medium);
QueueDel(card);
}
#region Card Effects
private void PerformFool(EntityUid target, bool reversed)
{
if (reversed)
{
if (_inventory.TryGetSlots(target, out var slots))
{
foreach (var slot in slots)
{
_inventory.TryUnequip(target, slot.Name, force: true);
}
}
}
else
{
EntityUid? shuttle = null;
var shuttles = EntityQueryEnumerator<ArrivalsShuttleComponent>();
while (shuttles.MoveNext(out var uid, out _))
{
shuttle = uid;
break;
}
if (shuttle == null)
return;
_transform.SetCoordinates(target, Transform(shuttle.Value).Coordinates);
}
}
private void PerformMagician(EntityUid target, bool reversed)
{
if (reversed)
{
var nearbyEntity = _entityLookup.GetEntitiesInRange<TransformComponent>(Transform(target).Coordinates, 6f, LookupFlags.Dynamic)
.Where(e => !e.Comp.Anchored).ToList();
foreach (var entity in nearbyEntity)
{
var entityUid = entity.Owner;
if (entityUid == target)
continue;
var targetPosition = _transform.GetWorldPosition(target);
var entityPosition = _transform.GetWorldPosition(entityUid);
var direction = (entityPosition - targetPosition).Normalized();
_physics.ApplyLinearImpulse(entityUid, direction * 2000f);
}
}
else
{
_statusEffects.TrySetStatusEffectDuration(target, "StatusEffectPainNumbness", TimeSpan.FromMinutes(2));
}
}
private void PerformHighPriestess(EntityUid target, bool reversed)
{
if (reversed)
{
/// Temporarily empty
}
else
{
var time = TimeSpan.FromSeconds(20);
_stun.TryAddParalyzeDuration(target, time);
Timer.Spawn(time, () =>
{
var damage = new DamageSpecifier { DamageDict = { { BluntDamage, 60 } } };
_damage.TryChangeDamage(target, damage);
});
}
}
private void PerformEmpress(EntityUid target, bool reversed)
{
if (reversed)
{
var nearbyEntity = _entityLookup.GetEntitiesInRange<MobStateComponent>(Transform(target).Coordinates, 6f)
.Where(e => e.Owner != target).ToList();
foreach (var entity in nearbyEntity)
{
var entityUid = entity.Owner;
if (HasComp<PacifiedComponent>(entityUid))
return;
EnsureComp<PacifiedComponent>(entityUid);
Timer.Spawn(TimeSpan.FromSeconds(40), () => { RemComp<PacifiedComponent>(entityUid); });
}
}
else
{
if (!TryComp<BloodstreamComponent>(target, out var bloodstream) || bloodstream.BloodSolution == null)
return;
var drugQuantity = new ReagentQuantity(Drug, FixedPoint2.New(4.5));
var notHealQuantity = new ReagentQuantity(NotHeal, FixedPoint2.New(12));
_solution.TryAddReagent(bloodstream.BloodSolution.Value, drugQuantity, out _);
_solution.TryAddReagent(bloodstream.BloodSolution.Value, notHealQuantity, out _);
}
}
private void PerformEmperor(EntityUid target, bool reversed)
{
if (reversed)
{
var selected = new List<EntityUid>();
var commandQuery = EntityQueryEnumerator<CommandStaffComponent>();
while (commandQuery.MoveNext(out var uid, out _))
selected.Add(uid);
if (selected.Count == 0)
return;
_transform.SetCoordinates(target, Transform(_random.Pick(selected)).Coordinates);
}
else
{
EntityUid? bridgeUid = null;
var beaconQuery = EntityQueryEnumerator<NavMapBeaconComponent>();
while (beaconQuery.MoveNext(out var uid, out var beacon))
{
if (beacon.DefaultText == "station-beacon-bridge")
{
bridgeUid = uid;
break;
}
}
if (bridgeUid == null)
return;
_transform.SetCoordinates(target, Transform(bridgeUid.Value).Coordinates);
}
}
private void PerformHierophant(EntityUid target, bool reversed)
{
if (reversed)
{
/// Temporarily empty
}
else
{
var slot = "jumpsuit";
if (!_inventory.TryGetSlotEntity(target, slot, out var clothing))
return;
if (!HasComp<BloodShieldActivaebleComponent>(clothing))
{
EnsureComp<BloodShieldActivaebleComponent>(clothing.Value).CurrentSlot = slot;
var shield = EnsureComp<EnergyShieldOwnerComponent>(target);
shield.ShieldEntity = Spawn("BloodCultShieldEffect", Transform(target).Coordinates);
_transform.SetParent(shield.ShieldEntity.Value, target);
}
}
}
private void PerformLovers(EntityUid target, bool reversed)
{
if (reversed)
{
var damage = new DamageSpecifier { DamageDict = { { BluntDamage, 20 }, { HeatDamage, 20 } } };
_damage.TryChangeDamage(target, damage, true);
_blood.TryModifyBloodLevel(target, -120);
}
else
{
var damage = new DamageSpecifier { DamageDict = { { BluntDamage, -40 }, { HeatDamage, -40 }, { PoisonDamage, -40 } } };
_damage.TryChangeDamage(target, damage, true);
_blood.TryModifyBloodLevel(target, 100);
}
}
private void PerformChariot(EntityUid target, bool reversed)
{
if (reversed)
{
_polymorph.PolymorphEntity(target, ChariotStatue);
}
else
{
var time = TimeSpan.FromSeconds(10);
_statusEffects.TrySetStatusEffectDuration(target, "StatusEffectDesoxyStamina", time);
bool isStunImmuned = false;
bool isSlowImmuned = false;
bool isPacified = false;
if (!_tag.HasTag(target, StunImmune))
{
isStunImmuned = _tag.TryAddTag(target, StunImmune);
}
if (!_tag.HasTag(target, SlowImmune))
{
isSlowImmuned = _tag.TryAddTag(target, SlowImmune);
}
if (!HasComp<PacifiedComponent>(target))
{
EnsureComp<PacifiedComponent>(target);
isPacified = true;
}
Timer.Spawn(time, () =>
{
if (isStunImmuned) _tag.RemoveTag(target, StunImmune);
if (isSlowImmuned) _tag.RemoveTag(target, SlowImmune);
if (isPacified) RemComp<PacifiedComponent>(target);
});
}
}
private void PerformJustice(EntityUid target, bool reversed)
{
if (reversed)
{
Spawn(RandomContainer, Transform(target).Coordinates);
}
else
{
var coords = Transform(target).Coordinates;
foreach (var ent in JusticeEnt)
Spawn(ent, coords);
}
}
private void PerformHermit(EntityUid target, bool reversed)
{
if (reversed)
{
var allEnt = new HashSet<EntityUid>();
var nearbyGuns = _entityLookup.GetEntitiesInRange<GunComponent>(Transform(target).Coordinates, 6f);
var nearbyMelees = _entityLookup.GetEntitiesInRange<MeleeWeaponComponent>(Transform(target).Coordinates, 6f)
.Where(e => !HasComp<MobStateComponent>(e) && !Transform(e).Anchored);
var nearbyArmor = _entityLookup.GetEntitiesInRange<ArmorComponent>(Transform(target).Coordinates, 6f);
var nearbyGrenades = _entityLookup.GetEntitiesInRange<TriggerOnUseComponent>(Transform(target).Coordinates, 6f)
.Where(e => _tag.HasTag(e, Grenade));
allEnt.UnionWith(nearbyGuns.Select(e => e.Owner));
allEnt.UnionWith(nearbyMelees.Select(e => e.Owner));
allEnt.UnionWith(nearbyArmor.Select(e => e.Owner));
allEnt.UnionWith(nearbyGrenades.Select(e => e.Owner));
foreach (var ent in allEnt)
{
var cash = Spawn(SpaceCash, Transform(ent).Coordinates);
if (TryComp<StackComponent>(cash, out var stack) && TryComp<StaticPriceComponent>(ent, out var price))
_stack.SetCount((cash, stack), (int)price.Price);
QueueDel(ent);
}
}
else
{
var map = Transform(target).MapID;
var selected = new List<EntityUid>();
var vendigsQuery = EntityQueryEnumerator<VendingMachineComponent>();
while (vendigsQuery.MoveNext(out var uid, out _))
{
if (map == Transform(uid).MapID)
selected.Add(uid);
}
if (selected.Count == 0)
return;
_transform.SetCoordinates(target, Transform(_random.Pick(selected)).Coordinates);
}
}
private void PerformWheelOfFortune(EntityUid target, bool reversed)
{
if (reversed)
{
var luck = _random.Next(1, 21);
_dice.RollFate(target, luck);
}
else
{
Spawn(RandomVending, Transform(target).Coordinates);
}
}
private void PerformStrength(EntityUid target, bool reversed)
{
if (reversed)
{
var nearbyEntity = _entityLookup.GetEntitiesInRange<MobStateComponent>(Transform(target).Coordinates, 6f);
foreach (var entity in nearbyEntity)
{
var entityUid = entity.Owner;
_hallucinations.StartHallucinations(entityUid, "Hallucinations", TimeSpan.FromMinutes(2), true, "MindBreaker");
}
}
else
{
if (!TryComp<DamageableComponent>(target, out var damageable))
return;
var oldMod = damageable.DamageModifierSetId;
_damage.SetDamageModifierSetId(target, "VampireBloodSwell");
Timer.Spawn(TimeSpan.FromSeconds(30), () => { _damage.SetDamageModifierSetId(target, oldMod); });
}
}
private void PerformHangedMan(EntityUid target, bool reversed)
{
if (reversed)
{
var slotMachine = Spawn(CursedSlotMachine, Transform(target).Coordinates);
_slotMachine.FreeSpeen(slotMachine, target);
}
else
{
_gravity.RefreshWeightless(target, false);
Timer.Spawn(TimeSpan.FromMinutes(1), () => { _gravity.RefreshWeightless(target, true); });
}
}
private void PerformDeath(EntityUid user, EntityUid target, bool reversed)
{
if (reversed)
{
var coords = Transform(target).Coordinates;
foreach (var ent in DeathEnt)
Spawn(ent, coords);
}
else
{
var nearbyEntity = _entityLookup.GetEntitiesInRange<MobStateComponent>(Transform(target).Coordinates, 6f)
.Where(e => e.Owner != user);
var damage = new DamageSpecifier { DamageDict = { { BluntDamage, 20 }, { HeatDamage, 20 } } };
foreach (var entity in nearbyEntity)
{
_damage.TryChangeDamage(entity.Owner, damage, true);
}
}
}
private void PerformTemperance(EntityUid target, bool reversed)
{
if (reversed)
{
for (var i = 0; i < 5; i++)
{
var pill = Spawn(Pill, Transform(target).Coordinates);
if (!_ingestion.TryIngest(target, pill))
break;
}
}
else
{
_statusEffects.TryRemoveStatusEffect(target, Drunk);
if (TryComp<DiseaseCarrierComponent>(target, out var disease))
disease.Diseases.Clear();
if (TryComp<DamageableComponent>(target, out var damageable))
{
var damageTypes = new[] { RadiationDamage, PoisonDamage };
foreach (var damageType in damageTypes)
{
if (damageable.Damage.DamageDict.TryGetValue(damageType, out var currentDamage) && currentDamage > 0)
{
var healSpecifier = new DamageSpecifier { DamageDict = { { damageType, -currentDamage } } };
_damage.TryChangeDamage(target, healSpecifier, true);
}
}
}
}
}
private void PerformDevil(EntityUid target, bool reversed)
{
if (reversed)
{
var grenade = Spawn(ClusterBang, Transform(target).Coordinates);
_trigger.Trigger(grenade);
}
else
{
var nearbyEntity = _entityLookup.GetEntitiesInRange<DamageableComponent>(Transform(target).Coordinates, 6f)
.Where(e => e.Owner != target && HasComp<MobStateComponent>(e)).Select(e => e.Owner).ToList();
var heal = new DamageSpecifier { DamageDict = { { BluntDamage, -45 }, { HeatDamage, -45 } } };
_damage.TryChangeDamage(target, heal, true);
_popup.PopupEntity(Loc.GetString("tarot-devil-healed"), target, target);
Timer.Spawn(TimeSpan.FromSeconds(3), () =>
{
if (!Exists(target))
return;
StartDamagePhase(nearbyEntity);
});
}
}
private void StartDamagePhase(List<EntityUid> initialNearby)
{
var validTargets = initialNearby.Where(Exists).ToList();
if (validTargets.Count == 0)
return;
var damageTicks = 30;
var currentDamageTick = 0;
var totalBluntPerTarget = 22;
var totalHeatPerTarget = 23;
var bluntPerSecondPerTarget = totalBluntPerTarget / (float)damageTicks;
var heatPerSecondPerTarget = totalHeatPerTarget / (float)damageTicks;
var damageDealt = new Dictionary<EntityUid, (float Blunt, float Heat)>();
foreach (var target in validTargets)
{
damageDealt[target] = (0, 0);
}
void DamageTick()
{
validTargets = validTargets.Where(Exists).ToList();
if (validTargets.Count == 0)
return;
foreach (var target in validTargets)
{
var expectedBlunt = bluntPerSecondPerTarget * (currentDamageTick + 1);
var expectedHeat = heatPerSecondPerTarget * (currentDamageTick + 1);
var (dealtBlunt, dealtHeat) = damageDealt[target];
var bluntThisTick = (int)(expectedBlunt - dealtBlunt);
var heatThisTick = (int)(expectedHeat - dealtHeat);
if (bluntThisTick > 0 || heatThisTick > 0)
{
var damageSpec = new DamageSpecifier();
if (bluntThisTick > 0)
damageSpec.DamageDict[BluntDamage] = bluntThisTick;
if (heatThisTick > 0)
damageSpec.DamageDict[HeatDamage] = heatThisTick;
_damage.TryChangeDamage(target, damageSpec, true);
if (_random.Prob(0.2f))
{
_popup.PopupEntity(Loc.GetString("tarot-devil-damaged"), target, target);
}
}
}
currentDamageTick++;
if (currentDamageTick < damageTicks)
{
Timer.Spawn(TimeSpan.FromSeconds(1), DamageTick);
}
}
DamageTick();
}
private void PerformTower(EntityUid target, bool reversed)
{
if (reversed)
{
var radius = 6;
var center = Transform(target).Coordinates;
for (int x = -radius; x <= radius; x++)
{
for (int y = -radius; y <= radius; y++)
{
if (x * x + y * y <= radius * radius)
{
if (_random.Prob(0.25f))
{
var spawnCoords = center.Offset(new Vector2(x, y));
Spawn(Rock, spawnCoords);
}
}
}
}
}
else
{
var grenade = Spawn(ClusterGrenade, Transform(target).Coordinates);
_trigger.Trigger(grenade);
}
}
private void PerformStars(EntityUid target, bool reversed)
{
if (reversed)
{
var damage = new DamageSpecifier { DamageDict = { { CellularDamage, 50 } } };
_damage.TryChangeDamage(target, damage, true);
var damageTypes = new[] { "ClosedFracture", "ArterialBleeding", "MildBurns" };
_surgery.TryAddInternalDamage(target, _random.Pick(damageTypes));
for (var i = 0; i < 2; i++)
{
var card = Spawn(EmptyCardTarot, Transform(target).Coordinates);
var tarot = EnsureComp<CardTarotComponent>(card);
var allCards = Enum.GetValues<CardTarot>();
tarot.Card = (CardTarot)_random.Next(1, allCards.Length);
bool reversedCard = _random.Prob(0.5f);
if (reversed) tarot.CardType = CardTarotType.Reversed;
_appearance.SetData(card, CardTarotVisuals.State, tarot.Card);
_appearance.SetData(card, CardTarotVisuals.Reversed, reversedCard);
_throwing.TryThrow(card, _random.NextVector2());
}
}
else
{
var lockers = new List<EntityUid>();
var lockersQuery = EntityQueryEnumerator<EntityStorageComponent, LockComponent, MetaDataComponent>();
while (lockersQuery.MoveNext(out var uid, out _, out _, out var meta))
{
if (meta.EntityPrototype != null && meta.EntityPrototype.ID == "LockerEvidence")
lockers.Add(uid);
}
if (lockers.Count == 0)
return;
var locker = _random.Pick(lockers);
var lockerCoords = Transform(locker).Coordinates;
var centerTile = Transform(locker).LocalPosition;
for (int radiusStep = 1; radiusStep <= 3; radiusStep++)
{
for (int i = 0; i < 8; i++)
{
var angle = (float)i / 8 * MathHelper.TwoPi;
var offset = new Vector2(
(float)Math.Cos(angle) * radiusStep,
(float)Math.Sin(angle) * radiusStep);
var testPos = centerTile + offset;
var testCoords = new EntityCoordinates(lockerCoords.EntityId, testPos);
var mapCoords = _transform.ToMapCoordinates(testCoords);
var intersecting = _entityLookup.GetEntitiesIntersecting(mapCoords, LookupFlags.Dynamic)
.Where(e => e != target).ToList();
if (intersecting.Count == 0)
{
_transform.SetCoordinates(target, testCoords);
_lock.Unlock(locker, null);
return;
}
}
}
}
}
private void PerformMoon(EntityUid target, bool reversed)
{
if (reversed)
{
// Well, like i couldn't think of anything smarter
var message = Loc.GetString("tarot-moon-m-message");
if (TryComp<HumanoidAppearanceComponent>(target, out var humanoid) && humanoid.Gender == Gender.Female)
message = Loc.GetString("tarot-moon-f-message");
_chat.TrySendInGameICMessage(target, message, InGameICChatType.Speak, false);
}
else
{
var grids = _mapManager.GetAllGrids(Transform(target).MapID)
.Where(g => !HasComp<BecomesStationComponent>(g) && !HasComp<ProtectedGridComponent>(g)).ToList();
if (grids.Count == 0)
return;
var randomGrid = _random.Pick(grids);
_transform.SetCoordinates(target, Transform(randomGrid).Coordinates);
}
}
private void PerformSun(EntityUid target, bool reversed)
{
if (reversed)
{
if (HasComp<PermanentBlindnessComponent>(target))
return;
EnsureComp<PermanentBlindnessComponent>(target).Blindness = 4;
Timer.Spawn(TimeSpan.FromMinutes(1), () => { RemComp<PermanentBlindnessComponent>(target); });
}
else
{
_rejuvenate.PerformRejuvenate(target);
}
}
private void PerformJudgement(EntityUid target, bool reversed)
{
if (reversed)
{
/// Temporarily empty
}
else
{
// ALL GHOSTS BE MINE!!!
var ghosts = new List<EntityUid>();
var ghostsQuery = EntityQueryEnumerator<GhostComponent>();
while (ghostsQuery.MoveNext(out var uid, out _))
ghosts.Add(uid);
foreach (var ghost in ghosts)
_transform.SetCoordinates(ghost, Transform(target).Coordinates);
}
}
private void PerformTheWorld(EntityUid target, bool reversed)
{
if (reversed)
{
// He should be doing something else, but that means "Temporarily empty." So it's a reference to JoJo
var host = EnsureComp<GuardianHostComponent>(target);
var guardian = Spawn(Stand, Transform(target).Coordinates);
_container.Insert(guardian, host.GuardianContainer);
host.HostedGuardian = guardian;
if (TryComp<GuardianComponent>(guardian, out var guardianComp))
guardianComp.Host = target;
}
else
{
Spawn(Smoke, Transform(target).Coordinates);
}
}
#endregion
#endregion
}

View File

@@ -0,0 +1,63 @@
using Content.Server.Chat.Systems;
using Content.Shared.Administration;
using Content.Shared.Mind;
using Robust.Shared.Console;
using Robust.Shared.Enums;
namespace Content.Server.Chat.Commands
{
[AnyCommand]
internal sealed class MindSayCommand : LocalizedEntityCommands
{
[Dependency] private readonly ChatSystem _chatSystem = default!;
public override string Command => "mindsay";
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 (player.Status != SessionStatus.InGame)
return;
if (player.AttachedEntity is not { } playerEntity)
{
shell.WriteError(Loc.GetString($"shell-must-be-attached-to-entity"));
return;
}
if (args.Length < 1)
return;
var message = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(message))
return;
// Process the mind message
if (_chatSystem.TryProcessMindMessage(playerEntity, message, out var modifiedMessage, out var channel))
{
if (channel != null)
{
// Check if entity has access to the channel
if (EntityManager.TryGetComponent<MindLinkComponent>(playerEntity, out var mindLink) &&
mindLink.Channels.Contains(channel.ID))
{
_chatSystem.SendMindMessage(playerEntity, modifiedMessage, channel);
}
else
{
shell.WriteError(Loc.GetString("chat-manager-no-access-mind-channel"));
}
}
else
{
shell.WriteError(Loc.GetString("chat-manager-no-mind-channel"));
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Dice;
[RegisterComponent]
public sealed partial class DiceOfFateComponent : Component
{
public bool Used;
}

View File

@@ -0,0 +1,333 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Antag;
using Content.Server.Body.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Polymorph.Systems;
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Administration.Systems;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Dice;
using Content.Shared.Disease;
using Content.Shared.Explosion;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.PDA;
using Content.Shared.Polymorph;
using Content.Shared.Popups;
using Content.Shared.Roles.Components;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Dice;
public sealed class DiceOfFateSystem : EntitySystem
{
[Dependency] private readonly SharedAccessSystem _access = default!;
[Dependency] private readonly IAdminLogManager _admin = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly DamageableSystem _damage = default!;
[Dependency] private readonly SharedDiseaseSystem _disease = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly MovementSpeedModifierSystem _speed = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly PolymorphSystem _polymorph = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DiceOfFateComponent, UseInHandEvent>(OnUseInHand, after: [typeof(SharedDiceSystem)]);
SubscribeLocalEvent<DiceOfFateComponent, LandEvent>(OnLand, after: [typeof(SharedDiceSystem)]);
}
private static readonly ProtoId<DamageModifierSetPrototype> DamageMod = "DiceOfFateMod";
private static readonly ProtoId<PolymorphPrototype> Monkey = "Monkey";
private static readonly ProtoId<DiseasePrototype> Cold = "SpaceCold";
private static readonly ProtoId<DamageTypePrototype> Damage = "Asphyxiation";
private static readonly EntProtoId RandomAgressive = "RandomAgressiveAnimal";
private static readonly EntProtoId RandomSpellbook = "RandomSpellbook";
private static readonly EntProtoId Revolver = "WeaponRevolverInspector";
private static readonly EntProtoId DefaultWizardRule = "Wizard";
private static readonly EntProtoId Cookie = "FoodBakedCookie";
private static readonly EntProtoId Servant = "PlushieLizard";
private static readonly EntProtoId Toolbox = "ToolboxThief";
private static readonly EntProtoId Cash = "SpaceCash10000";
private void OnUseInHand(Entity<DiceOfFateComponent> entity, ref UseInHandEvent args)
{
if (!TryComp<DiceComponent>(entity, out var dice) || entity.Comp.Used)
return;
entity.Comp.Used = true;
RollFate(args.User, dice.CurrentValue);
Timer.Spawn(TimeSpan.FromSeconds(1), () => { QueueDel(entity); }); // So that you can see the number
}
private void OnLand(Entity<DiceOfFateComponent> entity, ref LandEvent args)
{
if (args.User == null || !TryComp<DiceComponent>(entity, out var dice)
|| entity.Comp.Used)
return;
entity.Comp.Used = true;
RollFate(args.User.Value, dice.CurrentValue);
Timer.Spawn(TimeSpan.FromSeconds(1), () => { QueueDel(entity); }); // So that you can see the number
}
public void RollFate(EntityUid user, int value)
{
var success = value switch
{
1 => CompleteAnnihilation(user),
2 => InstantDeath(user),
3 => SummonAggressiveCreatures(user),
4 => DestroyAllEquippedItems(user),
5 => TransformIntoMonkey(user),
6 => PermanentMovementSpeedReduction(user),
7 => StunAndDamage(user),
8 => ExplosionUser(user),
9 => CommonCold(user),
10 => NothingHappens(user),
11 => SpawnCookie(user),
12 => FullHealthRestoration(user),
13 => SpawnMoney(user),
14 => SpawnRevolver(user),
15 => SpawnSpellbook(user),
16 => SummonServant(user),
17 => SuspiciousBeacon(user),
18 => FullAccess(user),
19 => PermanentDamageReduction(user),
20 => BecomeWizard(user),
_ => NothingHappens(user)
};
_admin.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(user):user} rools fade is '{success}', got nimber {value}.");
}
private bool CompleteAnnihilation(EntityUid user)
{
_body.GibBody(user, true, splatModifier: 10f);
return true;
}
private bool InstantDeath(EntityUid user)
{
var damage = new DamageSpecifier { DamageDict = { { Damage, 400 } } };
_damage.ChangeDamage(user, damage, true);
return true;
}
private bool SummonAggressiveCreatures(EntityUid user)
{
var count = _random.Next(3, 6);
for (var i = 0; i < count; i++)
{
Spawn(RandomAgressive, Transform(user).Coordinates);
}
return true;
}
private bool DestroyAllEquippedItems(EntityUid user)
{
if (_inventory.TryGetSlots(user, out var slots))
{
foreach (var slot in slots)
{
if (_inventory.TryGetSlotEntity(user, slot.Name, out var ent))
QueueDel(ent);
}
}
return true;
}
private bool TransformIntoMonkey(EntityUid user)
{
_polymorph.PolymorphEntity(user, Monkey);
return true;
}
private bool PermanentMovementSpeedReduction(EntityUid user)
{
if (TryComp(user, out MovementSpeedModifierComponent? speedmod))
{
var originalWalkSpeed = speedmod.BaseWalkSpeed;
var originalSprintSpeed = speedmod.BaseSprintSpeed;
var multiplier = _random.NextFloat(0.3f, 0.95f);
_speed.ChangeBaseSpeed(user, originalWalkSpeed * multiplier, originalSprintSpeed * multiplier, speedmod.Acceleration, speedmod);
}
return true;
}
private bool StunAndDamage(EntityUid user)
{
_stun.TryKnockdown(user, TimeSpan.FromSeconds(30));
var damage = new DamageSpecifier { DamageDict = { { Damage, 50 } } };
_damage.ChangeDamage(user, damage, true);
return true;
}
private bool ExplosionUser(EntityUid user)
{
if (!_prototype.TryIndex(ExplosionSystem.DefaultExplosionPrototypeId, out ExplosionPrototype? type))
return false;
_explosion.QueueExplosion(user, type.ID, 5000f, 3f, 10f);
return true;
}
private bool CommonCold(EntityUid user)
{
_disease.TryAddDisease(user, Cold);
return true;
}
private bool NothingHappens(EntityUid user)
{
// I'm Lazy
_popup.PopupEntity(Loc.GetString("reagent-desc-nothing"), user, user);
return true;
}
private bool SpawnCookie(EntityUid user)
{
var cookie = Spawn(Cookie, Transform(user).Coordinates);
_hands.TryForcePickupAnyHand(user, cookie);
return true;
}
private bool FullHealthRestoration(EntityUid user)
{
_rejuvenate.PerformRejuvenate(user);
return true;
}
private bool SpawnMoney(EntityUid user)
{
var cash = Spawn(Cash, Transform(user).Coordinates);
_hands.TryForcePickupAnyHand(user, cash);
return true;
}
private bool SpawnRevolver(EntityUid user)
{
var revolver = Spawn(Revolver, Transform(user).Coordinates);
_hands.TryForcePickupAnyHand(user, revolver);
return true;
}
private bool SpawnSpellbook(EntityUid user)
{
var spellbook = Spawn(RandomSpellbook, Transform(user).Coordinates);
_hands.TryForcePickupAnyHand(user, spellbook);
return true;
}
// Very goodluck
private bool SummonServant(EntityUid user)
{
var servant = Spawn(Servant, Transform(user).Coordinates);
_hands.TryForcePickupAnyHand(user, servant);
return true;
}
private bool SuspiciousBeacon(EntityUid user)
{
var toolbox = Spawn(Toolbox, Transform(user).Coordinates);
_hands.TryForcePickupAnyHand(user, toolbox);
return true;
}
private bool FullAccess(EntityUid user)
{
var ent = FindActiveId(user);
if (ent == null)
return false;
GiveAllAccess(ent.Value);
return true;
}
private EntityUid? FindActiveId(EntityUid target)
{
if (_inventory.TryGetSlotEntity(target, "id", out var slotEntity))
{
if (HasComp<AccessComponent>(slotEntity))
{
return slotEntity.Value;
}
else if (TryComp<PdaComponent>(slotEntity, out var pda)
&& HasComp<IdCardComponent>(pda.ContainedId))
{
return pda.ContainedId;
}
}
else if (TryComp<HandsComponent>(target, out var hands))
{
foreach (var held in _hands.EnumerateHeld((target, hands)))
{
if (HasComp<AccessComponent>(held))
{
return held;
}
}
}
return null;
}
private void GiveAllAccess(EntityUid entity)
{
var allAccess = _prototype
.EnumeratePrototypes<AccessLevelPrototype>()
.Select(p => new ProtoId<AccessLevelPrototype>(p.ID)).ToArray();
_access.TrySetTags(entity, allAccess);
}
private bool PermanentDamageReduction(EntityUid user)
{
_damage.SetDamageModifierSetId(user, DamageMod);
return true;
}
private bool BecomeWizard(EntityUid user)
{
if (!TryComp<ActorComponent>(user, out var actor))
return false;
_antag.ForceMakeAntag<WizardRoleComponent>(actor.PlayerSession, DefaultWizardRule);
return true;
}
}

View File

@@ -2,11 +2,14 @@ using System.Linq;
using Content.Server.Actions;
using Content.Server.Administration.Logs;
using Content.Server.Antag;
using Content.Server.Blood.Cult;
using Content.Server.Bed.Cryostorage;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Shared.Blood.Cult;
@@ -18,14 +21,17 @@ using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
using Content.Shared.NPC.Prototypes;
using Content.Shared.NPC.Systems;
using Content.Shared.Popups;
using Content.Shared.Zombies;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Random;
namespace Content.Server.GameTicking.Rules
{
@@ -34,7 +40,6 @@ namespace Content.Server.GameTicking.Rules
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly BloodCultSystem _cult = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
@@ -44,6 +49,12 @@ namespace Content.Server.GameTicking.Rules
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ObjectivesSystem _objectives = default!;
[Dependency] private readonly TargetObjectiveSystem _target = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
public readonly ProtoId<NpcFactionPrototype> BloodCultNpcFaction = "BloodCult";
@@ -54,44 +65,164 @@ namespace Content.Server.GameTicking.Rules
SubscribeLocalEvent<BloodCultRuleComponent, ComponentStartup>(OnRuleStartup);
SubscribeLocalEvent<BloodCultRuleComponent, AfterAntagEntitySelectedEvent>(OnCultistSelected);
SubscribeLocalEvent<BloodCultistComponent, ComponentStartup>((_, _, _) => CheckStage());
SubscribeLocalEvent<BloodCultConstructComponent, ComponentStartup>((_, _, _) => CheckStage());
SubscribeLocalEvent<BloodCultObjectComponent, ComponentShutdown>(OnBloodCultObjectShutdown);
SubscribeLocalEvent<BloodCultObjectComponent, CryostorageEnterEvent>(OnCryostorageEnter);
SubscribeLocalEvent<GodCalledEvent>(OnGodCalled);
SubscribeLocalEvent<RitualConductedEvent>(OnRitualConducted);
SubscribeLocalEvent<AutoCultistComponent, ComponentStartup>(OnAutoCultistAdded);
SubscribeLocalEvent<BloodCultistComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<BloodCultistComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<BloodCultistComponent, EntityZombifiedEvent>(OnOperativeZombified);
SubscribeLocalEvent<BloodCultistComponent, EntityZombifiedEvent>(OnCultistZombified);
}
private void OnRuleStartup(EntityUid uid, BloodCultRuleComponent component, ComponentStartup args)
{
List<string> gods = new List<string> { "Narsie", "Reaper", "Kharin" };
component.SelectedGod = gods[new Random().Next(gods.Count)];
Timer.Spawn(TimeSpan.FromMinutes(1), _cult.SelectRandomTargets);
component.SelectedGod = (BloodCultGod)_random.Next(0, 3);
}
private void OnCryostorageEnter(EntityUid uid, BloodCultObjectComponent component, CryostorageEnterEvent args)
{
var cult = GetActiveRule();
if (cult == null)
return;
if (!cult.SelectedTargets.Contains(uid))
return;
var newTarget = FindNewRandomTarget(cult, uid);
if (newTarget == null)
return;
ReplaceTargetForAllCultists(uid, newTarget.Value);
cult.SelectedTargets.Remove(uid);
cult.SelectedTargets.Add(newTarget.Value);
RemComp<BloodCultObjectComponent>(uid);
EnsureComp<BloodCultObjectComponent>(newTarget.Value);
}
#region Cultist Processing
private void OnCultistSelected(Entity<BloodCultRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
{
var ent = args.EntityUid;
if (mindId.Comp.SelectedTargets.Count == 0)
SelectRandomTargets(mindId.Comp);
MakeCultist(ent);
_antag.SendBriefing(ent, MakeBriefing(ent), Color.Red, null);
}
private void SelectRandomTargets(BloodCultRuleComponent cult)
{
cult.SelectedTargets.Clear();
var mindShieldCandidates = new List<EntityUid>();
var enumerator = EntityQueryEnumerator<MindShieldComponent>();
while (enumerator.MoveNext(out var uid, out _))
mindShieldCandidates.Add(uid);
if (mindShieldCandidates.Count >= 2)
{
var selectedIndices = new HashSet<int>();
while (selectedIndices.Count < 2)
{
var index = _random.Next(0, mindShieldCandidates.Count);
selectedIndices.Add(index);
}
foreach (var index in selectedIndices)
{
var target = mindShieldCandidates[index];
cult.SelectedTargets.Add(target);
EnsureComp<BloodCultObjectComponent>(target);
}
return;
}
foreach (var target in mindShieldCandidates)
{
cult.SelectedTargets.Add(target);
EnsureComp<BloodCultObjectComponent>(target);
}
var globalCandidates = new List<EntityUid>();
var globalEnumerator = EntityQueryEnumerator<HumanoidAppearanceComponent, ActorComponent>();
while (globalEnumerator.MoveNext(out var uid, out _, out _))
{
if (cult.SelectedTargets.Contains(uid) || HasComp<BloodCultistComponent>(uid))
continue;
globalCandidates.Add(uid);
}
while (cult.SelectedTargets.Count < 2 && globalCandidates.Count > 0)
{
var index = _random.Next(0, globalCandidates.Count);
var target = globalCandidates[index];
cult.SelectedTargets.Add(target);
EnsureComp<BloodCultObjectComponent>(target);
globalCandidates.RemoveAt(index);
}
}
private EntityUid? FindNewRandomTarget(BloodCultRuleComponent cult, EntityUid excludedTarget)
{
var candidates = new List<EntityUid>();
var query = EntityQueryEnumerator<HumanoidAppearanceComponent, ActorComponent>();
while (query.MoveNext(out var uid, out _, out _))
{
if (uid == excludedTarget || cult.SelectedTargets.Contains(uid)
|| HasComp<BloodCultistComponent>(uid)
|| HasComp<BloodCultObjectComponent>(uid))
continue;
candidates.Add(uid);
}
if (candidates.Count == 0)
return null;
var index = _random.Next(0, candidates.Count);
return candidates[index];
}
private void ReplaceTargetForAllCultists(EntityUid oldTarget, EntityUid newTarget)
{
var replacedObjectives = new List<EntityUid>();
var query = EntityQueryEnumerator<TargetObjectiveComponent, BloodCultTargetObjectiveComponent>();
while (query.MoveNext(out var objectiveUid, out var targetComp, out _))
{
if (targetComp.Target == oldTarget)
{
replacedObjectives.Add(objectiveUid);
}
}
foreach (var objectiveUid in replacedObjectives)
{
_target.SetTarget(objectiveUid, newTarget);
_meta.SetEntityName(objectiveUid, Loc.GetString("objective-condition-blood-ritual-person-title",
("targetName", Name(newTarget))));
}
}
private void MakeCultist(EntityUid ent)
{
var actionPrototypes = new[]
{
BloodCultistComponent.CultObjective,
BloodCultistComponent.CultCommunication,
BloodCultistComponent.BloodMagic,
BloodCultistComponent.RecallBloodDagger
};
foreach (var actionPrototype in actionPrototypes)
{
_action.AddAction(ent, actionPrototype);
}
var componentsToRemove = new[]
{
@@ -100,25 +231,65 @@ namespace Content.Server.GameTicking.Rules
};
foreach (var compType in componentsToRemove)
{
if (HasComp(ent, compType))
RemComp(ent, compType);
}
RemComp(ent, compType);
HandleMetabolism(ent);
CreateObjectivesForCultist(ent);
}
private void CreateObjectivesForCultist(EntityUid cultist)
{
var cult = GetActiveRule();
if (cult == null || cult.SelectedTargets.Count == 0)
return;
if (!_mind.TryGetMind(cultist, out var mindId, out var mind))
return;
foreach (var target in cult.SelectedTargets)
{
if (!Exists(target))
continue;
var objective = _objectives.TryCreateObjective(mindId, mind, cult.ObjectivePrototype);
if (objective == null)
continue;
_target.SetTarget(objective.Value, target);
_meta.SetEntityName(objective.Value, Loc.GetString("objective-condition-blood-ritual-person-title",
("targetName", Name(target)))); // <see cref="ObjectiveAssignedEvent"/> here doesn't worked, or i'm stupid
_mind.AddObjective(mindId, mind, objective.Value);
}
}
private void HandleMetabolism(EntityUid cultist)
{
if (TryComp<BodyComponent>(cultist, out var bodyComponent))
{
foreach (var organ in _body.GetBodyOrgans(cultist, bodyComponent))
{
if (TryComp<MetabolizerComponent>(organ.Id, out var metabolizer))
{
if (TryComp<StomachComponent>(organ.Id, out _))
_metabolism.ClearMetabolizerTypes(metabolizer);
_metabolism.TryAddMetabolizerType(metabolizer, "BloodCultist");
}
}
}
}
private string MakeBriefing(EntityUid ent)
{
string selectedGod = Loc.GetString("current-god-narsie");
string selectedGod = "";
var query = QueryActiveRules();
while (query.MoveNext(out _, out _, out var cult, out _))
{
selectedGod = cult.SelectedGod switch
{
"Narsie" => Loc.GetString("current-god-narsie"),
"Reaper" => Loc.GetString("current-god-reaper"),
"Kharin" => Loc.GetString("current-god-kharin"),
BloodCultGod.NarSi => Loc.GetString("current-god-narsie"),
BloodCultGod.Reaper => Loc.GetString("current-god-reaper"),
BloodCultGod.Kharin => Loc.GetString("current-god-kharin"),
_ => Loc.GetString("current-god-narsie")
};
break;
@@ -153,11 +324,11 @@ namespace Content.Server.GameTicking.Rules
var query = QueryActiveRules();
while (query.MoveNext(out _, out _, out var cult, out _))
{
string selectedDagger = cult.SelectedGod switch
EntProtoId selectedDagger = cult.SelectedGod switch
{
"Narsie" => "WeaponBloodDagger",
"Reaper" => "WeaponDeathDagger",
"Kharin" => "WeaponHellDagger",
BloodCultGod.NarSi => "WeaponBloodDagger",
BloodCultGod.Reaper => "WeaponDeathDagger",
BloodCultGod.Kharin => "WeaponHellDagger",
_ => "WeaponBloodDagger"
};
@@ -168,23 +339,134 @@ namespace Content.Server.GameTicking.Rules
}
}
private void HandleMetabolism(EntityUid cultist)
{
if (TryComp<BodyComponent>(cultist, out var bodyComponent))
{
foreach (var organ in _body.GetBodyOrgans(cultist, bodyComponent))
{
if (TryComp<MetabolizerComponent>(organ.Id, out var metabolizer))
{
if (TryComp<StomachComponent>(organ.Id, out _))
_metabolism.ClearMetabolizerTypes(metabolizer);
#endregion
_metabolism.TryAddMetabolizerType(metabolizer, "Cultist");
#region Stages
private void OnBloodCultObjectShutdown(EntityUid uid, BloodCultObjectComponent component, ComponentShutdown args)
{
var cult = GetActiveRule();
if (cult == null)
return;
if (!cult.SelectedTargets.Contains(uid))
{
CheckStage();
if (cult.SelectedTargets.Count == 0)
RaiseLocalEvent(new RitualConductedEvent());
return;
}
var newTarget = FindNewRandomTarget(cult, uid);
if (newTarget == null)
return;
ReplaceTargetForAllCultists(uid, newTarget.Value);
cult.SelectedTargets.Remove(uid);
cult.SelectedTargets.Add(newTarget.Value);
EnsureComp<BloodCultObjectComponent>(newTarget.Value);
}
private void CheckStage()
{
var cult = GetActiveRule();
if (cult == null)
return;
var totalCultEntities = GetCultEntities();
var playerCount = GetPlayerCount();
// Second
if (playerCount >= 100 && totalCultEntities >= playerCount * 0.1f || playerCount < 100 && totalCultEntities >= playerCount * 0.2f || cult.RitualStage)
{
foreach (var cultist in GetAllCultists())
{
if (!HasComp<BloodCultistEyesComponent>(cultist))
{
UpdateCultistEyes(cultist);
AddComp<BloodCultistEyesComponent>(cultist);
}
}
if (!cult.FirstTriggered)
{
var actorFilter = Filter.Empty();
var actorQuery = EntityQueryEnumerator<ActorComponent, BloodCultistComponent>();
while (actorQuery.MoveNext(out var actorUid, out var actor, out _))
{
actorFilter.AddPlayer(actor.PlayerSession);
_popup.PopupEntity(Loc.GetString("blood-cult-first-warning"), actorUid, actorUid, PopupType.SmallCaution);
}
_audio.PlayGlobal(new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/bloodcult_eyes.ogg"), actorFilter, true);
cult.FirstTriggered = true;
}
}
// Third
if (playerCount >= 100 && totalCultEntities >= playerCount * 0.2f || playerCount < 100 && totalCultEntities >= playerCount * 0.3f || cult.RitualStage)
{
foreach (var cultist in GetAllCultists())
{
EnsureComp<BloodPentagramDisplayComponent>(cultist);
}
if (!cult.SecondTriggered)
{
var actorFilter = Filter.Empty();
var actorQuery = EntityQueryEnumerator<ActorComponent, BloodCultistComponent>();
while (actorQuery.MoveNext(out var actorUid, out var actor, out _))
{
actorFilter.AddPlayer(actor.PlayerSession);
_popup.PopupEntity(Loc.GetString("blood-cult-second-warning"), actorUid, actorUid, PopupType.SmallCaution);
}
_audio.PlayGlobal(new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/bloodcult_halos.ogg"), actorFilter, true);
cult.SecondTriggered = true;
}
}
}
private void UpdateCultistEyes(EntityUid cultist)
{
if (TryComp<HumanoidAppearanceComponent>(cultist, out var appearanceComponent))
{
appearanceComponent.EyeColor = Color.FromHex("#E22218FF");
Dirty(cultist, appearanceComponent);
}
}
private int GetCultEntities()
{
var totalCultists = GetAllCultists().Count;
var totalConstructs = EntityQuery<BloodCultConstructComponent>().Count();
return totalCultists + totalConstructs;
}
private int GetPlayerCount()
{
int count = 0;
var players = AllEntityQuery<HumanoidAppearanceComponent, ActorComponent, TransformComponent>();
while (players.MoveNext(out _, out _, out _, out _))
count++;
return count;
}
private List<EntityUid> GetAllCultists()
{
var cultists = new List<EntityUid>();
var enumerator = EntityQueryEnumerator<BloodCultistComponent>();
while (enumerator.MoveNext(out var uid, out _))
cultists.Add(uid);
return cultists;
}
#endregion
protected override void AppendRoundEndText(EntityUid uid,
BloodCultRuleComponent component,
GameRuleComponent gameRule,
@@ -208,69 +490,71 @@ namespace Content.Server.GameTicking.Rules
}
}
private void OnGodCalled(GodCalledEvent ev)
public BloodCultRuleComponent? GetActiveRule()
{
var query = QueryActiveRules();
while (query.MoveNext(out _, out _, out var cult, out _))
{
if (cult.BloodCultWinCondition.Contains(BloodCultWinType.RitualConducted))
cult.BloodCultWinCondition.Remove(BloodCultWinType.RitualConducted);
return cult;
}
return null;
}
cult.WinType = BloodCultWinType.GodCalled;
private void OnGodCalled(GodCalledEvent ev)
{
var cult = GetActiveRule();
if (cult == null)
return;
if (!cult.BloodCultWinCondition.Contains(BloodCultWinType.GodCalled))
{
cult.BloodCultWinCondition.Add(BloodCultWinType.GodCalled);
_roundEndSystem.DoRoundEndBehavior(RoundEndBehavior.ShuttleCall, TimeSpan.FromMinutes(1f));
}
if (cult.BloodCultWinCondition.Contains(BloodCultWinType.RitualConducted))
cult.BloodCultWinCondition.Remove(BloodCultWinType.RitualConducted);
cult.WinType = BloodCultWinType.GodCalled;
if (!cult.BloodCultWinCondition.Contains(BloodCultWinType.GodCalled))
{
cult.BloodCultWinCondition.Add(BloodCultWinType.GodCalled);
_roundEndSystem.DoRoundEndBehavior(RoundEndBehavior.ShuttleCall, TimeSpan.FromMinutes(1f));
}
}
private void OnRitualConducted(RitualConductedEvent ev)
{
var query = QueryActiveRules();
while (query.MoveNext(out _, out _, out var cult, out _))
{
cult.WinType = BloodCultWinType.RitualConducted;
var cult = GetActiveRule();
if (cult == null)
return;
if (!cult.BloodCultWinCondition.Contains(BloodCultWinType.RitualConducted))
cult.BloodCultWinCondition.Add(BloodCultWinType.RitualConducted);
}
cult.RitualStage = true;
cult.WinType = BloodCultWinType.RitualConducted;
CheckStage();
if (!cult.BloodCultWinCondition.Contains(BloodCultWinType.RitualConducted))
cult.BloodCultWinCondition.Add(BloodCultWinType.RitualConducted);
}
private void OnMobStateChanged(EntityUid uid, BloodCultistComponent component, MobStateChangedEvent ev)
{
if (ev.NewMobState == MobState.Dead)
{
var query = QueryActiveRules();
while (query.MoveNext(out var ruleUid, out _, out var cult, out _))
{
CheckCultLose(ruleUid, cult);
}
}
CheckCultLose(GetActiveRule());
}
private void OnComponentRemove(EntityUid uid, BloodCultistComponent component, ComponentRemove args)
{
var query = QueryActiveRules();
while (query.MoveNext(out var ruleUid, out _, out var cult, out _))
{
CheckCultLose(ruleUid, cult);
}
CheckCultLose(GetActiveRule());
}
private void OnOperativeZombified(EntityUid uid, BloodCultistComponent component, EntityZombifiedEvent args)
private void OnCultistZombified(EntityUid uid, BloodCultistComponent component, EntityZombifiedEvent args)
{
var query = QueryActiveRules();
while (query.MoveNext(out var ruleUid, out _, out var cult, out _))
{
CheckCultLose(ruleUid, cult);
}
CheckCultLose(GetActiveRule());
}
private void CheckCultLose(EntityUid uid, BloodCultRuleComponent cult)
private void CheckCultLose(BloodCultRuleComponent? cult)
{
var hasLivingCultists = EntityManager.EntityQuery<BloodCultistComponent>().Any();
if (cult == null)
return;
var hasLivingCultists = EntityQuery<BloodCultistComponent>().Any();
if (!hasLivingCultists && !cult.BloodCultWinCondition.Contains(BloodCultWinType.GodCalled)
&& !cult.BloodCultWinCondition.Contains(BloodCultWinType.RitualConducted))
{

View File

@@ -1,19 +1,38 @@
using Content.Server.Blood.Cult;
using Content.Shared.Blood.Cult;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Rules.Components;
/// <summary>
/// Stores data for <see cref="BloodCultRuleSystem"/>.
/// Stores data for <see cref="BloodCultRuleSystem"/> and <see cref="BloodCultSystem"/>.
/// </summary>
[RegisterComponent, Access(typeof(BloodCultRuleSystem))]
[RegisterComponent, Access(typeof(BloodCultRuleSystem), typeof(BloodCultSystem))]
public sealed partial class BloodCultRuleComponent : Component
{
[DataField]
public string? SelectedGod;
public BloodCultGod? SelectedGod;
[DataField]
public BloodCultWinType WinType = BloodCultWinType.Neutral;
[DataField]
public List<BloodCultWinType> BloodCultWinCondition = new();
[DataField]
public HashSet<EntityUid> SelectedTargets = new();
public EntProtoId ObjectivePrototype = "BloodCultTargetObjective";
[DataField]
public int Curses = 2;
[DataField]
public int Offerings = 3;
[DataField] public bool FirstTriggered;
[DataField] public bool SecondTriggered;
[DataField] public bool RitualStage;
}
public enum BloodCultWinType : byte

View File

@@ -0,0 +1,9 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// A goal that requires completion of the ritual.
/// </summary>
[RegisterComponent, Access(typeof(BloodCultRitualObjectiveSystem))]
public sealed partial class BloodCultRitualObjectiveComponent : Component;

View File

@@ -0,0 +1,9 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// A common elimination task.
/// </summary>
[RegisterComponent, Access(typeof(BloodCultTargetObjectiveSystem))]
public sealed partial class BloodCultTargetObjectiveComponent : Component;

View File

@@ -0,0 +1,39 @@
using System.Linq;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Objectives.Components;
using Content.Shared.Objectives.Components;
namespace Content.Server.Objectives.Systems;
public sealed class BloodCultRitualObjectiveSystem : EntitySystem
{
[Dependency] private readonly BloodCultRuleSystem _bloodCult = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodCultRitualObjectiveComponent, ObjectiveGetProgressEvent>(OnGetProgress);
}
private void OnGetProgress(EntityUid uid, BloodCultRitualObjectiveComponent comp, ref ObjectiveGetProgressEvent args)
{
var cult = _bloodCult.GetActiveRule();
if (cult == null || !cult.RitualStage)
{
args.Progress = 0f;
return;
}
var condition = cult.BloodCultWinCondition.ToList();
if (condition.Contains(BloodCultWinType.GodCalled))
{
args.Progress = 1f;
}
else
{
args.Progress = 0.5f;
}
}
}

View File

@@ -0,0 +1,30 @@
using Content.Server.Objectives.Components;
using Content.Shared.Objectives.Components;
namespace Content.Server.Objectives.Systems;
public sealed class BloodCultTargetObjectiveSystem : EntitySystem
{
[Dependency] private readonly TargetObjectiveSystem _target = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodCultTargetObjectiveComponent, ObjectiveGetProgressEvent>(OnGetProgress);
}
private void OnGetProgress(EntityUid uid, BloodCultTargetObjectiveComponent comp, ref ObjectiveGetProgressEvent args)
{
if (!_target.GetTarget(uid, out var target))
return;
if (!Exists(target))
{
args.Progress = 1f;
return;
}
args.Progress = 0f;
}
}

View File

@@ -0,0 +1,469 @@
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Server.Destructible;
using Content.Server.Hands.Systems;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Shared.Damage.Systems;
using Content.Shared.Economy.SlotMachine;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Stacks;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Economy.SlotMachine;
public sealed class SlotMachineSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly DamageableSystem _damage = default!;
[Dependency] private readonly DestructibleSystem _destructible = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private static readonly string[] AllSymbols = { "♥", "★", "♠", "♦", "♣", "♡" };
private static readonly string[] CursedSymbols = { "☠", "🩸", "☢", "☣" };
private static readonly string[] CursedWinSymbols = { "💰", "♔", "🎮" };
private static readonly ProtoId<StackPrototype> Credit = "Credit";
private static readonly EntProtoId SpaceCash = "SpaceCash";
private static readonly EntProtoId Reward = "DiceOfFate";
private const float JackpotChance = 0.0002f;
private const float BigWinChance = 0.004f;
private const float MediumWinChance = 0.016f;
private const float SmallWinChance = 0.08f;
private const float TinyWinChance = 0.1f;
private const float CursedWinChance = 0.05f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SlotMachineComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SlotMachineComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<SlotMachineComponent, InteractUsingEvent>(OnInteractUsing);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<SlotMachineComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.Working && comp.SpinFinishTime.HasValue)
{
if (_timing.CurTime >= comp.SpinFinishTime.Value)
FinishSpin(uid, comp);
else
UpdateSlotsAnimation(uid, comp);
}
}
}
private void OnMapInit(EntityUid uid, SlotMachineComponent comp, MapInitEvent args)
=> UpdateAppearance(uid);
private void OnExamined(Entity<SlotMachineComponent> entity, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
string slots = string.Empty;
foreach (var slot in entity.Comp.Slots)
slots += $"{slot} ";
args.PushMarkup(Loc.GetString("slot-machine-examine", ("slots", slots.Trim()), ("spins", entity.Comp.Plays)));
if (TryComp<CursedSlotMachineComponent>(entity, out var cursedComp))
{
args.PushMarkup(Loc.GetString("cursed-slot-machine-uses",
("uses", cursedComp.Uses), ("max", cursedComp.MaxUses)));
}
}
private void OnInteractUsing(Entity<SlotMachineComponent> entity, ref InteractUsingEvent args)
{
if (args.Handled)
return;
args.Handled = TrySpin(entity, args.User, args.Used);
}
public bool TrySpin(Entity<SlotMachineComponent> entity, EntityUid user, EntityUid used)
{
if (!TryComp<StackComponent>(used, out var stack))
return false;
if (entity.Comp.Working)
{
_popup.PopupEntity(Loc.GetString("slot-machine-busy"), user, user);
return false;
}
bool isCursed = HasComp<CursedSlotMachineComponent>(entity);
if (!this.IsPowered(entity.Owner, EntityManager) && !isCursed)
{
_popup.PopupEntity(Loc.GetString("slot-machine-unpowered"), user, user);
return false;
}
if (stack.StackTypeId != Credit)
return false;
if (isCursed)
{
var cursedComp = Comp<CursedSlotMachineComponent>(entity);
if (cursedComp.Uses >= cursedComp.MaxUses)
{
_popup.PopupEntity(Loc.GetString("cursed-slot-machine-deny"), user, user, PopupType.SmallCaution);
return false;
}
}
if (stack.Count < entity.Comp.SpinCost)
{
_popup.PopupEntity(Loc.GetString("slot-machine-no-money"), user, user);
return false;
}
StartSpin(entity, user, isCursed);
_stack.ReduceCount(used, entity.Comp.SpinCost);
return true;
}
private void StartSpin(Entity<SlotMachineComponent> entity, EntityUid user, bool isCursed)
{
entity.Comp.User = user;
var spinTime = isCursed ? 5 : 2.5;
entity.Comp.SpinFinishTime = _timing.CurTime + TimeSpan.FromSeconds(spinTime);
entity.Comp.Working = true;
entity.Comp.Plays++;
entity.Comp.Slots = new[] { "?", "?", "?" };
UpdateAppearance(entity.Owner);
if (isCursed && TryComp<CursedSlotMachineComponent>(entity, out var cursedComp))
{
_audio.PlayPvs(entity.Comp.CoinSound, entity);
_audio.PlayPvs(cursedComp.RollSound, entity);
}
else
{
_audio.PlayPvs(entity.Comp.CoinSound, entity);
_audio.PlayPvs(entity.Comp.RollSound, entity);
}
_popup.PopupEntity(Loc.GetString("slot-machine-spinning"), user, user);
if (isCursed)
{
_popup.PopupEntity(Loc.GetString("cursed-slot-machine-spin", ("name", Identity.Name(user, EntityManager))),
entity.Owner, PopupType.Medium);
}
}
private void UpdateSlotsAnimation(EntityUid uid, SlotMachineComponent comp)
{
var symbols = HasComp<CursedSlotMachineComponent>(uid) ? CursedSymbols : AllSymbols;
for (int i = 0; i < comp.Slots.Length; i++)
{
if (_random.Prob(0.3f))
{
comp.Slots[i] = _random.Pick(symbols);
}
}
}
private void FinishSpin(EntityUid machineUid, SlotMachineComponent comp)
{
comp.Working = false;
comp.SpinFinishTime = null;
if (TryComp<CursedSlotMachineComponent>(machineUid, out var cursed))
{
DetermineCursedResult(machineUid, comp, cursed);
}
else
{
DetermineNormalResult(machineUid, comp);
}
UpdateAppearance(machineUid);
_audio.PlayPvs(comp.EndSound, machineUid);
}
private void DetermineNormalResult(EntityUid machineUid, SlotMachineComponent comp)
{
var user = comp.User;
if (user == null)
return;
var rand = _random.NextFloat();
if (rand < JackpotChance)
{
GenerateJackpotSlots(comp);
AwardJackpot(machineUid, comp, user.Value);
}
else if (rand < JackpotChance + BigWinChance)
{
GenerateBigWinSlots(comp);
AwardBigWin(machineUid, comp, user.Value);
}
else if (rand < JackpotChance + BigWinChance + MediumWinChance)
{
GenerateMediumWinSlots(comp);
AwardMediumWin(machineUid, comp, user.Value);
}
else if (rand < JackpotChance + BigWinChance + MediumWinChance + SmallWinChance)
{
GenerateSmallWinSlots(comp);
AwardSmallWin(machineUid, comp, user.Value);
}
else if (rand < JackpotChance + BigWinChance + MediumWinChance + SmallWinChance + TinyWinChance)
{
GenerateTinyWinSlots(comp);
AwardTinyWin(machineUid, comp, user.Value);
}
else
{
GenerateLoseSlots(comp);
_popup.PopupEntity(Loc.GetString("slot-machine-lose"), user.Value, user.Value);
_audio.PlayPvs(comp.FailedSound, machineUid);
}
comp.User = null;
}
private void DetermineCursedResult(EntityUid machineUid, SlotMachineComponent comp, CursedSlotMachineComponent cursed)
{
var user = comp.User;
if (user == null)
return;
var rand = _random.NextFloat();
if (rand < CursedWinChance)
{
GenerateCursedWinSlots(comp);
AwardCursedJackpot(machineUid, user.Value, cursed);
}
else
{
GenerateCursedLoseSlots(comp);
AwardCursedLoss(machineUid, comp, user.Value, cursed);
}
comp.User = null;
}
#region Slots Vis Generation
private void GenerateJackpotSlots(SlotMachineComponent comp)
{
comp.Slots = new[] { "★", "★", "★" };
}
private void GenerateBigWinSlots(SlotMachineComponent comp)
{
var symbol = _random.Pick(AllSymbols.Where(s => s != "★").ToArray());
comp.Slots = new[] { symbol, symbol, symbol };
}
private void GenerateMediumWinSlots(SlotMachineComponent comp)
{
var symbols = new[] { "♥", "♦", "♡" };
var symbol = _random.Pick(symbols);
comp.Slots = new[] { symbol, symbol, symbol };
}
private void GenerateSmallWinSlots(SlotMachineComponent comp)
{
var symbol = _random.Pick(AllSymbols);
var otherSymbols = AllSymbols.Where(s => s != symbol).ToArray();
var pattern = _random.Next(3);
switch (pattern)
{
case 0:
comp.Slots = new[] { symbol, symbol, _random.Pick(otherSymbols) };
break;
case 1:
comp.Slots = new[] { _random.Pick(otherSymbols), symbol, symbol };
break;
default:
comp.Slots = new[] { symbol, _random.Pick(otherSymbols), symbol };
break;
}
}
private void GenerateTinyWinSlots(SlotMachineComponent comp)
{
var symbols = new[] { "♠", "♣" };
var symbol = _random.Pick(symbols);
var otherSymbols = AllSymbols.Where(s => s != symbol).ToArray();
var pattern = _random.Next(3);
switch (pattern)
{
case 0:
comp.Slots = new[] { symbol, symbol, _random.Pick(otherSymbols) };
break;
case 1:
comp.Slots = new[] { _random.Pick(otherSymbols), symbol, symbol };
break;
default:
comp.Slots = new[] { symbol, _random.Pick(otherSymbols), symbol };
break;
}
}
private void GenerateLoseSlots(SlotMachineComponent comp)
{
while (true)
{
comp.Slots = new[]
{
_random.Pick(AllSymbols),
_random.Pick(AllSymbols),
_random.Pick(AllSymbols)
};
if (IsLosingCombination(comp.Slots))
break;
}
}
private void GenerateCursedWinSlots(SlotMachineComponent comp)
{
var symbol = _random.Pick(CursedWinSymbols);
comp.Slots = new[] { symbol, symbol, symbol };
}
private void GenerateCursedLoseSlots(SlotMachineComponent comp)
{
comp.Slots = new[]
{
_random.Pick(CursedSymbols),
_random.Pick(CursedSymbols),
_random.Pick(CursedSymbols)
};
}
private bool IsLosingCombination(string[] slots)
{
if (slots[0] == slots[1] && slots[1] == slots[2])
return false;
if (slots[0] == slots[1] || slots[1] == slots[2] || slots[0] == slots[2])
return false;
var luckySymbols = new[] { "♥", "♦", "♡" };
if (luckySymbols.Contains(slots[0]) && luckySymbols.Contains(slots[1]) && luckySymbols.Contains(slots[2]))
return false;
return true;
}
#endregion
#region Awards
private void AwardJackpot(EntityUid machineUid, SlotMachineComponent comp, EntityUid user)
{
SpawnAward(machineUid, user, comp.JackpotPrize);
_audio.PlayPvs(comp.JackpotSound, machineUid);
_popup.PopupEntity(Loc.GetString("slot-machine-jackpot", ("prize", comp.JackpotPrize)), user, user);
var name = Identity.Name(user, EntityManager);
_chat.DispatchGlobalAnnouncement(Loc.GetString("auto-announcements-jackpot", ("winner", name)),
Loc.GetString("auto-announcements-title"), true, colorOverride: Color.Turquoise);
}
private void AwardBigWin(EntityUid machineUid, SlotMachineComponent comp, EntityUid user)
{
SpawnAward(machineUid, user, comp.BigWinPrize);
_popup.PopupEntity(Loc.GetString("slot-machine-bigwin", ("prize", comp.BigWinPrize)), user, user);
}
private void AwardMediumWin(EntityUid machineUid, SlotMachineComponent comp, EntityUid user)
{
SpawnAward(machineUid, user, comp.MediumWinPrize);
_popup.PopupEntity(Loc.GetString("slot-medium-win", ("prize", comp.MediumWinPrize)), user, user);
}
private void AwardSmallWin(EntityUid machineUid, SlotMachineComponent comp, EntityUid user)
{
SpawnAward(machineUid, user, comp.SmallWinPrize);
_popup.PopupEntity(Loc.GetString("slot-small-win", ("prize", comp.SmallWinPrize)), user, user);
}
private void AwardTinyWin(EntityUid machineUid, SlotMachineComponent comp, EntityUid user)
{
SpawnAward(machineUid, user, comp.TinyWinPrize);
_popup.PopupEntity(Loc.GetString("slot-tiny-win", ("prize", comp.TinyWinPrize)), user, user);
}
private void AwardCursedJackpot(EntityUid machineUid, EntityUid user, CursedSlotMachineComponent cursedComp)
{
var die = Spawn(Reward, Transform(machineUid).Coordinates);
_hands.TryPickupAnyHand(user, die);
_audio.PlayPvs(cursedComp.JackpotSound, machineUid);
_popup.PopupEntity(Loc.GetString("cursed-slot-machine-jackpot", ("name", Name(user))), // He know who are you
machineUid, PopupType.LargeCaution);
cursedComp.Uses = 5; // Win. Stop
Timer.Spawn(TimeSpan.FromSeconds(5), () => { _destructible.DestroyEntity(machineUid); });
}
private void AwardCursedLoss(EntityUid machineUid, SlotMachineComponent comp, EntityUid user, CursedSlotMachineComponent cursedComp)
{
cursedComp.Uses++;
_damage.TryChangeDamage(user, cursedComp.Damage, true);
_audio.PlayPvs(comp.FailedSound, machineUid);
_popup.PopupEntity(Loc.GetString("cursed-slot-machine-lose"), user, user, PopupType.SmallCaution);
}
private void SpawnAward(EntityUid machineUid, EntityUid user, int award)
{
var cash = Spawn(SpaceCash, Transform(machineUid).Coordinates);
_stack.SetCount((cash, null), award);
_hands.TryPickupAnyHand(user, cash);
}
#endregion
public void FreeSpeen(Entity<SlotMachineComponent?> entity, EntityUid user)
{
if (!Resolve(entity.Owner, ref entity.Comp))
return;
StartSpin((entity.Owner, entity.Comp), user, HasComp<CursedSlotMachineComponent>(entity));
}
private void UpdateAppearance(Entity<SlotMachineComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp))
return;
_appearance.SetData(entity, SlotMachineVisuals.Working, entity.Comp.Working);
}
}

View File

@@ -65,25 +65,32 @@ namespace Content.Shared.Chat
/// </summary>
Dead = 1 << 10,
// Corvax-Wega-MindChat-start
/// <summary>
/// Mind chat
/// </summary>
Mind = 1 << 11,
// Corvax-Wega-MindChat-end
/// <summary>
/// Misc admin messages
/// </summary>
Admin = 1 << 11,
Admin = 1 << 12, // Corvax-Wega-Edit
/// <summary>
/// Admin alerts, messages likely of elevated importance to admins
/// </summary>
AdminAlert = 1 << 12,
AdminAlert = 1 << 13, // Corvax-Wega-Edit
/// <summary>
/// Admin chat
/// </summary>
AdminChat = 1 << 13,
AdminChat = 1 << 14, // Corvax-Wega-Edit
/// <summary>
/// Unspecified.
/// </summary>
Unspecified = 1 << 14,
Unspecified = 1 << 15, // Corvax-Wega-Edit
/// <summary>
/// Channels considered to be IC.

View File

@@ -11,6 +11,7 @@ public static class ChatChannelExtensions
ChatChannel.LOOC => Color.MediumTurquoise,
ChatChannel.OOC => Color.LightSkyBlue,
ChatChannel.Dead => Color.MediumPurple,
ChatChannel.Mind => Color.Peru, // Corvax-Wega-MindChat
ChatChannel.Admin => Color.Red,
ChatChannel.AdminAlert => Color.Red,
ChatChannel.AdminChat => Color.HotPink,

View File

@@ -46,6 +46,13 @@
/// </summary>
Dead = ChatChannel.Dead,
// Corvax-Wega-MindChat-start
/// <summary>
/// Mind chat
/// </summary>
Mind = ChatChannel.Mind,
// Corvax-Wega-MindChat-end
/// <summary>
/// Admin chat
/// </summary>

View File

@@ -2,6 +2,7 @@ using System.Collections.Frozen;
using System.Text.RegularExpressions;
using Content.Shared.ActionBlocker;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Mind; // Corvax-Wega-MindChat
using Content.Shared.Popups;
using Content.Shared.Radio;
using Content.Shared.Speech;
@@ -31,11 +32,13 @@ public abstract partial class SharedChatSystem : EntitySystem
public const char EmotesAltPrefix = '*';
public const char AdminPrefix = ']';
public const char WhisperPrefix = ',';
public const char MindPrefix = '+'; // Corvax-Wega-MindChat
public const char DefaultChannelKey = 'а'; // Corvax-Wega-Edit
// Corvax-TTS-Start: Moved from Server to Shared
public const int VoiceRange = 10; // how far voice goes in world units
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
public const int MindChatRange = 1000; // Corvax-Wega-MindChat
public static readonly SoundSpecifier DefaultAnnouncementSound
= new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
@@ -57,6 +60,8 @@ public abstract partial class SharedChatSystem : EntitySystem
/// </summary>
private FrozenDictionary<char, RadioChannelPrototype> _keyCodes = default!;
private FrozenDictionary<char, MindChannelPrototype> _mindKeyCodes = default!; // Corvax-Wega-MindChat
public override void Initialize()
{
base.Initialize();
@@ -66,6 +71,7 @@ public abstract partial class SharedChatSystem : EntitySystem
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypeReload);
CacheRadios();
CacheEmotes();
CacheMindChannels(); // Corvax-Wega-MindChat
}
protected virtual void OnPrototypeReload(PrototypesReloadedEventArgs obj)
@@ -75,6 +81,11 @@ public abstract partial class SharedChatSystem : EntitySystem
if (obj.WasModified<EmotePrototype>())
CacheEmotes();
// Corvax-Wega-MindChat-start
if (obj.WasModified<MindChannelPrototype>())
CacheMindChannels();
// Corvax-Wega-MindChat-end
}
private void CacheRadios()
@@ -83,6 +94,14 @@ public abstract partial class SharedChatSystem : EntitySystem
.ToFrozenDictionary(x => x.KeyCode);
}
// Corvax-Wega-MindChat-start
private void CacheMindChannels()
{
_mindKeyCodes = _prototypeManager.EnumeratePrototypes<MindChannelPrototype>()
.ToFrozenDictionary(x => x.KeyCode);
}
// Corvax-Wega-MindChat-end
/// <summary>
/// Attempts to find an applicable <see cref="SpeechVerbPrototype"/> for a speaking entity's message.
/// If one is not found, returns <see cref="DefaultSpeechVerb"/>.
@@ -200,6 +219,42 @@ public abstract partial class SharedChatSystem : EntitySystem
return true;
}
// Corvax-Wega-MindChat-start
public bool TryProcessMindMessage(
EntityUid source,
string input,
out string output,
out MindChannelPrototype? channel,
bool quiet = false)
{
output = input.Trim();
channel = null;
if (input.Length == 0 || !input.StartsWith(MindPrefix))
return false;
if (input.Length < 2 || char.IsWhiteSpace(input[1]))
{
output = SanitizeMessageCapital(input[1..].TrimStart());
if (!quiet)
_popup.PopupEntity(Loc.GetString("chat-manager-no-mind-key"), source, source);
return true;
}
var channelKey = input[1];
channelKey = char.ToLower(channelKey);
output = SanitizeMessageCapital(input[2..].TrimStart());
if (!_mindKeyCodes.TryGetValue(channelKey, out channel) && !quiet)
{
var msg = Loc.GetString("chat-manager-no-such-mind-channel", ("key", channelKey));
_popup.PopupEntity(msg, source, source);
}
return true;
}
// Corvax-Wega-MindChat-end
public string SanitizeMessageCapital(string message)
{
if (string.IsNullOrEmpty(message))
@@ -450,6 +505,15 @@ public abstract partial class SharedChatSystem : EntitySystem
SoundSpecifier? announcementSound = null,
Color? colorOverride = null)
{ }
// Corvax-Wega-MindChat-start
public virtual void SendMindMessage(
EntityUid source,
string message,
MindChannelPrototype channel,
bool ignoreActionBlocker = false)
{ }
// Corvax-Wega-MindChat-end
}
/// <summary>

View File

@@ -130,6 +130,7 @@ public abstract class SharedEmpSystem : EntitySystem
{
if (exclusionsSet.Contains(uid))
continue;
TryEmpEffects(uid, energyConsumption, TimeSpan.FromSeconds(duration));
}
Spawn(EmpPulseEffectPrototype, coordinates);

View File

@@ -3,35 +3,27 @@ using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Blood.Cult.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[RegisterComponent, NetworkedComponent]
public sealed partial class BloodCultistComponent : Component
{
public bool BloodMagicActive = false;
public EntityUid? SelectedSpell { get; set; }
[DataField] public EntityUid? SelectedSpell { get; set; }
public List<EntityUid?> SelectedEmpoweringSpells = new();
[DataField] public List<EntityUid?> SelectedEmpoweringSpells = new();
[DataField, AutoNetworkedField]
public EntityUid? RecallDaggerActionEntity;
[DataField] public EntityUid? RecallDaggerActionEntity;
public EntityUid? RecallSpearAction { get; set; }
[DataField] public EntityUid? RecallSpearAction { get; set; }
[DataField, AutoNetworkedField]
public EntityUid? RecallSpearActionEntity;
[DataField] public EntityUid? RecallSpearActionEntity;
[DataField]
public int BloodCount = 5;
[DataField]
public int Empowering = 0;
public static readonly EntProtoId CultObjective = "ActionBloodCultObjective";
public static readonly EntProtoId CultCommunication = "ActionBloodCultComms";
public static readonly EntProtoId BloodMagic = "ActionBloodMagic";
public static readonly EntProtoId RecallBloodDagger = "ActionRecallBloodDagger";
public static readonly EntProtoId RecallBloodSpear = "RecallBloodCultSpear";
@@ -40,14 +32,17 @@ public sealed partial class BloodCultistComponent : Component
public ProtoId<FactionIconPrototype> StatusIcon { get; set; } = "BloodCultistFaction";
}
[RegisterComponent]
public sealed partial class AutoCultistComponent : Component;
[RegisterComponent, NetworkedComponent]
public sealed partial class ShowCultistIconsComponent : Component;
[RegisterComponent]
public sealed partial class AutoCultistComponent : Component;
public sealed partial class BloodCultObjectComponent : Component;
[RegisterComponent]
public sealed partial class BloodCultObjectComponent : Component;
public sealed partial class BloodCultWeaponComponent : Component;
[RegisterComponent]
public sealed partial class BloodDaggerComponent : Component
@@ -59,15 +54,21 @@ public sealed partial class BloodDaggerComponent : Component
[RegisterComponent]
public sealed partial class BloodSpellComponent : Component
{
[DataField]
public List<string> Prototype = new();
[DataField(required: true)]
public BloodCultSpell SpellType = default!;
}
[RegisterComponent]
public sealed partial class BloodRuneComponent : Component
{
[DataField(required: true)]
public BloodCultRune RuneType = default!;
[DataField]
public string Prototype = default!;
public string Desc { get; private set; } = string.Empty;
[ViewVariables(VVAccess.ReadOnly)]
public string LocDesc => Loc.GetString(Desc);
public bool IsActive = true;
@@ -83,13 +84,18 @@ public sealed partial class BloodRitualDimensionalRendingComponent : Component
public bool Activate = false;
public float NextTimeTick { get; set; }
[DataField("ritualMusic")]
public SoundSpecifier RitualMusic = new SoundCollectionSpecifier("BloodCultMusic");
public bool SoundPlayed;
}
[RegisterComponent, NetworkedComponent]
public sealed partial class BloodStructureComponent : Component
{
[DataField("structureGear")]
public List<string> StructureGear { get; private set; } = new();
public List<EntProtoId> StructureGear { get; private set; } = new();
[ViewVariables(VVAccess.ReadOnly), DataField]
public TimeSpan ActivateTime = TimeSpan.Zero;
@@ -100,9 +106,6 @@ public sealed partial class BloodStructureComponent : Component
[DataField]
public SoundSpecifier? Sound { get; private set; }
[DataField]
public bool CanInteract = true;
public bool IsActive = true;
}
@@ -112,6 +115,12 @@ public sealed partial class BloodPylonComponent : Component
public float NextTimeTick { get; set; }
}
[RegisterComponent]
public sealed partial class BloodShieldActivaebleComponent : Component
{
public string CurrentSlot = "outerClothing";
}
[RegisterComponent]
public sealed partial class BloodOrbComponent : Component
{
@@ -122,7 +131,7 @@ public sealed partial class BloodOrbComponent : Component
public sealed partial class StoneSoulComponent : Component
{
[DataField("soulProto", required: true)]
public string SoulProto { get; set; } = default!;
public EntProtoId SoulProto { get; set; } = default!;
public EntityUid? SoulEntity;
@@ -138,6 +147,9 @@ public sealed partial class ConstructComponent : Component;
[RegisterComponent, NetworkedComponent]
public sealed partial class BloodCultConstructComponent : Component;
[RegisterComponent, NetworkedComponent]
public sealed partial class BloodCultGhostComponent : Component;
[RegisterComponent, NetworkedComponent]
public sealed partial class BloodShuttleCurseComponent : Component;
@@ -155,26 +167,7 @@ public sealed partial class BloodSharpenerComponent : Component;
/// Заглушка для логики
/// </summary>
[RegisterComponent]
public sealed partial class CultistEyesComponent : Component;
public sealed partial class BloodCultistEyesComponent : Component;
[RegisterComponent, NetworkedComponent]
public sealed partial class PentagramDisplayComponent : Component;
[Serializable, NetSerializable]
public enum RuneColorVisuals
{
Color
}
[Serializable, NetSerializable]
public enum StoneSoulVisualLayers : byte
{
Base,
Soul
}
[Serializable, NetSerializable]
public enum StoneSoulVisuals : byte
{
HasSoul
}
public sealed partial class BloodPentagramDisplayComponent : Component;

View File

@@ -0,0 +1,113 @@
using Content.Shared.Eui;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Blood.Cult;
[Serializable, NetSerializable]
public enum BloodCultGod : byte
{
NarSi,
Reaper,
Kharin
}
[Serializable, NetSerializable]
public enum BloodCultSpell : byte
{
Stun,
Teleport,
ShadowShackles,
TwistedConstruction,
SummonEquipment,
BloodRites
}
[Serializable, NetSerializable]
public enum BloodCultRune : byte
{
Offering,
Teleport,
Empowering,
Revive,
Barrier,
Summoning,
Bloodboil,
Spiritrealm,
Ritual,
Default
}
[Serializable, NetSerializable]
public enum RuneColorVisuals : byte
{
Color
}
[Serializable, NetSerializable]
public enum StoneSoulVisuals : byte
{
HasSoul
}
[Serializable, NetSerializable]
public enum VeilShifterVisuals : byte
{
Charged
}
[Serializable, NetSerializable]
public sealed class BloodMagicState : EuiStateBase
{
}
[Serializable, NetSerializable]
public enum BloodRitesUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public enum BloodConstructUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public enum BloodStructureUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public sealed class BloodStructureBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly List<EntProtoId> Items;
public BloodStructureBoundUserInterfaceState(List<EntProtoId> items)
{
Items = items;
}
}
[Serializable, NetSerializable]
public enum BloodRunesUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public sealed class BloodRitualBoundUserInterfaceState : BoundUserInterfaceState
{
}
[Serializable, NetSerializable]
public enum EmpoweringRuneUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public enum SummoningRuneUiKey : byte
{
Key
}

View File

@@ -1,5 +1,7 @@
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Content.Shared.Eui;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Blood.Cult;
@@ -14,35 +16,83 @@ public sealed class RitualConductedEvent : EntityEventArgs
}
[Serializable, NetSerializable]
public sealed class BloodMagicPressedEvent : EntityEventArgs
public sealed class BloodMagicSelectSpellMessage(EntProtoId spell) : EuiMessageBase
{
public NetEntity Uid { get; }
public readonly EntProtoId Spell = spell;
}
public BloodMagicPressedEvent(NetEntity uid)
[Serializable, NetSerializable]
public sealed class BloodRitesSelectRitesMessage : BoundUserInterfaceMessage
{
public EntProtoId Rites { get; }
public BloodRitesSelectRitesMessage(EntProtoId rites)
{
Uid = uid;
Rites = rites;
}
}
[Serializable, NetSerializable]
public sealed class BloodMagicMenuClosedEvent : EntityEventArgs
public sealed class BloodConstructSelectMessage : BoundUserInterfaceMessage
{
public NetEntity Uid { get; }
public string SelectedSpell { get; }
public EntProtoId Construct { get; }
public BloodMagicMenuClosedEvent(NetEntity uid, string selectedSpell)
public BloodConstructSelectMessage(EntProtoId construct)
{
Uid = uid;
SelectedSpell = selectedSpell;
Construct = construct;
}
}
[Serializable, NetSerializable]
public sealed class BloodStructureSelectMessage : BoundUserInterfaceMessage
{
public EntProtoId Item { get; }
public BloodStructureSelectMessage(EntProtoId item)
{
Item = item;
}
}
[Serializable, NetSerializable]
public sealed class SelectBloodRuneMessage : BoundUserInterfaceMessage
{
public EntProtoId RuneProtoId { get; }
public SelectBloodRuneMessage(EntProtoId runeProtoId)
{
RuneProtoId = runeProtoId;
}
}
[Serializable, NetSerializable]
public sealed class EmpoweringRuneSelectSpellMessage : BoundUserInterfaceMessage
{
public EntProtoId Spell { get; }
public EmpoweringRuneSelectSpellMessage(EntProtoId spell)
{
Spell = spell;
}
}
[Serializable, NetSerializable]
public sealed class SummoningRuneSelectCultistMessage : BoundUserInterfaceMessage
{
public NetEntity CultistUid { get; }
public SummoningRuneSelectCultistMessage(NetEntity cultistUid)
{
CultistUid = cultistUid;
}
}
[Serializable, NetSerializable]
public sealed partial class BloodMagicDoAfterEvent : SimpleDoAfterEvent
{
public string SelectedSpell { get; }
public EntProtoId SelectedSpell { get; }
public BloodMagicDoAfterEvent(string selectedSpell)
public BloodMagicDoAfterEvent(EntProtoId selectedSpell)
{
SelectedSpell = selectedSpell;
}
@@ -53,89 +103,17 @@ public sealed partial class TeleportSpellDoAfterEvent : SimpleDoAfterEvent
{
}
[Serializable, NetSerializable]
public sealed class EmpoweringRuneMenuOpenedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public EmpoweringRuneMenuOpenedEvent(NetEntity uid)
{
Uid = uid;
}
}
[Serializable, NetSerializable]
public sealed class EmpoweringRuneMenuClosedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public string SelectedSpell { get; }
public EmpoweringRuneMenuClosedEvent(NetEntity uid, string selectedSpell)
{
Uid = uid;
SelectedSpell = selectedSpell;
}
}
[Serializable, NetSerializable]
public sealed partial class EmpoweringDoAfterEvent : SimpleDoAfterEvent
{
public string SelectedSpell { get; }
public EntProtoId SelectedSpell { get; }
public EmpoweringDoAfterEvent(string selectedSpell)
public EmpoweringDoAfterEvent(EntProtoId selectedSpell)
{
SelectedSpell = selectedSpell;
}
}
[Serializable, NetSerializable]
public sealed class BloodRitesPressedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public BloodRitesPressedEvent(NetEntity uid)
{
Uid = uid;
}
}
[Serializable, NetSerializable]
public sealed class BloodRitesMenuClosedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public string SelectedRites { get; }
public BloodRitesMenuClosedEvent(NetEntity uid, string selectedRites)
{
Uid = uid;
SelectedRites = selectedRites;
}
}
[Serializable, NetSerializable]
public sealed class RunesMenuOpenedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public RunesMenuOpenedEvent(NetEntity uid)
{
Uid = uid;
}
}
[Serializable, NetSerializable]
public sealed class RuneSelectEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public string RuneProto { get; }
public RuneSelectEvent(NetEntity uid, string runeProto)
{
Uid = uid;
RuneProto = runeProto;
}
}
[Serializable, NetSerializable]
public sealed partial class BloodRuneDoAfterEvent : SimpleDoAfterEvent
{
@@ -154,99 +132,7 @@ public sealed partial class BloodRuneCleaningDoAfterEvent : SimpleDoAfterEvent
{
}
[Serializable, NetSerializable]
public sealed class SummoningRuneMenuOpenedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public SummoningRuneMenuOpenedEvent(NetEntity uid)
{
Uid = uid;
}
}
[Serializable, NetSerializable]
public sealed class SummoningSelectedEvent : EntityEventArgs
{
public NetEntity User { get; }
public NetEntity Target { get; }
public SummoningSelectedEvent(NetEntity user, NetEntity target)
{
User = user;
Target = target;
}
}
[Serializable, NetSerializable]
public sealed class OpenConstructMenuEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public NetEntity ConstructUid { get; }
public NetEntity Mind { get; }
public OpenConstructMenuEvent(NetEntity uid, NetEntity constructUid, NetEntity mind)
{
Uid = uid;
ConstructUid = constructUid;
Mind = mind;
}
}
[Serializable, NetSerializable]
public sealed class BloodConstructMenuClosedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public NetEntity ConstructUid { get; }
public NetEntity Mind { get; }
public string ConstructProto { get; }
public BloodConstructMenuClosedEvent(NetEntity uid, NetEntity constructUid, NetEntity mind, string constructProto)
{
Uid = uid;
ConstructUid = constructUid;
Mind = mind;
ConstructProto = constructProto;
}
}
[Serializable, NetSerializable]
public sealed class OpenStructureMenuEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public NetEntity Structure { get; }
public OpenStructureMenuEvent(NetEntity uid, NetEntity structure)
{
Uid = uid;
Structure = structure;
}
}
[Serializable, NetSerializable]
public sealed class BloodStructureMenuClosedEvent : EntityEventArgs
{
public NetEntity Uid { get; }
public string Item { get; }
public NetEntity Structure { get; }
public BloodStructureMenuClosedEvent(NetEntity uid, string item, NetEntity structure)
{
Uid = uid;
Item = item;
Structure = structure;
}
}
// Abilities
public sealed partial class BloodCultObjectiveActionEvent : InstantActionEvent
{
}
public sealed partial class BloodCultCommuneActionEvent : InstantActionEvent
{
}
public sealed partial class BloodCultBloodMagicActionEvent : InstantActionEvent
{
}
@@ -282,7 +168,6 @@ public sealed partial class RecallBloodDaggerEvent : InstantActionEvent
{
}
public sealed partial class BloodCultHallucinationsActionEvent : EntityTargetActionEvent
{
}

View File

@@ -28,9 +28,7 @@ public abstract class SharedBloodCultSystem : EntitySystem
continue;
var protoId = meta.EntityPrototype?.ID;
if (protoId == BloodCultistComponent.CultObjective.Id
|| protoId == BloodCultistComponent.CultCommunication.Id
|| protoId == BloodCultistComponent.BloodMagic.Id
if (protoId == BloodCultistComponent.BloodMagic.Id
|| protoId == BloodCultistComponent.RecallBloodDagger.Id)
{
_action.RemoveAction(cultist, actionId);
@@ -38,29 +36,18 @@ public abstract class SharedBloodCultSystem : EntitySystem
}
}
if (bloodCultist.RecallSpearActionEntity != null)
_action.RemoveAction(cultist, bloodCultist.RecallSpearActionEntity);
if (bloodCultist.SelectedSpell != null)
_action.RemoveAction(cultist, bloodCultist.SelectedSpell.Value);
_action.RemoveAction(cultist, bloodCultist.RecallSpearActionEntity);
_action.RemoveAction(cultist, bloodCultist.SelectedSpell);
foreach (var spell in bloodCultist.SelectedEmpoweringSpells)
{
if (spell != null)
{
_action.RemoveAction(cultist, spell.Value);
}
}
_action.RemoveAction(cultist, spell);
var stunTime = TimeSpan.FromSeconds(4);
var name = Identity.Entity(cultist, EntityManager);
_stun.TryKnockdown(cultist, stunTime, true);
_popup.PopupEntity(Loc.GetString("blood-cult-break-control", ("name", name)), cultist);
_stun.TryKnockdown(cultist, TimeSpan.FromSeconds(4), true);
_popup.PopupEntity(Loc.GetString("blood-cult-break-control", ("name", Identity.Entity(cultist, EntityManager))), cultist);
RemComp<BloodCultistComponent>(cultist);
if (HasComp<CultistEyesComponent>(cultist)) RemComp<CultistEyesComponent>(cultist);
if (HasComp<PentagramDisplayComponent>(cultist)) RemComp<PentagramDisplayComponent>(cultist);
RemComp<BloodCultistEyesComponent>(cultist);
RemComp<BloodPentagramDisplayComponent>(cultist);
}
#endregion
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Card.Tarot.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class CardTarotComponent : Component
{
[DataField(required: true)]
public CardTarot Card = CardTarot.NotEnchanted;
[DataField]
public CardTarotType CardType = CardTarotType.Normal;
public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
}

View File

@@ -0,0 +1,45 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Card.Tarot;
[Serializable, NetSerializable]
public enum CardTarotType : byte
{
Normal,
Reversed
}
[Serializable, NetSerializable]
public enum CardTarot : byte
{
NotEnchanted,
Fool,
Magician,
HighPriestess,
Empress,
Emperor,
Hierophant,
Lovers,
Chariot,
Justice,
Hermit,
WheelOfFortune,
Strength,
HangedMan,
Death,
Temperance,
Devil,
Tower,
Stars,
Moon,
Sun,
Judgement,
TheWorld // !!!!
}
[Serializable, NetSerializable]
public enum CardTarotVisuals : byte
{
State,
Reversed
}

View File

@@ -0,0 +1,11 @@
namespace Content.Shared.EnergyShield;
[RegisterComponent]
public sealed partial class EnergyShieldOwnerComponent : Component
{
[DataField]
public EntityUid? ShieldEntity = null;
[DataField]
public int SustainingCount = 3;
}

View File

@@ -0,0 +1,58 @@
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
namespace Content.Shared.EnergyShield;
public sealed partial class EnergyShieldSystem : EntitySystem
{
[Dependency] private readonly SharedMeleeWeaponSystem _meleeWeapon = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EnergyShieldOwnerComponent, AttackedEvent>(OnAttacked);
SubscribeLocalEvent<EnergyShieldOwnerComponent, ProjectileReflectAttemptEvent>(OnProjectileAttemptE);
}
private void OnAttacked(Entity<EnergyShieldOwnerComponent> ent, ref AttackedEvent args)
{
if (ent.Comp.ShieldEntity == null || ent.Comp.SustainingCount <= 0)
{
RemCompDeferred(ent.Owner, ent.Comp);
QueueDel(ent.Comp.ShieldEntity);
return;
}
ent.Comp.SustainingCount--;
var damage = _meleeWeapon.GetDamage(args.Used, args.User);
args.BonusDamage = -damage;
if (ent.Comp.SustainingCount <= 0)
{
QueueDel(ent.Comp.ShieldEntity);
RemCompDeferred(ent.Owner, ent.Comp);
}
}
private void OnProjectileAttemptE(Entity<EnergyShieldOwnerComponent> ent, ref ProjectileReflectAttemptEvent args)
{
if (ent.Comp.ShieldEntity == null || ent.Comp.SustainingCount <= 0)
{
RemCompDeferred(ent.Owner, ent.Comp);
QueueDel(ent.Comp.ShieldEntity);
return;
}
ent.Comp.SustainingCount--;
args.Cancelled = true;
QueueDel(args.ProjUid);
if (ent.Comp.SustainingCount <= 0)
{
QueueDel(ent.Comp.ShieldEntity);
RemCompDeferred(ent.Owner, ent.Comp);
}
}
}

View File

@@ -0,0 +1,28 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Mind;
[Prototype("mindChannel")]
public sealed partial class MindChannelPrototype : IPrototype
{
/// <summary>
/// Human-readable name for the channel.
/// </summary>
[DataField("name")]
public LocId Name { get; private set; } = string.Empty;
[ViewVariables(VVAccess.ReadOnly)]
public string LocalizedName => Loc.GetString(Name);
/// <summary>
/// Single-character prefix to determine what channel a message should be sent to.
/// </summary>
[DataField("keycode")]
public char KeyCode { get; private set; } = '\0';
[DataField("color")]
public Color Color { get; private set; } = Color.Peru;
[IdDataField, ViewVariables]
public string ID { get; private set; } = default!;
}

View File

@@ -0,0 +1,17 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Mind;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MindLinkComponent : Component
{
/// <summary>
/// Available mind channels for this entity
/// </summary>
[DataField, AutoNetworkedField]
public HashSet<ProtoId<MindChannelPrototype>> Channels = new();
}
[RegisterComponent]
public sealed partial class AdminMindLinkListenerComponent : Component;

View File

@@ -0,0 +1,62 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.Serialization;
namespace Content.Shared.Economy.SlotMachine;
[RegisterComponent]
public sealed partial class SlotMachineComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public bool Working = false;
[DataField, ViewVariables(VVAccess.ReadOnly)]
public int Plays = 0;
[DataField]
public string[] Slots = { "?", "?", "?" };
[DataField]
public int SpinCost = 10;
[DataField]
public EntityUid? User;
public TimeSpan? SpinFinishTime;
[ViewVariables(VVAccess.ReadOnly)] public int JackpotPrize = 50000;
[ViewVariables(VVAccess.ReadOnly)] public int BigWinPrize = 2500;
[ViewVariables(VVAccess.ReadOnly)] public int MediumWinPrize = 1250;
[ViewVariables(VVAccess.ReadOnly)] public int SmallWinPrize = 50;
[ViewVariables(VVAccess.ReadOnly)] public int TinyWinPrize = 10;
// Sounds
public SoundSpecifier CoinSound = new SoundCollectionSpecifier("CoinDrop");
public SoundSpecifier RollSound = new SoundPathSpecifier("/Audio/_Wega/Machines/Roulette/roulettewheel.ogg");
public SoundSpecifier EndSound = new SoundPathSpecifier("/Audio/_Wega/Machines/Roulette/ding_short.ogg");
public SoundSpecifier JackpotSound = new SoundPathSpecifier("/Audio/_Wega/Machines/Roulette/roulettejackpot.ogg");
public SoundSpecifier FailedSound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg");
}
[RegisterComponent]
public sealed partial class CursedSlotMachineComponent : Component
{
[DataField] public int Uses = 0;
[DataField] public int MaxUses = 5;
[ViewVariables(VVAccess.ReadOnly)]
public DamageSpecifier Damage = new DamageSpecifier()
{
DamageDict = { ["Blunt"] = 10, ["Heat"] = 10 }
};
// Sounds
public SoundSpecifier RollSound = new SoundPathSpecifier("/Audio/_Wega/Machines/Roulette/cursed.ogg");
public SoundSpecifier JackpotSound = new SoundPathSpecifier("/Audio/_Wega/Machines/Roulette/cursed_jackpot.ogg");
}
[Serializable, NetSerializable]
public enum SlotMachineVisuals : byte
{
Working
}

Binary file not shown.

View File

@@ -0,0 +1,4 @@
- files: ["coindrop.ogg", "coindrop2.ogg", "cursed_jackpot.ogg", "cursed.ogg", "ding_short.ogg", "roulettejackpot.ogg", "roulettewheel.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Taken from TG Station"
source: "https://github.com/tgstation/tgstation"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
- files: ["tear_of_veil.ogg"]
license: "CC-BY-3.0"
copyright: "Created by Bolgarich"
source: "https://www.youtube.com/watch?v=NqNHKfTAvcw"

Binary file not shown.

View File

@@ -20,3 +20,4 @@ auto-announcements-holiday-mode =
Мы надеемся, что это добавит настроения и сделает ваше пребывание на станции еще более комфортным. Если у вас возникнут вопросы или предложения, пожалуйста, сообщите об этом через службу поддержки.
С уважением,
Автоматическая система "Текна"
auto-announcements-jackpot = ПОЗДРАВЛЯЕМ! {$winner} выиграл ДЖЕКПОТ в игровом автомате!

View File

@@ -20,26 +20,30 @@ select-blood-recharge = Кровавая Перезарядка
select-blood-spear = Кровавое Копье
select-blood-bolt-barrage = Кровавый Шквал Болтов
runes-ui-default-title = Руны
offering-rune = Руна Предложения
offering-rune-desc = Конвертирует нормального члена экипажа в культиста, исцеляя его от физических и ожоговых повреждений. Также создаёт ритуальный кинжал. Если цель мертва, непригодна для конвертации или имеет имплант "Защита разума", она гибнет, оставляя камень душ.
teleport-rune = Руна Телепорта
teleport-rune-desc = Телепортирует объекты и людей на другую случайную руну телепортации. Может быть использована для достаточно быстрой транспортировки.
empowering-rune = Руна Усиления
empowering-rune-desc = Позволяет дополнительно подготовить 4 доступных заклинания и снижает время их подготовки.
revive-rune = Руна Возрождения
revive-rune-desc = Позволяет воскрешать павших культистов, используя глобальные заряды, накопленные при жертвоприношениях на руне предложения. Также можно пробуждать кататоников(ССД) новой душой.
barrier-rune = Руна Барьера
barrier-rune-desc = Создаёт защитный барьер с 100 здоровья, который можно активировать или деактивировать, но каждое использование наносит урон заклинателю.
summoning-rune = Руна Призыва
summoning-rune-desc = Вызывает на руну живого культиста, после чего руна стирается. Работает только на станции.
bloodboil-rune = Руна Вскипания Крови
bloodboil-rune-desc = Высасывает здоровье у заклинателей и наносит мощный урон всем, кто видит руну. Не действует на существа без крови.
spiritrealm-rune = Руна Царства Духов
spiritrealm-rune-desc = Позволяет самому стать духом, чтобы координировать культ.
ritual-dimensional-rending-rune = Ритуал Разрыва Измерений
ritual-dimensional-rending-rune-desc = Вызывает Одно из божеств крови через пространственный разрыв.
# System
blood-cultist-eyes-glow-examined = [color=red]Глаза {$name} созерцают неестественным цветом, это не к добру...[/color]
blood-cult-first-warning = Ваши глаза начали полыхать заревом крови
blood-cult-second-warning = Кровавая пентаграмма образуется над вами, это знак к действию
blood-cult-targets-no-select = Цели ещё не определены...
blood-cult-ritual-completed-next-objective = Ритуал завершён. Приступайте к следующей цели — призыв Нар-Си
blood-cult-objective-complete = Цель выполнена! Слава Нар-Си!
blood-cult-current-targets = Текущие жертвы: { $targets }. Принесите их в жертву, чтобы достичь своей цели.
blood-cult-no-valid-targets = Нет доступных целей для выполнения задачи...
blood-dagger-failed-interact = кинжал выскальзывает из вашей руки порезав ее
blood-dagger-failed-interact = кинжал выскальзывает из вашей руки лезвием нанося увечья
blood-sharpener-success = точило обратилось в прах
blood-sharpener-failed = кинжал уже был заточен
blood-cult-failed-attack = вы не можете навредить членам культа
@@ -47,6 +51,7 @@ stone-soul-empty = камень души пустой
stone-soul-already-summoned = душа уже была призвана
stone-soul-summoned = душа призвана
stone-soul-retracted = душа возвратилось в камень
veil-shifter-examined = Осталось {$count} зарядов
blood-curse-failed = кровавое проклятие не удалось
blood-veil-shifter-failed = ничего не произошло
blood-construct-no-mind = камень души пустой
@@ -54,8 +59,6 @@ blood-construct-failed = конструкт пустой
blood-construct-succses = конструкт призван
blood-structure-failed = взаимодействие будет возможно через { $time } секунд
cult-commune-title = Общение
cult-commune-massage = { $name }: { $massage }
blood-cult-dagger-not-found = кинжала не существует
blood-cult-dagger-recalled = кинжал образуется перед вами
blood-cult-blood-dagger-exists = вы уже имеете кинжал
@@ -65,6 +68,7 @@ blood-orb-invalid-input = Невозможное значение, укажит
blood-orb-not-enough-blood = У вас не достаточно собранной крови
blood-orb-success = Вы выделили { $amount } единиц крови
blood-orb-absorbed = сфера растекается в лужу крови и поглащается
blood-cult-recharge-failed = недостаточно крови для перезарядки...
blood-cult-spear-failed = недостаточно крови для призыва копья
cult-spear-not-found = кровавого копья не существует
cult-spear-too-far = расстояние до копья слишком велико
@@ -81,7 +85,7 @@ ritual-activate-too-soon = РИТУАЛ МОЖНО БУДЕТ НАЧАТЬ ПО
ritual-activate-failed = НЕВОЗМОЖНО НАЧАТЬ РИТУАЛ
rune-activate-failed = невозможно активировать руну
blood-ritual-warning = Образы древнего богоподобного существа соединяюстя воедино { $location }. Прервите ритуал любой целой, пока станция не была уничтожена!
blood-ritual-activate-warning = Был обнаружен сдвиг пространства { $location }. Прекратите распространение всеми доступными средствами. Ожидаемое расширение через 45 секунд.
blood-ritual-activate-warning = Был обнаружен сдвиг пространства { $location }. Прекратите распространение всеми доступными средствами. Ожидаемое расширение через 90 секунд.
ritual-failed = РИТУАЛ ПРОВАЛИ
blood-cultist-offering-message = Mah'weyh pleggh at e'ntrath!

View File

@@ -0,0 +1,29 @@
tarot-card-not-enchanted = Карта Таро не зачарована
tarot-used = {$name} использует карту {$type}
tarot-card-fool = Дурака
tarot-card-magician = Мага
tarot-card-highpriestess = Верховного жреца
tarot-card-empress = Императрицы
tarot-card-emperor = Императора
tarot-card-hierophant = Иерофанта
tarot-card-lovers = Любовников
tarot-card-chariot = Колесницы
tarot-card-justice = Правосудия
tarot-card-hermit = Отшельника
tarot-card-wheeloffortune = Колесо удачи
tarot-card-strength = Силы
tarot-card-hangedman = Повешенного
tarot-card-death = Смерти
tarot-card-temperance = Умеренности
tarot-card-devil = Демона
tarot-card-tower = Башни
tarot-card-stars = Звезд
tarot-card-moon = Луны
tarot-card-sun = Солнца
tarot-card-judgement = Суда
tarot-card-theworld = ЗААААААА ВАРДООООООООО
tarot-devil-healed = Темная энергия исцеляет ваши раны...
tarot-devil-damaged = Темная энергия высасывает вашу жизненную силу...
tarot-moon-m-message = Я долбоеб
tarot-moon-f-message = Я дура

View File

@@ -0,0 +1 @@
chat-mind-message-wrap = [color={$color}]{$channel} [bold]{$name}[/bold] размышляет, "{$message}"[/color]

View File

@@ -0,0 +1,2 @@
hud-chatbox-select-Mind = Разум
hud-chatbox-select-channel-Mind = Разум

View File

@@ -0,0 +1,2 @@
chat-manager-no-access-mind-channel = Вы не можете пользоваться данным каналом
chat-manager-no-mind-channel = Канала не существует

View File

@@ -1,14 +1,16 @@
blood-cult-title = Культ крови
blood-cult-description = Верные последователи крови среди нас...
objective-issuer-blood-god = [color=red]Геометриви Крови[/color]
blood-cult-role-greeting-human =
Вы Культист Крови!
Вы часть тёмного культа, что служит Геометриви Крови, { $god }.
Ваша цель призвать аватар и привести культ к победе.
Вы - Культист Крови!
Вы часть тёмного культа, что служит Геометриви Крови, { $god }.
Ваша цель - призвать аватар и привести культ к победе.
В ваших руках ритуальный кинжал и руны для создания страшных структур. Жертвы должны быть принесены в дар высокопоставленные члены экипажа или охраны.
Призовите божество в укромном месте с помощью 9 культистов и ритуала.
Только так вы обеспечите победу!
blood-cult-role-greeting-animal = Вы Существо Крови! Вы часть тени, служите Геометрии Крови, { $god }. Помогите своим братьям в призыве и принесении жертв.
blood-cult-role-greeting-animal = Вы - Существо Крови! Вы - часть тени, служите Геометрии Крови, { $god }. Помогите своим братьям в призыве и принесении жертв.
current-god-narsie = Нар'Си
current-god-reaper = Жнецу

View File

@@ -1,3 +1,3 @@
metabolizer-type-vampire = Вампир
metabolizer-type-cultist = Культист
metabolizer-type-blood-cultist = Культист крови
metabolizer-type-ariral = Арирал

View File

@@ -0,0 +1 @@
blood-cult-mind-channel = Культ крови

View File

@@ -0,0 +1 @@
objective-condition-blood-ritual-person-title = Пренисите в жертву { $targetName } во славу Геометриви Крови.

View File

@@ -0,0 +1,18 @@
slot-machine-examine = [color=green]{$slots}[/color] | Всего сыграно {$spins} раз
slot-machine-busy = Автомат уже крутится!
slot-machine-unpowered = Машина без питания
slot-machine-no-money = Недостаточно денег для игры!
slot-machine-spinning = Автомат начинает вращение...
slot-machine-lose = Вы ничего не выиграли.
slot-machine-jackpot = ДЖЕКПОТ! Вы выиграли {$prize} кредитов!
slot-machine-bigwin = Крупный выигрыш! Вы выиграли {$prize} кредитов!
slot-medium-win = Выигрыш! Вы получили {$prize} кредитов.
slot-small-win = Неплохо! Вы выиграли {$prize} кредитов.
slot-tiny-win = Скромный приз, вы выиграли {$prize} кредитов!
cursed-slot-machine-uses = Использований [color=red]{$uses}/{$max}[/color]
cursed-slot-machine-deny = Вы играли слишком много раз...
cursed-slot-machine-lose = Вы чувствуете боль, когда энергия покидает ваше тело!
cursed-slot-machine-jackpot = СМЕХ РАЗДАЕТСЯ ВОКРУГ! {$name} ВЫИГРАЛ ДЖЕКПОТ!
cursed-slot-machine-spin = {$name} дергает рычаг с блеском в глазах!

View File

@@ -0,0 +1 @@
cargo-gift-gambling = слот машины

View File

@@ -38,3 +38,5 @@ uplink-syndie-player-name = Плеер синдиката
uplink-syndie-player-desc = Позволяет разбавить рутинную резню своей любимой музыкой.
uplink-syndie-pouch-name = Усиленный карманный подсумок синдиката
uplink-syndie-pouch-desc = Вместительный подсумок с усиленной защитой от взрывов. Всё, что нужно агенту, останется при нём.
uplink-syndie-dice-of-fate-name = Кость судьбы
uplink-syndie-dice-of-fate-desc = Неизвестной таинственности и силы артефакт, который способен кардинально изменить судьбу кинувшего его. Однако это невероятный риск!

View File

@@ -0,0 +1,125 @@
ent-DiceOfFate = { ent-d20Dice }
.desc = { ent-d20Dice.desc }
.suffix = Кость судьбы, НЕ МАППИТЬ
ent-CardTarotBoxEmpty = коробка с картами Таро
.desc = Зачарованная колода карт Таро.
.suffix = Пустая
ent-CardTarotBoxFilled = { ent-CardTarotBoxEmpty }
.desc = { ent-CardTarotBoxEmpty.desc }
.suffix = Полная, НЕ МАППИТЬ
ent-BaseCardTarot = карта Таро
.desc = Это одна из карт из колоды Таро.
ent-CardTarotNotEnchanted = пустая карта Таро
.desc = Это одна из карт в колоде Таро, но она, кажется, пустая.
ent-CardTarotFool = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotMagician = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotHighPriestess = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotEmpress = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotEmperor = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotHierophant = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotLovers = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotChariot = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotJustice = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotHermit = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotWheelOfFortune = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotStrength = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotHangedMan = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotDeath = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotTemperance = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotDevil = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotTower = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotStars = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotMoon = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotSun = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotJudgement = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotTheWorld = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
ent-CardTarotFoolReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotMagicianReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotHighPriestessReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotEmpressReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotEmperorReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotHierophantReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotLoversReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotChariotReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotJusticeReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotHermitReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotWheelOfFortuneReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotStrengthReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotHangedManReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotDeathReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotTemperanceReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotDevilReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotTowerReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotStarsReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotMoonReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotSunReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotJudgementReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая
ent-CardTarotTheWorldReversed = { ent-BaseCardTarot }
.desc = { ent-BaseCardTarot.desc }
.suffix = Перевернутая

View File

@@ -0,0 +1,8 @@
ent-SlotMachine = слот машина
.desc = Азартные игры для необщительных людей. Внесите 10 кредитов и испытайте свою удачу.
ent-BrokenSlotMachine = сломанная слот машина
.desc = Видимо, кому-то очень не повезло и он был очень зол.
.suffix = Сломанная
ent-CursedSlotMachine = слот машина
.desc = Высокие ставки, высокие награды. От этого автомата исходит жутковатая аура.
.suffix = Проклятая, НЕ МАППИТЬ

View File

@@ -0,0 +1,3 @@
ent-BloodCultTargetObjective = { ent-BaseObjective }
ent-BloodCultRitualObjective = Проведите кровавый ритуал
.desc = Геометриви Крови жаждет вернуться в этот мир, и вы должны совершить ритуал, чтобы вернуть ее обратно.

View File

@@ -359,6 +359,7 @@
Piercing: 0.5
Heat: 0.5
- type: GroupExamine
- type: BloodShieldActivaeble # Corvax-Wega-Blood-Cult
- type: entity
parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing]

View File

@@ -114,6 +114,7 @@
- type: MovementSpeedModifier
baseWalkSpeed: 30
# Corvax-End
- type: AdminMindLinkListener # Corvax-Wega-MindChat
- type: entity
abstract: true

View File

@@ -15,6 +15,11 @@
- id: d12Dice
- id: d20Dice
- id: PercentileDie
# Corvax-Wega-DiceOfFate-start
# For fun lol
- id: DiceOfFate
prob: 0.00001
# Corvax-Wega-DiceOfFate-end
- type: Sprite
sprite: Objects/Fun/dice.rsi
state: dicebag

View File

@@ -12,6 +12,11 @@
belt: ClothingBeltWand
pocket1: WizardTeleportScroll
pocket2: WizardsGrimoire
# Corvax-Wega-CardTarot-start
storage:
back:
- CardTarotBoxFilled
# Corvax-Wega-CardTarot-end
- type: startingGear
id: WizardRedGear

View File

@@ -1,31 +1,4 @@
# Basic
- type: entity
id: ActionBloodCultObjective
parent: BaseAction
name: "[color=red]Objective[/color]"
description: "Shows the current purpose of the cult."
categories: [ HideSpawnMenu ]
components:
- type: Action
icon: { sprite: _Wega/Interface/Actions/actions_bloodcult.rsi, state: "vote" }
useDelay: 5
- type: InstantAction
event: !type:BloodCultObjectiveActionEvent
- type: entity
id: ActionBloodCultComms
parent: BaseAction
name: "[color=red]Communication[/color]"
description: "Allows you to talk to your fellow humans."
categories: [ HideSpawnMenu ]
components:
- type: Action
icon: { sprite: _Wega/Interface/Actions/actions_bloodcult.rsi, state: "comms" }
checkCanInteract: false
useDelay: 5
- type: InstantAction
event: !type:BloodCultCommuneActionEvent
- type: entity
id: ActionBloodMagic
parent: BaseAction

View File

@@ -26,4 +26,14 @@
product: SpaceVillainArcadeFilled
cost: 1500
category: cargoproduct-category-name-fun
group: market
group: market
- type: cargoProduct
id: SlotMachine
icon:
sprite: _Wega/Structures/Machines/slot_machine.rsi
state: base
product: SlotMachine
cost: 5000
category: cargoproduct-category-name-fun
group: market

Some files were not shown because too many files have changed in this diff Show More