Psionics (#44)
* Psionics It's a ton of stuff relating to the basic Psionics system and all the powers. I'm saving this as a bit of a sanity check before moving forward. Left to do: 1. Implementing the Psionic faction so that the chat works as intended. 2. Adding the start-state cooldown timers to the actions. * Cleaned up everything with the word 'Psionic' on it. Got the psionic chat working. Got some other stuff working * Some final psionic cleanup. The last batch of content. * Update RobustToolbox * rebased * Revert "Update RobustToolbox" This reverts commitc0cf35d03f. * Update RobustToolbox * Revert "Update RobustToolbox" This reverts commitc4dc828df7. * Update RobustToolbox * Psionics It's a ton of stuff relating to the basic Psionics system and all the powers. I'm saving this as a bit of a sanity check before moving forward. Left to do: 1. Implementing the Psionic faction so that the chat works as intended. 2. Adding the start-state cooldown timers to the actions. * Cleaned up everything with the word 'Psionic' on it. Got the psionic chat working. Got some other stuff working * Some final psionic cleanup. The last batch of content. * rebased * Cleaned up everything with the word 'Psionic' on it. Got the psionic chat working. Got some other stuff working * Broken Commit With these changes in place, the unit does not work. Recording them so i don't lose my work. * Brings it All Together. Dawn of the final Commit. Rebase completed. * Update RobustToolbox * Changed 'Station Events' to 'StationEvents' and cleaned up the Delta-V Events.yml file of duplicate events. * Delete ghost_roles.yml Duplicate. * Update familiars.yml * Update familiars.yml * Update GlimmerReactiveSystem.cs * Makes tinfoil hats craftable. * Decided I'm not dealing with adding fugitives or Glimmer Wisps right now. * Psionic invisibility won't work now that Eye component exists. Or at least, the integrator test won't psas. * Update special.yml * Added #nyanotrasen code or //Nyanotrasen code to many, many files. * Properly fixes comments. --------- Signed-off-by: Colin-Tel <113523727+Colin-Tel@users.noreply.github.com> Signed-off-by: PHCodes <47927305+PHCodes@users.noreply.github.com> Co-authored-by: Debug <sidneymaatman@gmail.com> Co-authored-by: Colin-Tel <113523727+Colin-Tel@users.noreply.github.com>
This commit is contained in:
parent
531d99c064
commit
93c3b03b11
|
|
@ -14,7 +14,7 @@ namespace Content.Client.Chat.Managers
|
|||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public event Action? PermissionsUpdated; //Nyano - Summary: need to be able to update perms for new psionics.
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = Logger.GetSawmill("chat");
|
||||
|
|
@ -67,9 +67,19 @@ namespace Content.Client.Chat.Managers
|
|||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
//Nyano - Summary: sends the command for telepath communication.
|
||||
case ChatSelectChannel.Telepathic:
|
||||
_consoleHost.ExecuteCommand($"tsay \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
|
||||
}
|
||||
}
|
||||
//Nyano - Summary: fires off the update permissions script.
|
||||
public void UpdatePermissions()
|
||||
{
|
||||
PermissionsUpdated?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,5 +7,11 @@ namespace Content.Client.Chat.Managers
|
|||
void Initialize();
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel);
|
||||
|
||||
/// <summary>
|
||||
/// Nyano - Summary:. Will refresh perms.
|
||||
/// </summary>
|
||||
event Action PermissionsUpdated;
|
||||
public void UpdatePermissions();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
|
||||
namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed partial class GlimmerMonitorUi : UIFragment
|
||||
{
|
||||
private GlimmerMonitorUiFragment? _fragment;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new GlimmerMonitorUiFragment();
|
||||
|
||||
_fragment.OnSync += _ => SendSyncMessage(userInterface);
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is not GlimmerMonitorUiState monitorState)
|
||||
return;
|
||||
|
||||
_fragment?.UpdateState(monitorState.GlimmerValues);
|
||||
}
|
||||
|
||||
private void SendSyncMessage(BoundUserInterface userInterface)
|
||||
{
|
||||
var syncMessage = new GlimmerMonitorSyncMessageEvent();
|
||||
var message = new CartridgeUiMessage(syncMessage);
|
||||
userInterface.SendMessage(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<cartridges:GlimmerMonitorUiFragment xmlns:cartridges="clr-namespace:Content.Client.Nyanotrasen.CartridgeLoader.Cartridges"
|
||||
xmlns="https://spacestation14.io" Margin="1 0 2 0">
|
||||
<PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
|
||||
<BoxContainer Name="SettingsBox" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="False">
|
||||
<Label Text="{Loc 'glimmer-monitor-interval'}"/>
|
||||
<Button Name="IntervalButton1" Access="Public" Text="1m" StyleClasses="OpenRight"/>
|
||||
<Button Name="IntervalButton5" Access="Public" Text="5m" StyleClasses="OpenBoth"/>
|
||||
<Button Name="IntervalButton10" Access="Public" Text="10m" StyleClasses="OpenLeft"/>
|
||||
<Button Name="SyncButton" Access="Public" Text="{Loc 'glimmer-monitor-sync'}" Margin="200 0 0 0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="MonitorBox" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
</BoxContainer>
|
||||
</cartridges:GlimmerMonitorUiFragment>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Content.Client.Nyanotrasen.UserInterface.CustomControls;
|
||||
|
||||
namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GlimmerMonitorUiFragment : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public event Action<bool>? OnSync;
|
||||
private List<int> _cachedValues = new();
|
||||
|
||||
public GlimmerMonitorUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
|
||||
var intervalGroup = new ButtonGroup();
|
||||
IntervalButton1.Group = intervalGroup;
|
||||
IntervalButton5.Group = intervalGroup;
|
||||
IntervalButton10.Group = intervalGroup;
|
||||
|
||||
IntervalButton1.Pressed = true;
|
||||
|
||||
IntervalButton1.OnPressed += _ => UpdateState(_cachedValues);
|
||||
IntervalButton5.OnPressed += _ => UpdateState(_cachedValues);
|
||||
IntervalButton10.OnPressed += _ => UpdateState(_cachedValues);
|
||||
|
||||
SyncButton.OnPressed += _ => OnSync?.Invoke(true);
|
||||
}
|
||||
|
||||
public void UpdateState(List<int> glimmerValues)
|
||||
{
|
||||
_cachedValues = glimmerValues;
|
||||
if (glimmerValues.Count < 1)
|
||||
return;
|
||||
|
||||
MonitorBox.RemoveAllChildren();
|
||||
|
||||
var glimmerLabel = new Label();
|
||||
glimmerLabel.Text = Loc.GetString("glimmer-monitor-current-glimmer", ("glimmer", glimmerValues[^1]));
|
||||
MonitorBox.AddChild(glimmerLabel);
|
||||
|
||||
var formattedValues = FormatGlimmerValues(glimmerValues);
|
||||
var graph = new GlimmerGraph(_resourceCache, formattedValues);
|
||||
graph.SetSize = new Vector2(450, 250);
|
||||
MonitorBox.AddChild(graph);
|
||||
}
|
||||
|
||||
|
||||
private List<int> FormatGlimmerValues(List<int> glimmerValues)
|
||||
{
|
||||
var returnList = glimmerValues;
|
||||
|
||||
if (IntervalButton5.Pressed)
|
||||
{
|
||||
returnList = GetAveragedList(glimmerValues, 5);
|
||||
}
|
||||
else if (IntervalButton10.Pressed)
|
||||
{
|
||||
returnList = GetAveragedList(glimmerValues, 10);
|
||||
}
|
||||
|
||||
return ClipToFifteen(returnList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format glimmer values to get <=15 data points correctly.
|
||||
/// </summary>
|
||||
private List<int> ClipToFifteen(List<int> glimmerValues)
|
||||
{
|
||||
List<int> returnList;
|
||||
|
||||
if (glimmerValues.Count <= 15)
|
||||
{
|
||||
returnList = glimmerValues;
|
||||
}
|
||||
else
|
||||
{
|
||||
returnList = glimmerValues.Skip(glimmerValues.Count - 15).ToList();
|
||||
}
|
||||
|
||||
return returnList;
|
||||
}
|
||||
|
||||
private List<int> GetAveragedList(IEnumerable<int> glimmerValues, int interval)
|
||||
{
|
||||
var returnList = new List<int>();
|
||||
var subtotal = 0;
|
||||
var elementsPassed = 0;
|
||||
for (int i = 0; i < glimmerValues.Count(); ++i)
|
||||
{
|
||||
subtotal += glimmerValues.ElementAt(i);
|
||||
++elementsPassed;
|
||||
if (elementsPassed == interval)
|
||||
{
|
||||
returnList.Add(subtotal / interval);
|
||||
subtotal = 0;
|
||||
elementsPassed = 0;
|
||||
}
|
||||
}
|
||||
if (elementsPassed != 0)
|
||||
returnList.Add(subtotal / elementsPassed);
|
||||
return returnList;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Client.Chat.Managers;
|
||||
using Robust.Client.Player;
|
||||
|
||||
namespace Content.Client.Nyanotrasen.Chat
|
||||
{
|
||||
public sealed class PsionicChatUpdateSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PsionicComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PsionicComponent, ComponentRemove>(OnRemove);
|
||||
}
|
||||
|
||||
public PsionicComponent? Player => CompOrNull<PsionicComponent>(_playerManager.LocalPlayer?.ControlledEntity);
|
||||
public bool IsPsionic => Player != null;
|
||||
|
||||
private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args)
|
||||
{
|
||||
_chatManager.UpdatePermissions();
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args)
|
||||
{
|
||||
_chatManager.UpdatePermissions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Content.Client.Psionics.Glimmer;
|
||||
|
||||
public enum GlimmerReactiveVisualLayers : byte
|
||||
{
|
||||
GlimmerEffect,
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Content.Client.Eui;
|
||||
using Content.Shared.Psionics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Psionics.UI
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class AcceptPsionicsEui : BaseEui
|
||||
{
|
||||
private readonly AcceptPsionicsWindow _window;
|
||||
|
||||
public AcceptPsionicsEui()
|
||||
{
|
||||
_window = new AcceptPsionicsWindow();
|
||||
|
||||
_window.DenyButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new AcceptPsionicsChoiceMessage(AcceptPsionicsUiButton.Deny));
|
||||
_window.Close();
|
||||
};
|
||||
|
||||
_window.AcceptButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new AcceptPsionicsChoiceMessage(AcceptPsionicsUiButton.Accept));
|
||||
_window.Close();
|
||||
};
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
IoCManager.Resolve<IClyde>().RequestWindowAttention();
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Psionics.UI
|
||||
{
|
||||
public sealed class AcceptPsionicsWindow : DefaultWindow
|
||||
{
|
||||
public readonly Button DenyButton;
|
||||
public readonly Button AcceptButton;
|
||||
|
||||
public AcceptPsionicsWindow()
|
||||
{
|
||||
|
||||
Title = Loc.GetString("accept-psionics-window-title");
|
||||
|
||||
Contents.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
(new Label()
|
||||
{
|
||||
Text = Loc.GetString("accept-psionics-window-prompt-text-part")
|
||||
}),
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Align = AlignMode.Center,
|
||||
Children =
|
||||
{
|
||||
(AcceptButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("accept-cloning-window-accept-button"),
|
||||
}),
|
||||
|
||||
(new Control()
|
||||
{
|
||||
MinSize = new Vector2(20, 0)
|
||||
}),
|
||||
|
||||
(DenyButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("accept-cloning-window-deny-button"),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Content.Client.Resources;
|
||||
|
||||
namespace Content.Client.Nyanotrasen.UserInterface.CustomControls;
|
||||
|
||||
public sealed class GlimmerGraph : Control
|
||||
{
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly List<int> _glimmer;
|
||||
private const int XOffset = 15;
|
||||
private const int YOffset = 210;
|
||||
private const int Length = 450;
|
||||
private static int YOffsetTop => YOffset - 200;
|
||||
|
||||
public GlimmerGraph(IResourceCache resourceCache, List<int> glimmer)
|
||||
{
|
||||
_resourceCache = resourceCache;
|
||||
_glimmer = glimmer;
|
||||
HorizontalAlignment = HAlignment.Left;
|
||||
VerticalAlignment = VAlignment.Bottom;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
var box = new UIBox2(new Vector2(XOffset, YOffset), new Vector2(XOffset + Length, YOffsetTop));
|
||||
handle.DrawRect(box, Color.FromHex("#424245"));
|
||||
var texture = _resourceCache.GetTexture("/Textures/Interface/glimmerGraph.png");
|
||||
handle.DrawTexture(texture, new Vector2(XOffset, YOffsetTop));
|
||||
|
||||
if (_glimmer.Count < 2)
|
||||
return;
|
||||
|
||||
var spacing = Length / (_glimmer.Count - 1);
|
||||
|
||||
var i = 0;
|
||||
while (i + 1 < _glimmer.Count)
|
||||
{
|
||||
var vector1 = new Vector2(XOffset + i * spacing, YOffset - _glimmer[i] / 5);
|
||||
var vector2 = new Vector2(XOffset + (i + 1) * spacing, YOffset - _glimmer[i + 1] / 5);
|
||||
handle.DrawLine(vector1, vector2, Color.FromHex("#A200BB"));
|
||||
handle.DrawLine(vector1 + new Vector2(0, 1), vector2 + new Vector2(0, 1), Color.FromHex("#A200BB"));
|
||||
handle.DrawLine(vector1 - new Vector2(0, 1), vector2 - new Vector2(0, 1), Color.FromHex("#A200BB"));
|
||||
handle.DrawLine(new Vector2(XOffset + i * spacing, YOffset), new Vector2(XOffset + i * spacing, YOffsetTop), Color.FromHex("#686868"));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +35,7 @@ using Robust.Shared.Random;
|
|||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Client.Nyanotrasen.Chat; //Nyano - Summary: chat namespace.
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Chat;
|
||||
|
||||
|
|
@ -56,6 +57,7 @@ public sealed class ChatUIController : UIController
|
|||
[UISystemDependency] private readonly GhostSystem? _ghost = default;
|
||||
[UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default;
|
||||
[UISystemDependency] private readonly ChatSystem? _chatSys = default;
|
||||
[UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; //Nyano - Summary: makes the psionic chat available.
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
|
|
@ -70,7 +72,8 @@ public sealed class ChatUIController : UIController
|
|||
{SharedChatSystem.EmotesAltPrefix, ChatSelectChannel.Emotes},
|
||||
{SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin},
|
||||
{SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio},
|
||||
{SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}
|
||||
{SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead},
|
||||
{SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //Nyano - Summary: adds the telepathic prefix =.
|
||||
};
|
||||
|
||||
public static readonly Dictionary<ChatSelectChannel, char> ChannelPrefixes = new()
|
||||
|
|
@ -83,7 +86,8 @@ public sealed class ChatUIController : UIController
|
|||
{ChatSelectChannel.Emotes, SharedChatSystem.EmotesPrefix},
|
||||
{ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix},
|
||||
{ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix},
|
||||
{ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}
|
||||
{ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix},
|
||||
{ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //Nyano - Summary: associates telepathic with =.
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -163,6 +167,7 @@ public sealed class ChatUIController : UIController
|
|||
_sawmill = Logger.GetSawmill("chat");
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
_admin.AdminStatusUpdated += UpdateChannelPermissions;
|
||||
_manager.PermissionsUpdated += UpdateChannelPermissions; //Nyano - Summary: the event for when permissions are updated for psionics.
|
||||
_player.LocalPlayerChanged += OnLocalPlayerChanged;
|
||||
_state.OnStateChanged += StateChanged;
|
||||
_net.RegisterNetMessage<MsgChatMessage>(OnChatMessage);
|
||||
|
|
@ -524,8 +529,17 @@ public sealed class ChatUIController : UIController
|
|||
FilterableChannels |= ChatChannel.AdminAlert;
|
||||
FilterableChannels |= ChatChannel.AdminChat;
|
||||
CanSendChannels |= ChatSelectChannel.Admin;
|
||||
FilterableChannels |= ChatChannel.Telepathic; //Nyano - Summary: makes admins able to see psionic chat.
|
||||
}
|
||||
|
||||
// Nyano - Summary: - Begin modified code block to add telepathic as a channel for a psionic user.
|
||||
if (_psionic != null && _psionic.IsPsionic)
|
||||
{
|
||||
FilterableChannels |= ChatChannel.Telepathic;
|
||||
CanSendChannels |= ChatSelectChannel.Telepathic;
|
||||
}
|
||||
// /Nyano - End modified code block
|
||||
|
||||
SelectableChannels = CanSendChannels;
|
||||
|
||||
// Necessary so that we always have a channel to fall back to.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ public sealed partial class ChannelFilterPopup : Popup
|
|||
ChatChannel.Whisper,
|
||||
ChatChannel.Emotes,
|
||||
ChatChannel.Radio,
|
||||
ChatChannel.Telepathic, //Nyano - Summary: adds telepathic chat to where it belongs in order in the chat.
|
||||
ChatChannel.LOOC,
|
||||
ChatChannel.OOC,
|
||||
ChatChannel.Dead,
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ public sealed class ChannelSelectorButton : Button
|
|||
ChatSelectChannel.OOC => Color.LightSkyBlue,
|
||||
ChatSelectChannel.Dead => Color.MediumPurple,
|
||||
ChatSelectChannel.Admin => Color.HotPink,
|
||||
ChatSelectChannel.Telepathic => Color.PaleVioletRed, //Nyano - Summary: determines the color for the chat.
|
||||
_ => Color.DarkGray
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public sealed class ChannelSelectorPopup : Popup
|
|||
ChatSelectChannel.Whisper,
|
||||
ChatSelectChannel.Emotes,
|
||||
ChatSelectChannel.Radio,
|
||||
ChatSelectChannel.Telepathic, //Nyano - Summary: determines the order in which telepathic shows.
|
||||
ChatSelectChannel.LOOC,
|
||||
ChatSelectChannel.OOC,
|
||||
ChatSelectChannel.Dead,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Server.Abilities.Psionics; //Nyano - Summary: the psniocs bin where dispel is located.
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
public sealed partial class AnomalySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAnomalySystem _sharedAnomaly = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DispelPowerSystem _dispel = default!;
|
||||
private void InitializePsionics()
|
||||
{
|
||||
SubscribeLocalEvent<AnomalyComponent, DispelledEvent>(OnDispelled);
|
||||
}
|
||||
|
||||
//Nyano - Summary: gives dispellable behavior to Anomalies.
|
||||
private void OnDispelled(EntityUid uid, AnomalyComponent component, DispelledEvent args)
|
||||
{
|
||||
_dispel.DealDispelDamage(uid);
|
||||
_sharedAnomaly.ChangeAnomalyHealth(uid, 0 - _random.NextFloat(0.4f, 0.8f), component);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Content.Shared.Anomaly.Components;
|
|||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Research.Components;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
|
|
@ -91,6 +92,14 @@ public sealed partial class AnomalySystem
|
|||
if (!TryComp<AnomalyComponent>(anomaly, out var anomalyComponent) || anomalyComponent.ConnectedVessel != null)
|
||||
return;
|
||||
|
||||
// Nyano - Summary - Begin modified code block: tie anomaly harvesting to glimmer rate.
|
||||
if (this.IsPowered(uid, EntityManager) &&
|
||||
TryComp<GlimmerSourceComponent>(anomaly, out var glimmerSource))
|
||||
{
|
||||
glimmerSource.Active = true;
|
||||
}
|
||||
// Nyano - End modified code block.
|
||||
|
||||
component.Anomaly = scanner.ScannedAnomaly;
|
||||
anomalyComponent.ConnectedVessel = uid;
|
||||
UpdateVesselAppearance(uid, component);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||
SubscribeLocalEvent<AnomalyComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<AnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||
|
||||
InitializePsionics(); //Nyano - Summary: stats up psionic related behavior.
|
||||
InitializeGenerator();
|
||||
InitializeScanner();
|
||||
InitializeVessel();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Content.Server.Administration.Managers;
|
|||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Nyanotrasen.Chat;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
|
|
@ -54,6 +55,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
|
||||
//Nyano - Summary: pulls in the nyano chat system for psionics.
|
||||
[Dependency] private readonly NyanoChatSystem _nyanoChatSystem = default!;
|
||||
|
||||
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
|
||||
|
|
@ -238,6 +242,10 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
case InGameICChatType.Emote:
|
||||
SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
break;
|
||||
//Nyano - Summary: case adds the telepathic chat sending ability.
|
||||
case InGameICChatType.Telepathic:
|
||||
_nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -867,7 +875,8 @@ public enum InGameICChatType : byte
|
|||
{
|
||||
Speak,
|
||||
Emote,
|
||||
Whisper
|
||||
Whisper,
|
||||
Telepathic //Nyano - Summary: adds telepathic as a type of message users can receive.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Content.Server.Speech.Components;
|
|||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Server.Psionics; //Nyano - Summary: pulls in the ability for the sentient creature to become psionic.
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects;
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ public sealed partial class MakeSentient : ReagentEffect
|
|||
|
||||
ghostRole = entityManager.AddComponent<GhostRoleComponent>(uid);
|
||||
entityManager.EnsureComponent<GhostTakeoverAvailableComponent>(uid);
|
||||
entityManager.EnsureComponent<PotentialPsionicComponent>(uid); //Nyano - Summary:. Makes the animated body able to get psionics.
|
||||
|
||||
var entityData = entityManager.GetComponent<MetaDataComponent>(uid);
|
||||
ghostRole.RoleName = entityData.EntityName;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ using Robust.Shared.Containers;
|
|||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Psionics; //Nyano - Summary: allows the potential psionic ability to be written to the character.
|
||||
|
||||
namespace Content.Server.Cloning
|
||||
{
|
||||
|
|
@ -241,6 +242,9 @@ namespace Content.Server.Cloning
|
|||
var mob = Spawn(speciesPrototype.Prototype, Transform(uid).MapPosition);
|
||||
_humanoidSystem.CloneAppearance(bodyToClone, mob);
|
||||
|
||||
///Nyano - Summary: adds the potential psionic trait to the reanimated mob.
|
||||
EnsureComp<PotentialPsionicComponent>(mob);
|
||||
|
||||
var ev = new CloningEvent(bodyToClone, mob);
|
||||
RaiseLocalEvent(bodyToClone, ref ev);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,5 +25,13 @@ namespace Content.Server.NPC.Components
|
|||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly HashSet<string> HostileFactions = new();
|
||||
|
||||
// Nyano - Summary - Begin modified code block: support for specific entities to be friendly.
|
||||
/// <summary>
|
||||
/// Permanently friendly specific entities. Our summoner, etc.
|
||||
/// Would like to separate. Could I do that by extending this method, maybe?
|
||||
/// </summary>
|
||||
public HashSet<EntityUid> ExceptionalFriendlies = new();
|
||||
// Nyano - End modified code block.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ namespace Content.Server.NPC.Systems;
|
|||
|
||||
/// <summary>
|
||||
/// Outlines faction relationships with each other.
|
||||
/// part of psionics rework was making this a partial class. Should've already been handled upstream, based on the linter.
|
||||
/// </summary>
|
||||
public sealed class NpcFactionSystem : EntitySystem
|
||||
public sealed partial class NpcFactionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly FactionExceptionSystem _factionException = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Revenant.Components;
|
||||
using Content.Server.Guardian;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class DispelPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly GuardianSystem _guardianSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DispelPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<DispelPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<DispelPowerActionEvent>(OnPowerUsed);
|
||||
|
||||
SubscribeLocalEvent<DispellableComponent, DispelledEvent>(OnDispelled);
|
||||
SubscribeLocalEvent<DamageOnDispelComponent, DispelledEvent>(OnDmgDispelled);
|
||||
// Upstream stuff we're just gonna handle here
|
||||
SubscribeLocalEvent<GuardianComponent, DispelledEvent>(OnGuardianDispelled);
|
||||
SubscribeLocalEvent<FamiliarComponent, DispelledEvent>(OnFamiliarDispelled);
|
||||
SubscribeLocalEvent<RevenantComponent, DispelledEvent>(OnRevenantDispelled);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.DispelActionEntity, component.DispelActionId );
|
||||
_actions.TryGetActionData( component.DispelActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.DispelActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.DispelActionEntity;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, DispelPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.DispelActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(DispelPowerActionEvent args)
|
||||
{
|
||||
if (HasComp<PsionicInsulationComponent>(args.Target))
|
||||
return;
|
||||
|
||||
var ev = new DispelledEvent();
|
||||
RaiseLocalEvent(args.Target, ev, false);
|
||||
|
||||
if (ev.Handled)
|
||||
{
|
||||
args.Handled = true;
|
||||
_psionics.LogPowerUsed(args.Performer, "dispel");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDispelled(EntityUid uid, DispellableComponent component, DispelledEvent args)
|
||||
{
|
||||
QueueDel(uid);
|
||||
Spawn("Ash", Transform(uid).Coordinates);
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("psionic-burns-up", ("item", uid)), Transform(uid).Coordinates, Filter.Pvs(uid), true, Shared.Popups.PopupType.MediumCaution);
|
||||
_audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(uid), uid, true);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDmgDispelled(EntityUid uid, DamageOnDispelComponent component, DispelledEvent args)
|
||||
{
|
||||
var damage = component.Damage;
|
||||
var modifier = (1 + component.Variance) - (_random.NextFloat(0, component.Variance * 2));
|
||||
|
||||
damage *= modifier;
|
||||
DealDispelDamage(uid, damage);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGuardianDispelled(EntityUid uid, GuardianComponent guardian, DispelledEvent args)
|
||||
{
|
||||
if (TryComp<GuardianHostComponent>(guardian.Host, out var host))
|
||||
_guardianSystem.ToggleGuardian(guardian.Host, host);
|
||||
|
||||
DealDispelDamage(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnFamiliarDispelled(EntityUid uid, FamiliarComponent component, DispelledEvent args)
|
||||
{
|
||||
if (component.Source != null)
|
||||
EnsureComp<SummonableRespawningComponent>(component.Source.Value);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRevenantDispelled(EntityUid uid, RevenantComponent component, DispelledEvent args)
|
||||
{
|
||||
DealDispelDamage(uid);
|
||||
_statusEffects.TryAddStatusEffect(uid, "Corporeal", TimeSpan.FromSeconds(30), false, "Corporeal");
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void DealDispelDamage(EntityUid uid, DamageSpecifier? damage = null)
|
||||
{
|
||||
if (Deleted(uid))
|
||||
return;
|
||||
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("psionic-burn-resist", ("item", uid)), Transform(uid).Coordinates, Filter.Pvs(uid), true, Shared.Popups.PopupType.SmallCaution);
|
||||
_audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(uid), uid, true);
|
||||
|
||||
if (damage == null)
|
||||
{
|
||||
damage = new();
|
||||
damage.DamageDict.Add("Blunt", 100);
|
||||
}
|
||||
_damageableSystem.TryChangeDamage(uid, damage, true, true);
|
||||
}
|
||||
}
|
||||
public sealed class DispelledEvent : HandledEntityEventArgs {}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class MetapsionicPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popups = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MetapsionicPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<MetapsionicPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<MetapsionicPowerComponent, MetapsionicPowerActionEvent>(OnPowerUsed);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.MetapsionicActionEntity, component.MetapsionicActionId );
|
||||
_actions.TryGetActionData( component.MetapsionicActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.MetapsionicActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.MetapsionicActionEntity;
|
||||
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.MetapsionicActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args)
|
||||
{
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range))
|
||||
{
|
||||
if (HasComp<PsionicComponent>(entity) && entity != uid && !HasComp<PsionicInsulationComponent>(entity) &&
|
||||
!(HasComp<ClothingGrantPsionicPowerComponent>(entity) && Transform(entity).ParentUid == uid))
|
||||
{
|
||||
_popups.PopupEntity(Loc.GetString("metapsionic-pulse-success"), uid, uid, PopupType.LargeCaution);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
_popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large);
|
||||
_psionics.LogPowerUsed(uid, "metapsionic pulse", 2, 4);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.GameTicking;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class MindSwapPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MindSwapPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<MindSwapPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<MindSwapPowerActionEvent>(OnPowerUsed);
|
||||
SubscribeLocalEvent<MindSwappedComponent, MindSwapPowerReturnActionEvent>(OnPowerReturned);
|
||||
SubscribeLocalEvent<MindSwappedComponent, DispelledEvent>(OnDispelled);
|
||||
SubscribeLocalEvent<MindSwappedComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<GhostAttemptHandleEvent>(OnGhostAttempt);
|
||||
//
|
||||
SubscribeLocalEvent<MindSwappedComponent, ComponentInit>(OnSwapInit);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId );
|
||||
_actions.TryGetActionData( component.MindSwapActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.MindSwapActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.MindSwapActionEntity;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.MindSwapActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(MindSwapPowerActionEvent args)
|
||||
{
|
||||
if (!(TryComp<DamageableComponent>(args.Target, out var damageable) && damageable.DamageContainerID == "Biological"))
|
||||
return;
|
||||
|
||||
if (HasComp<PsionicInsulationComponent>(args.Target))
|
||||
return;
|
||||
|
||||
Swap(args.Performer, args.Target);
|
||||
|
||||
_psionics.LogPowerUsed(args.Performer, "mind swap");
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPowerReturned(EntityUid uid, MindSwappedComponent component, MindSwapPowerReturnActionEvent args)
|
||||
{
|
||||
if (HasComp<PsionicInsulationComponent>(component.OriginalEntity) || HasComp<PsionicInsulationComponent>(uid))
|
||||
return;
|
||||
|
||||
if (HasComp<MobStateComponent>(uid) && !_mobStateSystem.IsAlive(uid))
|
||||
return;
|
||||
|
||||
// How do we get trapped?
|
||||
// 1. Original target doesn't exist
|
||||
if (!component.OriginalEntity.IsValid() || Deleted(component.OriginalEntity))
|
||||
{
|
||||
GetTrapped(uid);
|
||||
return;
|
||||
}
|
||||
// 1. Original target is no longer mindswapped
|
||||
if (!TryComp<MindSwappedComponent>(component.OriginalEntity, out var targetMindSwap))
|
||||
{
|
||||
GetTrapped(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Target has undergone a different mind swap
|
||||
if (targetMindSwap.OriginalEntity != uid)
|
||||
{
|
||||
GetTrapped(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Target is dead
|
||||
if (HasComp<MobStateComponent>(component.OriginalEntity) && _mobStateSystem.IsDead(component.OriginalEntity))
|
||||
{
|
||||
GetTrapped(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
Swap(uid, component.OriginalEntity, true);
|
||||
}
|
||||
|
||||
private void OnDispelled(EntityUid uid, MindSwappedComponent component, DispelledEvent args)
|
||||
{
|
||||
Swap(uid, component.OriginalEntity, true);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, MindSwappedComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
RemComp<MindSwappedComponent>(uid);
|
||||
}
|
||||
|
||||
private void OnGhostAttempt(GhostAttemptHandleEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!HasComp<MindSwappedComponent>(args.Mind.CurrentEntity))
|
||||
return;
|
||||
|
||||
//No idea where the viaCommand went. It's on the internal OnGhostAttempt, but not this layer. Maybe unnecessary.
|
||||
/*if (!args.viaCommand)
|
||||
return;*/
|
||||
|
||||
args.Result = false;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSwapInit(EntityUid uid, MindSwappedComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.MindSwapReturnActionEntity, component.MindSwapReturnActionId );
|
||||
_actions.TryGetActionData( component.MindSwapReturnActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.MindSwapReturnActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.MindSwapReturnActionEntity;
|
||||
}
|
||||
|
||||
public void Swap(EntityUid performer, EntityUid target, bool end = false)
|
||||
{
|
||||
if (end && (!HasComp<MindSwappedComponent>(performer) || !HasComp<MindSwappedComponent>(target)))
|
||||
return;
|
||||
|
||||
// Get the minds first. On transfer, they'll be gone.
|
||||
MindComponent? performerMind = null;
|
||||
MindComponent? targetMind = null;
|
||||
|
||||
// This is here to prevent missing MindContainerComponent Resolve errors.
|
||||
if(!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)){
|
||||
performerMind = null;
|
||||
};
|
||||
|
||||
if(!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)){
|
||||
targetMind = null;
|
||||
};
|
||||
|
||||
// Do the transfer.
|
||||
if (performerMind != null)
|
||||
_mindSystem.TransferTo(performerMindId, target, ghostCheckOverride: true, false, performerMind);
|
||||
|
||||
if (targetMind != null)
|
||||
_mindSystem.TransferTo(targetMindId, performer, ghostCheckOverride: true, false, targetMind);
|
||||
|
||||
if (end)
|
||||
{
|
||||
var performerMindPowerComp = EntityManager.GetComponent<MindSwappedComponent>(performer);
|
||||
var targetMindPowerComp = EntityManager.GetComponent<MindSwappedComponent>(target);
|
||||
_actions.RemoveAction(performer, performerMindPowerComp.MindSwapReturnActionEntity);
|
||||
_actions.RemoveAction(target, targetMindPowerComp.MindSwapReturnActionEntity);
|
||||
|
||||
RemComp<MindSwappedComponent>(performer);
|
||||
RemComp<MindSwappedComponent>(target);
|
||||
return;
|
||||
}
|
||||
|
||||
var perfComp = EnsureComp<MindSwappedComponent>(performer);
|
||||
var targetComp = EnsureComp<MindSwappedComponent>(target);
|
||||
|
||||
perfComp.OriginalEntity = target;
|
||||
targetComp.OriginalEntity = performer;
|
||||
}
|
||||
|
||||
public void GetTrapped(EntityUid uid)
|
||||
{
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("mindswap-trapped"), uid, uid, Shared.Popups.PopupType.LargeCaution);
|
||||
var perfComp = EnsureComp<MindSwappedComponent>(uid);
|
||||
_actions.RemoveAction(uid, perfComp.MindSwapReturnActionEntity, null);
|
||||
|
||||
if (HasComp<TelegnosticProjectionComponent>(uid))
|
||||
{
|
||||
RemComp<PsionicallyInvisibleComponent>(uid);
|
||||
RemComp<StealthComponent>(uid);
|
||||
EnsureComp<SpeechComponent>(uid);
|
||||
EnsureComp<DispellableComponent>(uid);
|
||||
MetaData(uid).EntityName = Loc.GetString("telegnostic-trapped-entity-name");
|
||||
MetaData(uid).EntityDescription = Loc.GetString("telegnostic-trapped-entity-desc");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class MindSwappedComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public EntityUid OriginalEntity = default!;
|
||||
[DataField("mindSwapReturnActionId",
|
||||
customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? MindSwapReturnActionId = "ActionMindSwapReturn";
|
||||
|
||||
[DataField("mindSwapReturnActionEntity")]
|
||||
public EntityUid? MindSwapReturnActionEntity;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Server.Beam;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class NoosphericZapPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly BeamSystem _beam = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<NoosphericZapPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<NoosphericZapPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<NoosphericZapPowerActionEvent>(OnPowerUsed);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.NoosphericZapActionEntity, component.NoosphericZapActionId );
|
||||
_actions.TryGetActionData( component.NoosphericZapActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.NoosphericZapActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.NoosphericZapActionEntity;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.NoosphericZapActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(NoosphericZapPowerActionEvent args)
|
||||
{
|
||||
if (!HasComp<PotentialPsionicComponent>(args.Target))
|
||||
return;
|
||||
|
||||
if (HasComp<PsionicInsulationComponent>(args.Target))
|
||||
return;
|
||||
|
||||
_beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric");
|
||||
|
||||
_stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(5), false);
|
||||
_statusEffectsSystem.TryAddStatusEffect(args.Target, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent");
|
||||
|
||||
_psionics.LogPowerUsed(args.Performer, "noospheric zap");
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Stealth;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Server.Psionics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class PsionicInvisibilityPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly SharedStealthSystem _stealth = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PsionicInvisibilityPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PsionicInvisibilityPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<PsionicInvisibilityPowerComponent, PsionicInvisibilityPowerActionEvent>(OnPowerUsed);
|
||||
SubscribeLocalEvent<RemovePsionicInvisibilityOffPowerActionEvent>(OnPowerOff);
|
||||
SubscribeLocalEvent<PsionicInvisibilityUsedComponent, ComponentInit>(OnStart);
|
||||
SubscribeLocalEvent<PsionicInvisibilityUsedComponent, ComponentShutdown>(OnEnd);
|
||||
SubscribeLocalEvent<PsionicInvisibilityUsedComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId );
|
||||
_actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.PsionicInvisibilityActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.PsionicInvisibilityActionEntity;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.PsionicInvisibilityActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args)
|
||||
{
|
||||
if (HasComp<PsionicInvisibilityUsedComponent>(uid))
|
||||
return;
|
||||
|
||||
ToggleInvisibility(args.Performer);
|
||||
var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype);
|
||||
_actions.AddAction(uid, action, action);
|
||||
_actions.TryGetActionData( action, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(action);
|
||||
|
||||
_psionics.LogPowerUsed(uid, "psionic invisibility");
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPowerOff(RemovePsionicInvisibilityOffPowerActionEvent args)
|
||||
{
|
||||
if (!HasComp<PsionicInvisibilityUsedComponent>(args.Performer))
|
||||
return;
|
||||
|
||||
ToggleInvisibility(args.Performer);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnStart(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentInit args)
|
||||
{
|
||||
EnsureComp<PsionicallyInvisibleComponent>(uid);
|
||||
EnsureComp<PacifiedComponent>(uid);
|
||||
var stealth = EnsureComp<StealthComponent>(uid);
|
||||
_stealth.SetVisibility(uid, 0.66f, stealth);
|
||||
SoundSystem.Play("/Audio/Effects/toss.ogg", Filter.Pvs(uid), uid);
|
||||
|
||||
}
|
||||
|
||||
private void OnEnd(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (Terminating(uid))
|
||||
return;
|
||||
|
||||
RemComp<PsionicallyInvisibleComponent>(uid);
|
||||
RemComp<PacifiedComponent>(uid);
|
||||
RemComp<StealthComponent>(uid);
|
||||
SoundSystem.Play("/Audio/Effects/toss.ogg", Filter.Pvs(uid), uid);
|
||||
//Pretty sure this DOESN'T work as intended.
|
||||
_actions.RemoveAction(uid, component.PsionicInvisibilityUsedActionEntity);
|
||||
|
||||
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(8), false);
|
||||
DirtyEntity(uid);
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, PsionicInvisibilityUsedComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased)
|
||||
return;
|
||||
|
||||
ToggleInvisibility(uid);
|
||||
}
|
||||
|
||||
public void ToggleInvisibility(EntityUid uid)
|
||||
{
|
||||
if (!HasComp<PsionicInvisibilityUsedComponent>(uid))
|
||||
{
|
||||
EnsureComp<PsionicInvisibilityUsedComponent>(uid);
|
||||
} else
|
||||
{
|
||||
RemComp<PsionicInvisibilityUsedComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Psionics.Events;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Examine;
|
||||
using static Content.Shared.Examine.ExamineSystemShared;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class PsionicRegenerationPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PsionicRegenerationPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PsionicRegenerationPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<PsionicRegenerationPowerComponent, PsionicRegenerationPowerActionEvent>(OnPowerUsed);
|
||||
|
||||
SubscribeLocalEvent<PsionicRegenerationPowerComponent, DispelledEvent>(OnDispelled);
|
||||
SubscribeLocalEvent<PsionicRegenerationPowerComponent, PsionicRegenerationDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId );
|
||||
_actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.PsionicRegenerationActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.PsionicRegenerationActionEntity;
|
||||
}
|
||||
|
||||
private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args)
|
||||
{
|
||||
var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime);
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId);
|
||||
|
||||
component.DoAfter = doAfterId;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)),
|
||||
uid,
|
||||
// TODO: Use LoS-based Filter when one is available.
|
||||
Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)),
|
||||
true,
|
||||
PopupType.Medium);
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundUse, component.Owner, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f));
|
||||
_psionics.LogPowerUsed(uid, "psionic regeneration");
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.PsionicRegenerationActionEntity);
|
||||
}
|
||||
|
||||
private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args)
|
||||
{
|
||||
if (component.DoAfter == null)
|
||||
return;
|
||||
|
||||
_doAfterSystem.Cancel(component.DoAfter);
|
||||
component.DoAfter = null;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationDoAfterEvent args)
|
||||
{
|
||||
component.DoAfter = null;
|
||||
|
||||
if (!TryComp<BloodstreamComponent>(uid, out var stream))
|
||||
return;
|
||||
|
||||
// DoAfter has no way to run a callback during the process to give
|
||||
// small doses of the reagent, so we wait until either the action
|
||||
// is cancelled (by being dispelled) or complete to give the
|
||||
// appropriate dose. A timestamp delta is used to accomplish this.
|
||||
var percentageComplete = Math.Min(1f, (_gameTiming.CurTime - args.StartedAt).TotalSeconds / component.UseDelay);
|
||||
|
||||
var solution = new Solution();
|
||||
solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(component.EssenceAmount * percentageComplete));
|
||||
_bloodstreamSystem.TryAddToChemicals(uid, solution, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class PyrokinesisPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PyrokinesisPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PyrokinesisPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<PyrokinesisPowerActionEvent>(OnPowerUsed);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId );
|
||||
_actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.PyrokinesisActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.PyrokinesisActionEntity;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.PyrokinesisActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(PyrokinesisPowerActionEvent args)
|
||||
{
|
||||
if (!TryComp<FlammableComponent>(args.Target, out var flammableComponent))
|
||||
return;
|
||||
|
||||
flammableComponent.FireStacks += 5;
|
||||
_flammableSystem.Ignite(args.Target, args.Target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("pyrokinesis-power-used", ("target", args.Target)), args.Target, Shared.Popups.PopupType.LargeCaution);
|
||||
|
||||
_psionics.LogPowerUsed(args.Performer, "pyrokinesis");
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class TelegnosisPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly MindSwapPowerSystem _mindSwap = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<TelegnosisPowerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<TelegnosisPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<TelegnosisPowerComponent, TelegnosisPowerActionEvent>(OnPowerUsed);
|
||||
SubscribeLocalEvent<TelegnosticProjectionComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId );
|
||||
_actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData );
|
||||
if (actionData is { UseDelay: not null })
|
||||
_actions.StartUseDelay(component.TelegnosisActionEntity);
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
psionic.PsionicAbility = component.TelegnosisActionEntity;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.TelegnosisActionEntity);
|
||||
}
|
||||
|
||||
private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args)
|
||||
{
|
||||
var projection = Spawn(component.Prototype, Transform(uid).Coordinates);
|
||||
Transform(projection).AttachToGridOrMap();
|
||||
_mindSwap.Swap(uid, projection);
|
||||
|
||||
_psionics.LogPowerUsed(uid, "telegnosis");
|
||||
args.Handled = true;
|
||||
}
|
||||
private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args)
|
||||
{
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server.Abilities.Psionics
|
||||
{
|
||||
public sealed class PsionicAbilitiesSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PsionicAwaitingPlayerComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, PsionicAwaitingPlayerComponent component, PlayerAttachedEvent args)
|
||||
{
|
||||
if (TryComp<PsionicBonusChanceComponent>(uid, out var bonus) && bonus.Warn == true)
|
||||
_euiManager.OpenEui(new AcceptPsionicsEui(uid, this), args.Player);
|
||||
else
|
||||
AddRandomPsionicPower(uid);
|
||||
RemCompDeferred<PsionicAwaitingPlayerComponent>(uid);
|
||||
}
|
||||
|
||||
public void AddPsionics(EntityUid uid, bool warn = true)
|
||||
{
|
||||
if (Deleted(uid))
|
||||
return;
|
||||
|
||||
if (HasComp<PsionicComponent>(uid))
|
||||
return;
|
||||
|
||||
//Don't know if this will work. New mind state vs old.
|
||||
if (!TryComp<MindContainerComponent>(uid, out var mindContainer) ||
|
||||
!_mindSystem.TryGetMind(uid, out _, out var mind ))
|
||||
//||
|
||||
//!_mindSystem.TryGetMind(uid, out var mind, mindContainer))
|
||||
{
|
||||
EnsureComp<PsionicAwaitingPlayerComponent>(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mindSystem.TryGetSession(mind, out var client))
|
||||
return;
|
||||
|
||||
if (warn && TryComp<ActorComponent>(uid, out var actor))
|
||||
_euiManager.OpenEui(new AcceptPsionicsEui(uid, this), client);
|
||||
else
|
||||
AddRandomPsionicPower(uid);
|
||||
}
|
||||
|
||||
public void AddPsionics(EntityUid uid, string powerComp)
|
||||
{
|
||||
if (Deleted(uid))
|
||||
return;
|
||||
|
||||
if (HasComp<PsionicComponent>(uid))
|
||||
return;
|
||||
|
||||
AddComp<PsionicComponent>(uid);
|
||||
|
||||
var newComponent = (Component) _componentFactory.GetComponent(powerComp);
|
||||
newComponent.Owner = uid;
|
||||
|
||||
EntityManager.AddComponent(uid, newComponent);
|
||||
}
|
||||
|
||||
public void AddRandomPsionicPower(EntityUid uid)
|
||||
{
|
||||
AddComp<PsionicComponent>(uid);
|
||||
|
||||
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>("RandomPsionicPowerPool", out var pool))
|
||||
{
|
||||
Logger.Error("Can't index the random psionic power pool!");
|
||||
return;
|
||||
}
|
||||
|
||||
// uh oh, stinky!
|
||||
var newComponent = (Component) _componentFactory.GetComponent(pool.Pick());
|
||||
newComponent.Owner = uid;
|
||||
|
||||
EntityManager.AddComponent(uid, newComponent);
|
||||
|
||||
_glimmerSystem.Glimmer += _random.Next(1, 5);
|
||||
}
|
||||
|
||||
public void RemovePsionics(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<PsionicComponent>(uid, out var psionic))
|
||||
return;
|
||||
|
||||
if (!psionic.Removable)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>("RandomPsionicPowerPool", out var pool))
|
||||
{
|
||||
Logger.Error("Can't index the random psionic power pool!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var compName in pool.Weights.Keys)
|
||||
{
|
||||
// component moment
|
||||
var comp = _componentFactory.GetComponent(compName);
|
||||
if (EntityManager.TryGetComponent(uid, comp.GetType(), out var psionicPower))
|
||||
RemComp(uid, psionicPower);
|
||||
}
|
||||
if (psionic.PsionicAbility != null){
|
||||
_actionsSystem.TryGetActionData( psionic.PsionicAbility, out var psiAbility );
|
||||
if (psiAbility != null){
|
||||
var owner = psiAbility.Owner;
|
||||
_actionsSystem.RemoveAction(uid, psiAbility.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
_statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(5), false, "StutteringAccent");
|
||||
|
||||
RemComp<PsionicComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.Audio
|
||||
{
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SharedAmbientSoundSystem), typeof(GlimmerReactiveSystem))]
|
||||
public sealed partial class GlimmerSoundComponent : Component
|
||||
{
|
||||
[DataField("glimmerTier", required: true), ViewVariables(VVAccess.ReadWrite)] // only for map editing
|
||||
public Dictionary<string, SoundSpecifier> Sound { get; set; } = new();
|
||||
|
||||
public bool GetSound(GlimmerTier glimmerTier, out SoundSpecifier? spec)
|
||||
{
|
||||
return Sound.TryGetValue(glimmerTier.ToString(), out spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class GlimmerMonitorCartridgeComponent : Component
|
||||
{ }
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed class GlimmerMonitorCartridgeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
|
||||
[Dependency] private readonly PassiveGlimmerReductionSystem _glimmerReductionSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GlimmerMonitorCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
|
||||
SubscribeLocalEvent<GlimmerMonitorCartridgeComponent, CartridgeMessageEvent>(OnMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This gets called when the ui fragment needs to be updated for the first time after activating
|
||||
/// </summary>
|
||||
private void OnUiReady(EntityUid uid, GlimmerMonitorCartridgeComponent component, CartridgeUiReadyEvent args)
|
||||
{
|
||||
UpdateUiState(uid, args.Loader, component);
|
||||
}
|
||||
|
||||
private void OnMessage(EntityUid uid, GlimmerMonitorCartridgeComponent component, CartridgeMessageEvent args)
|
||||
{
|
||||
if (args is not GlimmerMonitorSyncMessageEvent)
|
||||
return;
|
||||
;
|
||||
UpdateUiState(uid, EntityManager.GetEntity( args.LoaderUid ), component);
|
||||
}
|
||||
|
||||
public void UpdateUiState(EntityUid uid, EntityUid loaderUid, GlimmerMonitorCartridgeComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var state = new GlimmerMonitorUiState(_glimmerReductionSystem.GlimmerValues);
|
||||
_cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Drugs;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Content.Server.Nyanotrasen.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for nyano's chat stuff
|
||||
/// </summary>
|
||||
|
||||
public sealed class NyanoChatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
private IEnumerable<INetChannel> GetPsionicChatClients()
|
||||
{
|
||||
return Filter.Empty()
|
||||
.AddWhereAttachedEntity(IsEligibleForTelepathy)
|
||||
.Recipients
|
||||
.Select(p => p.ConnectedClient);
|
||||
}
|
||||
|
||||
private IEnumerable<INetChannel> GetAdminClients()
|
||||
{
|
||||
return _adminManager.ActiveAdmins
|
||||
.Select(p => p.ConnectedClient);
|
||||
}
|
||||
|
||||
private List<INetChannel> GetDreamers(IEnumerable<INetChannel> removeList)
|
||||
{
|
||||
var filtered = Filter.Empty()
|
||||
.AddWhereAttachedEntity(entity => HasComp<SleepingComponent>(entity) || HasComp<SeeingRainbowsComponent>(entity) && !HasComp<PsionicsDisabledComponent>(entity) && !HasComp<PsionicInsulationComponent>(entity))
|
||||
.Recipients
|
||||
.Select(p => p.ConnectedClient);
|
||||
|
||||
var filteredList = filtered.ToList();
|
||||
|
||||
foreach (var entity in removeList)
|
||||
filteredList.Remove(entity);
|
||||
|
||||
return filteredList;
|
||||
}
|
||||
|
||||
private bool IsEligibleForTelepathy(EntityUid entity)
|
||||
{
|
||||
return HasComp<PsionicComponent>(entity)
|
||||
&& !HasComp<PsionicsDisabledComponent>(entity)
|
||||
&& !HasComp<PsionicInsulationComponent>(entity)
|
||||
&& (!TryComp<MobStateComponent>(entity, out var mobstate) || mobstate.CurrentState == MobState.Alive);
|
||||
}
|
||||
|
||||
public void SendTelepathicChat(EntityUid source, string message, bool hideChat)
|
||||
{
|
||||
if (!IsEligibleForTelepathy(source))
|
||||
return;
|
||||
|
||||
var clients = GetPsionicChatClients();
|
||||
var admins = GetAdminClients();
|
||||
string messageWrap;
|
||||
string adminMessageWrap;
|
||||
|
||||
messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message",
|
||||
("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", message));
|
||||
|
||||
adminMessageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message-admin",
|
||||
("source", source), ("message", message));
|
||||
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Telepathic chat from {ToPrettyString(source):Player}: {message}");
|
||||
|
||||
_chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, messageWrap, source, hideChat, true, clients.ToList(), Color.PaleVioletRed);
|
||||
|
||||
_chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, adminMessageWrap, source, hideChat, true, admins, Color.PaleVioletRed);
|
||||
|
||||
if (_random.Prob(0.1f))
|
||||
_glimmerSystem.Glimmer++;
|
||||
|
||||
if (_random.Prob(Math.Min(0.33f + ((float) _glimmerSystem.Glimmer / 1500), 1)))
|
||||
{
|
||||
float obfuscation = (0.25f + (float) _glimmerSystem.Glimmer / 2000);
|
||||
var obfuscated = ObfuscateMessageReadability(message, obfuscation);
|
||||
_chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients), Color.PaleVioletRed);
|
||||
}
|
||||
|
||||
foreach (var repeater in EntityQuery<TelepathicRepeaterComponent>())
|
||||
{
|
||||
_chatSystem.TrySendInGameICMessage(repeater.Owner, message, InGameICChatType.Speak, false);
|
||||
}
|
||||
}
|
||||
|
||||
private string ObfuscateMessageReadability(string message, float chance)
|
||||
{
|
||||
var modifiedMessage = new StringBuilder(message);
|
||||
|
||||
for (var i = 0; i < message.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace((modifiedMessage[i])))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_random.Prob(1 - chance))
|
||||
{
|
||||
modifiedMessage[i] = '~';
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedMessage.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
internal sealed class TSayCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "tsay";
|
||||
public string Description => "Send chat messages to the telepathic.";
|
||||
public string Help => "tsay <text>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not IPlayerSession player)
|
||||
{
|
||||
shell.WriteError("This command cannot be run from the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.Status != SessionStatus.InGame)
|
||||
return;
|
||||
|
||||
if (player.AttachedEntity is not {} playerEntity)
|
||||
{
|
||||
shell.WriteError("You don't have an entity!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length < 1)
|
||||
return;
|
||||
|
||||
var message = string.Join(" ", args).Trim();
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
//Not sure if I should hide the logs from this. Default is false.
|
||||
EntitySystem.Get<ChatSystem>().TrySendInGameICMessage(playerEntity, message, InGameICChatType.Telepathic, ChatTransmitRange.Normal, false, shell, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace Content.Server.Nyanotrasen.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Repeats whatever is happening in telepathic chat.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TelepathicRepeaterComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects
|
||||
{
|
||||
/// <summary>
|
||||
/// Rerolls psionics once.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ChemRemovePsionic : ReagentEffect
|
||||
{
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-chem-remove-psionic", ("chance", Probability));
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
if (args.Scale != 1f)
|
||||
return;
|
||||
|
||||
var psySys = args.EntityManager.EntitySysManager.GetEntitySystem<PsionicAbilitiesSystem>();
|
||||
|
||||
psySys.RemovePsionics(args.SolutionEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Server.Psionics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects
|
||||
{
|
||||
/// <summary>
|
||||
/// Rerolls psionics once.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ChemRerollPsionic : ReagentEffect
|
||||
{
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-chem-reroll-psionic", ("chance", Probability));
|
||||
|
||||
/// <summary>
|
||||
/// Reroll multiplier.
|
||||
/// </summary>
|
||||
[DataField("bonusMultiplier")]
|
||||
public float BonusMuliplier = 1f;
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
var psySys = args.EntityManager.EntitySysManager.GetEntitySystem<PsionicsSystem>();
|
||||
|
||||
psySys.RerollPsionics(args.SolutionEntity, bonusMuliplier: BonusMuliplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Chemistry.ReactionEffects;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class ChangeGlimmerReactionEffect : ReagentEffect
|
||||
{
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-change-glimmer-reaction-effect", ("chance", Probability),
|
||||
("count", Count));
|
||||
|
||||
/// <summary>
|
||||
/// Added to glimmer when reaction occurs.
|
||||
/// </summary>
|
||||
[DataField("count")]
|
||||
public int Count = 1;
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
var glimmersys = args.EntityManager.EntitySysManager.GetEntitySystem<GlimmerSystem>();
|
||||
|
||||
glimmersys.Glimmer += Count;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using Content.Shared.Chemistry.Components;
|
||||
|
||||
namespace Content.Server.Chemistry.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class SolutionRegenerationSwitcherComponent : Component
|
||||
{
|
||||
[DataField("options", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<Solution> Options = default!;
|
||||
|
||||
[DataField("currentIndex"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public int CurrentIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Should the already generated solution be kept when switching?
|
||||
/// </summary>
|
||||
[DataField("keepSolution"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool KeepSolution = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems
|
||||
{
|
||||
public sealed class SolutionRegenerationSwitcherSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popups = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = Logger.GetSawmill("chemistry");
|
||||
|
||||
SubscribeLocalEvent<SolutionRegenerationSwitcherComponent, GetVerbsEvent<AlternativeVerb>>(AddSwitchVerb);
|
||||
}
|
||||
|
||||
private void AddSwitchVerb(EntityUid uid, SolutionRegenerationSwitcherComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
if (component.Options.Count <= 1)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
SwitchReagent(uid, component, args.User);
|
||||
},
|
||||
Text = Loc.GetString("autoreagent-switch"),
|
||||
Priority = 2
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void SwitchReagent(EntityUid uid, SolutionRegenerationSwitcherComponent component, EntityUid user)
|
||||
{
|
||||
if (!TryComp<SolutionRegenerationComponent>(uid, out var solutionRegenerationComponent))
|
||||
{
|
||||
_sawmill.Warning($"{ToPrettyString(uid)} has no SolutionRegenerationComponent.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.CurrentIndex + 1 == component.Options.Count)
|
||||
component.CurrentIndex = 0;
|
||||
else
|
||||
component.CurrentIndex++;
|
||||
|
||||
if (!_solutionSystem.TryGetSolution(uid, solutionRegenerationComponent.Solution, out var solution))
|
||||
{
|
||||
_sawmill.Error($"Can't get SolutionRegeneration.Solution for {ToPrettyString(uid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var newSolution = component.Options[component.CurrentIndex];
|
||||
var primaryId = newSolution.GetPrimaryReagentId();
|
||||
if (primaryId == null)
|
||||
{
|
||||
_sawmill.Error($"Can't get PrimaryReagentId for {ToPrettyString(uid)} on index {component.CurrentIndex}.");
|
||||
return;
|
||||
}
|
||||
ReagentPrototype? proto;
|
||||
|
||||
//Only reagents with spritePath property can change appearance of transformable containers!
|
||||
if (!string.IsNullOrWhiteSpace(primaryId?.Prototype))
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(primaryId.Value.Prototype, out proto))
|
||||
{
|
||||
_sawmill.Error($"Can't get get reagent prototype {primaryId} for {ToPrettyString(uid)}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else return;
|
||||
|
||||
// Empty out the current solution.
|
||||
if (!component.KeepSolution)
|
||||
solution.RemoveAllSolution();
|
||||
|
||||
// Replace the generating solution with the newly selected solution.
|
||||
var generated = solutionRegenerationComponent.Generated;
|
||||
generated.RemoveAllSolution();
|
||||
_solutionSystem.TryAddSolution(uid, generated, newSolution);
|
||||
|
||||
_popups.PopupEntity(Loc.GetString("autoregen-switched", ("reagent", proto.LocalizedName)), user, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.NPC.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
/// <summary>
|
||||
/// Allows clothing to add a faction to you when you wear it.
|
||||
/// </summary>
|
||||
public sealed partial class ClothingAddFactionComponent : Component
|
||||
{
|
||||
public bool IsActive = false;
|
||||
|
||||
/// <summary>
|
||||
/// Faction added
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite),
|
||||
DataField("faction", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<NpcFactionPrototype>))]
|
||||
public string Faction = "";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Content.Server.NPC.Components;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
public partial class NpcFactionSystem : EntitySystem
|
||||
{
|
||||
public void InitializeCore()
|
||||
{
|
||||
SubscribeLocalEvent<NpcFactionMemberComponent, GetNearbyHostilesEvent>(OnGetNearbyHostiles);
|
||||
}
|
||||
|
||||
public bool ContainsFaction(EntityUid uid, string faction, NpcFactionMemberComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
return component.Factions.Contains(faction);
|
||||
}
|
||||
|
||||
public void AddFriendlyEntity(EntityUid uid, EntityUid fEntity, NpcFactionMemberComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
|
||||
component.ExceptionalFriendlies.Add(fEntity);
|
||||
}
|
||||
|
||||
private void OnGetNearbyHostiles(EntityUid uid, NpcFactionMemberComponent component, ref GetNearbyHostilesEvent args)
|
||||
{
|
||||
args.ExceptionalFriendlies.UnionWith(component.ExceptionalFriendlies);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it's trying to determine which nearby entities are hostile.
|
||||
/// </summary>
|
||||
/// <param name="ExceptionalHostiles">Entities that will be counted as hostile regardless of faction. Overriden by friendlies.</param>
|
||||
/// <param name="ExceptionalFriendlies">Entities that will be counted as friendly regardless of faction. Overrides hostiles. </param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct GetNearbyHostilesEvent(HashSet<EntityUid> ExceptionalHostiles, HashSet<EntityUid> ExceptionalFriendlies);
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using Content.Server.NPC.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
public partial class NpcFactionSystem : EntitySystem
|
||||
{
|
||||
public void InitializeItems()
|
||||
{
|
||||
SubscribeLocalEvent<NpcFactionMemberComponent, ItemPurchasedEvent>(OnItemPurchased);
|
||||
|
||||
SubscribeLocalEvent<ClothingAddFactionComponent, GotEquippedEvent>(OnClothingEquipped);
|
||||
SubscribeLocalEvent<ClothingAddFactionComponent, GotUnequippedEvent>(OnClothingUnequipped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we bought something we probably don't want it to start biting us after it's automatically placed in our hands.
|
||||
/// If you do, consider finding a better solution to grenade penguin CBT.
|
||||
/// </summary>
|
||||
private void OnItemPurchased(EntityUid uid, NpcFactionMemberComponent component, ref ItemPurchasedEvent args)
|
||||
{
|
||||
component.ExceptionalFriendlies.Add(args.Purchaser);
|
||||
}
|
||||
|
||||
private void OnClothingEquipped(EntityUid uid, ClothingAddFactionComponent component, GotEquippedEvent args)
|
||||
{
|
||||
if (!TryComp<ClothingComponent>(uid, out var clothing))
|
||||
return;
|
||||
|
||||
if (!clothing.Slots.HasFlag(args.SlotFlags))
|
||||
return;
|
||||
|
||||
if (!TryComp<NpcFactionMemberComponent>(args.Equipee, out var factionComponent))
|
||||
return;
|
||||
|
||||
if (factionComponent.Factions.Contains(component.Faction))
|
||||
return;
|
||||
|
||||
component.IsActive = true;
|
||||
AddFaction(args.Equipee, component.Faction);
|
||||
}
|
||||
|
||||
private void OnClothingUnequipped(EntityUid uid, ClothingAddFactionComponent component, GotUnequippedEvent args)
|
||||
{
|
||||
if (!component.IsActive)
|
||||
return;
|
||||
|
||||
component.IsActive = false;
|
||||
RemoveFaction(args.Equipee, component.Faction);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Shared.Psionics;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Abilities.Psionics;
|
||||
|
||||
namespace Content.Server.Psionics
|
||||
{
|
||||
public sealed class AcceptPsionicsEui : BaseEui
|
||||
{
|
||||
private readonly PsionicAbilitiesSystem _psionicsSystem;
|
||||
private readonly EntityUid _entity;
|
||||
|
||||
public AcceptPsionicsEui(EntityUid entity, PsionicAbilitiesSystem psionicsSys)
|
||||
{
|
||||
_entity = entity;
|
||||
_psionicsSystem = psionicsSys;
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
if (msg is not AcceptPsionicsChoiceMessage choice ||
|
||||
choice.Button == AcceptPsionicsUiButton.Deny)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_psionicsSystem.AddRandomPsionicPower(_entity);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Psionics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class AntiPsionicWeaponComponent : Component
|
||||
{
|
||||
|
||||
[DataField("modifiers", required: true)]
|
||||
public DamageModifierSet Modifiers = default!;
|
||||
|
||||
[DataField("psychicStaminaDamage")]
|
||||
public float PsychicStaminaDamage = 30f;
|
||||
|
||||
[DataField("disableChance")]
|
||||
public float DisableChance = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// Punish when used against a non-psychic.
|
||||
/// </summary
|
||||
[DataField("punish")]
|
||||
public bool Punish = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Psionics.Dreams
|
||||
{
|
||||
public sealed class DreamsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private float _accumulator = 0f;
|
||||
private float _updateRate = 15f;
|
||||
|
||||
public readonly IReadOnlyList<string> DreamSetPrototypes = new[]
|
||||
{
|
||||
"adjectives",
|
||||
"names_first",
|
||||
"verbs",
|
||||
};
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
_accumulator += frameTime;
|
||||
if (_accumulator < _updateRate)
|
||||
return;
|
||||
|
||||
_accumulator -= _updateRate;
|
||||
_updateRate = _random.NextFloat(10f, 30f);
|
||||
|
||||
foreach (var sleeper in EntityQuery<SleepingComponent>())
|
||||
{
|
||||
if (!TryComp<ActorComponent>(sleeper.Owner, out var actor))
|
||||
continue;
|
||||
|
||||
var setName = _random.Pick(DreamSetPrototypes);
|
||||
|
||||
if (!_prototypeManager.TryIndex<DatasetPrototype>(setName, out var set))
|
||||
return;
|
||||
|
||||
var msg = _random.Pick(set.Values) + "..."; //todo... does the seperator need loc?
|
||||
|
||||
var messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message",
|
||||
("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", msg));
|
||||
|
||||
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Telepathic,
|
||||
msg, messageWrap, sleeper.Owner, false, actor.PlayerSession.ConnectedClient, Color.PaleVioletRed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Psionics.Glimmer;
|
||||
|
||||
[AdminCommand(AdminFlags.Logs)]
|
||||
public sealed class GlimmerShowCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "glimmershow";
|
||||
public string Description => Loc.GetString("command-glimmershow-description");
|
||||
public string Help => Loc.GetString("command-glimmershow-help");
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
shell.WriteLine(entMan.EntitySysManager.GetEntitySystem<GlimmerSystem>().Glimmer.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class GlimmerSetCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "glimmerset";
|
||||
public string Description => Loc.GetString("command-glimmerset-description");
|
||||
public string Help => Loc.GetString("command-glimmerset-help");
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
return;
|
||||
|
||||
if (!int.TryParse(args[0], out var glimmerValue))
|
||||
return;
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
entMan.EntitySysManager.GetEntitySystem<GlimmerSystem>().Glimmer = glimmerValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
using Content.Server.Audio;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Server.Lightning;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Revenant.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Psionics.Glimmer
|
||||
{
|
||||
public sealed class GlimmerReactiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!;
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _sharedAmbientSoundSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly LightningSystem _lightning = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
|
||||
[Dependency] private readonly AnchorableSystem _anchorableSystem = default!;
|
||||
[Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!;
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
[Dependency] private readonly RevenantSystem _revenantSystem = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
|
||||
|
||||
public float Accumulator = 0;
|
||||
public const float UpdateFrequency = 15f;
|
||||
public float BeamCooldown = 3;
|
||||
public GlimmerTier LastGlimmerTier = GlimmerTier.Minimal;
|
||||
public bool GhostsVisible = false;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, GlimmerTierChangedEvent>(OnTierChanged);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, GetVerbsEvent<AlternativeVerb>>(AddShockVerb);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, DestructionEventArgs>(OnDestroyed);
|
||||
SubscribeLocalEvent<SharedGlimmerReactiveComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update relevant state on an Entity.
|
||||
/// </summary>
|
||||
/// <param name="glimmerTierDelta">The number of steps in tier
|
||||
/// difference since last update. This can be zero for the sake of
|
||||
/// toggling the enabled states.</param>
|
||||
private void UpdateEntityState(EntityUid uid, SharedGlimmerReactiveComponent component, GlimmerTier currentGlimmerTier, int glimmerTierDelta)
|
||||
{
|
||||
var isEnabled = true;
|
||||
|
||||
if (component.RequiresApcPower)
|
||||
if (TryComp(uid, out ApcPowerReceiverComponent? apcPower))
|
||||
isEnabled = apcPower.Powered;
|
||||
|
||||
_appearanceSystem.SetData(uid, GlimmerReactiveVisuals.GlimmerTier, isEnabled ? currentGlimmerTier : GlimmerTier.Minimal);
|
||||
|
||||
// update ambient sound
|
||||
if (TryComp(uid, out GlimmerSoundComponent? glimmerSound)
|
||||
&& TryComp(uid, out AmbientSoundComponent? ambientSoundComponent)
|
||||
&& glimmerSound.GetSound(currentGlimmerTier, out SoundSpecifier? spec))
|
||||
{
|
||||
if (spec != null)
|
||||
_sharedAmbientSoundSystem.SetSound(uid, spec, ambientSoundComponent);
|
||||
}
|
||||
|
||||
if (component.ModulatesPointLight) //SharedPointLightComponent is now being fetched via TryGetLight.
|
||||
if (_pointLightSystem.TryGetLight(uid, out var pointLight))
|
||||
{
|
||||
_pointLightSystem.SetEnabled(uid, isEnabled ? currentGlimmerTier != GlimmerTier.Minimal : false, pointLight);
|
||||
// The light energy and radius are kept updated even when off
|
||||
// to prevent the need to store additional state.
|
||||
//
|
||||
// Note that this doesn't handle edge cases where the
|
||||
// PointLightComponent is removed while the
|
||||
// GlimmerReactiveComponent is still present.
|
||||
_pointLightSystem.SetEnergy(uid, pointLight.Energy + glimmerTierDelta * component.GlimmerToLightEnergyFactor, pointLight);
|
||||
_pointLightSystem.SetRadius(uid, pointLight.Radius + glimmerTierDelta * component.GlimmerToLightRadiusFactor, pointLight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track when the component comes online so it can be given the
|
||||
/// current status of the glimmer tier, if it wasn't around when an
|
||||
/// update went out.
|
||||
/// </summary>
|
||||
private void OnMapInit(EntityUid uid, SharedGlimmerReactiveComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.RequiresApcPower && !HasComp<ApcPowerReceiverComponent>(uid))
|
||||
Logger.Warning($"{ToPrettyString(uid)} had RequiresApcPower set to true but no ApcPowerReceiverComponent was found on init.");
|
||||
|
||||
UpdateEntityState(uid, component, LastGlimmerTier, (int) LastGlimmerTier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the glimmer tier appearance data if the component's removed,
|
||||
/// just in case some objects can temporarily become reactive to the
|
||||
/// glimmer.
|
||||
/// </summary>
|
||||
private void OnComponentRemove(EntityUid uid, SharedGlimmerReactiveComponent component, ComponentRemove args)
|
||||
{
|
||||
UpdateEntityState(uid, component, GlimmerTier.Minimal, -1 * (int) LastGlimmerTier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the Entity has RequiresApcPower set to true, this will force an
|
||||
/// update to the entity's state.
|
||||
/// </summary>
|
||||
private void OnPowerChanged(EntityUid uid, SharedGlimmerReactiveComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
if (component.RequiresApcPower)
|
||||
UpdateEntityState(uid, component, LastGlimmerTier, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable / disable special effects from higher tiers.
|
||||
/// </summary>
|
||||
private void OnTierChanged(EntityUid uid, SharedGlimmerReactiveComponent component, GlimmerTierChangedEvent args)
|
||||
{
|
||||
if (!TryComp<ApcPowerReceiverComponent>(uid, out var receiver))
|
||||
return;
|
||||
|
||||
if (args.CurrentTier >= GlimmerTier.Dangerous)
|
||||
{
|
||||
if (!Transform(uid).Anchored)
|
||||
AnchorOrExplode(uid);
|
||||
|
||||
receiver.PowerDisabled = false;
|
||||
receiver.NeedsPower = false;
|
||||
} else
|
||||
{
|
||||
receiver.NeedsPower = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddShockVerb(EntityUid uid, SharedGlimmerReactiveComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if(!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (!TryComp<ApcPowerReceiverComponent>(uid, out var receiver))
|
||||
return;
|
||||
|
||||
if (receiver.NeedsPower)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
_sharedAudioSystem.PlayPvs(component.ShockNoises, args.User);
|
||||
_electrocutionSystem.TryDoElectrocution(args.User, null, _glimmerSystem.Glimmer / 200, TimeSpan.FromSeconds((float) _glimmerSystem.Glimmer / 100), false);
|
||||
},
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")),
|
||||
Text = Loc.GetString("power-switch-component-toggle-verb"),
|
||||
Priority = -3
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, SharedGlimmerReactiveComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (args.Origin == null)
|
||||
return;
|
||||
|
||||
if (!_random.Prob((float) _glimmerSystem.Glimmer / 1000))
|
||||
return;
|
||||
|
||||
var tier = _glimmerSystem.GetGlimmerTier();
|
||||
if (tier < GlimmerTier.High)
|
||||
return;
|
||||
Beam(uid, args.Origin.Value, tier);
|
||||
}
|
||||
|
||||
private void OnDestroyed(EntityUid uid, SharedGlimmerReactiveComponent component, DestructionEventArgs args)
|
||||
{
|
||||
Spawn("MaterialBluespace1", Transform(uid).Coordinates);
|
||||
|
||||
var tier = _glimmerSystem.GetGlimmerTier();
|
||||
if (tier < GlimmerTier.High)
|
||||
return;
|
||||
|
||||
var totalIntensity = (float) (_glimmerSystem.Glimmer * 2);
|
||||
var slope = (float) (11 - _glimmerSystem.Glimmer / 100);
|
||||
var maxIntensity = 20;
|
||||
|
||||
var removed = (float) _glimmerSystem.Glimmer * _random.NextFloat(0.1f, 0.15f);
|
||||
_glimmerSystem.Glimmer -= (int) removed;
|
||||
BeamRandomNearProber(uid, _glimmerSystem.Glimmer / 350, _glimmerSystem.Glimmer / 50);
|
||||
_explosionSystem.QueueExplosion(uid, "Default", totalIntensity, slope, maxIntensity);
|
||||
}
|
||||
|
||||
private void OnUnanchorAttempt(EntityUid uid, SharedGlimmerReactiveComponent component, UnanchorAttemptEvent args)
|
||||
{
|
||||
if (_glimmerSystem.GetGlimmerTier() >= GlimmerTier.Dangerous)
|
||||
{
|
||||
_sharedAudioSystem.PlayPvs(component.ShockNoises, args.User);
|
||||
_electrocutionSystem.TryDoElectrocution(args.User, null, _glimmerSystem.Glimmer / 200, TimeSpan.FromSeconds((float) _glimmerSystem.Glimmer / 100), false);
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void BeamRandomNearProber(EntityUid prober, int targets, float range = 10f)
|
||||
{
|
||||
List<EntityUid> targetList = new();
|
||||
foreach (var target in _entityLookupSystem.GetComponentsInRange<StatusEffectsComponent>(Transform(prober).Coordinates, range))
|
||||
{
|
||||
if (target.AllowedEffects.Contains("Electrocution"))
|
||||
targetList.Add(target.Owner);
|
||||
}
|
||||
|
||||
foreach(var reactive in _entityLookupSystem.GetComponentsInRange<SharedGlimmerReactiveComponent>(Transform(prober).Coordinates, range))
|
||||
{
|
||||
targetList.Add(reactive.Owner);
|
||||
}
|
||||
|
||||
_random.Shuffle(targetList);
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
if (targets <= 0)
|
||||
return;
|
||||
|
||||
Beam(prober, target, _glimmerSystem.GetGlimmerTier(), false);
|
||||
targets--;
|
||||
}
|
||||
}
|
||||
|
||||
private void Beam(EntityUid prober, EntityUid target, GlimmerTier tier, bool obeyCD = true)
|
||||
{
|
||||
if (obeyCD && BeamCooldown != 0)
|
||||
return;
|
||||
|
||||
if (Deleted(prober) || Deleted(target))
|
||||
return;
|
||||
|
||||
var lxform = Transform(prober);
|
||||
var txform = Transform(target);
|
||||
|
||||
if (!lxform.Coordinates.TryDistance(EntityManager, txform.Coordinates, out var distance))
|
||||
return;
|
||||
if (distance > (float) (_glimmerSystem.Glimmer / 100))
|
||||
return;
|
||||
|
||||
string beamproto;
|
||||
|
||||
switch (tier)
|
||||
{
|
||||
case GlimmerTier.Dangerous:
|
||||
beamproto = "SuperchargedLightning";
|
||||
break;
|
||||
case GlimmerTier.Critical:
|
||||
beamproto = "HyperchargedLightning";
|
||||
break;
|
||||
default:
|
||||
beamproto = "ChargedLightning";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
_lightning.ShootLightning(prober, target, beamproto);
|
||||
BeamCooldown += 3f;
|
||||
}
|
||||
|
||||
private void AnchorOrExplode(EntityUid uid)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
if (xform.Anchored)
|
||||
return;
|
||||
|
||||
if (!TryComp<PhysicsComponent>(uid, out var physics))
|
||||
return;
|
||||
|
||||
var coordinates = xform.Coordinates;
|
||||
var gridUid = xform.GridUid;
|
||||
|
||||
if (_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
{
|
||||
var tileIndices = grid.TileIndicesFor(coordinates);
|
||||
|
||||
if (_anchorableSystem.TileFree(grid, tileIndices, physics.CollisionLayer, physics.CollisionMask) &&
|
||||
_transformSystem.AnchorEntity(uid, xform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wasn't able to get a grid or a free tile, so explode.
|
||||
_destructibleSystem.DestroyEntity(uid);
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent args)
|
||||
{
|
||||
Accumulator = 0;
|
||||
|
||||
// It is necessary that the GlimmerTier is reset to the default
|
||||
// tier on round restart. This system will persist through
|
||||
// restarts, and an undesired event will fire as a result after the
|
||||
// start of the new round, causing modulatable PointLights to have
|
||||
// negative Energy if the tier was higher than Minimal on restart.
|
||||
LastGlimmerTier = GlimmerTier.Minimal;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
Accumulator += frameTime;
|
||||
BeamCooldown = Math.Max(0, BeamCooldown - frameTime);
|
||||
|
||||
if (Accumulator > UpdateFrequency)
|
||||
{
|
||||
var currentGlimmerTier = _glimmerSystem.GetGlimmerTier();
|
||||
|
||||
var reactives = EntityQuery<SharedGlimmerReactiveComponent>();
|
||||
if (currentGlimmerTier != LastGlimmerTier) {
|
||||
var glimmerTierDelta = (int) currentGlimmerTier - (int) LastGlimmerTier;
|
||||
var ev = new GlimmerTierChangedEvent(LastGlimmerTier, currentGlimmerTier, glimmerTierDelta);
|
||||
|
||||
foreach (var reactive in reactives)
|
||||
{
|
||||
UpdateEntityState(reactive.Owner, reactive, currentGlimmerTier, glimmerTierDelta);
|
||||
RaiseLocalEvent(reactive.Owner, ev);
|
||||
}
|
||||
|
||||
LastGlimmerTier = currentGlimmerTier;
|
||||
}
|
||||
if (currentGlimmerTier == GlimmerTier.Critical)
|
||||
{
|
||||
_ghostSystem.MakeVisible(true);
|
||||
_revenantSystem.MakeVisible(true);
|
||||
GhostsVisible = true;
|
||||
foreach (var reactive in reactives)
|
||||
{
|
||||
BeamRandomNearProber(reactive.Owner, 1, 12);
|
||||
}
|
||||
} else if (GhostsVisible == true)
|
||||
{
|
||||
_ghostSystem.MakeVisible(false);
|
||||
_revenantSystem.MakeVisible(false);
|
||||
GhostsVisible = false;
|
||||
}
|
||||
Accumulator = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is fired when the broader glimmer tier has changed,
|
||||
/// not on every single adjustment to the glimmer count.
|
||||
///
|
||||
/// <see cref="GlimmerSystem.GetGlimmerTier"/> has the exact
|
||||
/// values corresponding to tiers.
|
||||
/// </summary>
|
||||
public class GlimmerTierChangedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// What was the last glimmer tier before this event fired?
|
||||
/// </summary>
|
||||
public readonly GlimmerTier LastTier;
|
||||
|
||||
/// <summary>
|
||||
/// What is the current glimmer tier?
|
||||
/// </summary>
|
||||
public readonly GlimmerTier CurrentTier;
|
||||
|
||||
/// <summary>
|
||||
/// What is the change in tiers between the last and current tier?
|
||||
/// </summary>
|
||||
public readonly int TierDelta;
|
||||
|
||||
public GlimmerTierChangedEvent(GlimmerTier lastTier, GlimmerTier currentTier, int tierDelta)
|
||||
{
|
||||
LastTier = lastTier;
|
||||
CurrentTier = currentTier;
|
||||
TierDelta = tierDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Configuration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Server.Psionics.Glimmer
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the passive reduction of glimmer.
|
||||
/// </summary>
|
||||
public sealed class PassiveGlimmerReductionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly GlimmerMonitorCartridgeSystem _cartridgeSys = default!;
|
||||
|
||||
/// List of glimmer values spaced by minute.
|
||||
public List<int> GlimmerValues = new();
|
||||
|
||||
public TimeSpan TargetUpdatePeriod = TimeSpan.FromSeconds(6);
|
||||
|
||||
private int _updateIncrementor;
|
||||
public TimeSpan NextUpdateTime = default!;
|
||||
public TimeSpan LastUpdateTime = default!;
|
||||
|
||||
private float _glimmerLostPerSecond;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
|
||||
_cfg.OnValueChanged(CCVars.GlimmerLostPerSecond, UpdatePassiveGlimmer, true);
|
||||
}
|
||||
|
||||
private void OnRoundRestartCleanup(RoundRestartCleanupEvent args)
|
||||
{
|
||||
GlimmerValues.Clear();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
if (NextUpdateTime > curTime)
|
||||
return;
|
||||
|
||||
|
||||
var delta = curTime - LastUpdateTime;
|
||||
var maxGlimmerLost = (int) Math.Round(delta.TotalSeconds * _glimmerLostPerSecond);
|
||||
|
||||
// It used to be 75% to lose one glimmer per ten seconds, but now it's 50% per six seconds.
|
||||
// The probability is exactly the same over the same span of time. (0.25 ^ 3 == 0.5 ^ 6)
|
||||
// This math is just easier to do for pausing's sake.
|
||||
var actualGlimmerLost = _random.Next(0, 1 + maxGlimmerLost);
|
||||
|
||||
_glimmerSystem.Glimmer -= actualGlimmerLost;
|
||||
|
||||
_updateIncrementor++;
|
||||
|
||||
// Since we normally update every 6 seconds, this works out to a minute.
|
||||
if (_updateIncrementor == 10)
|
||||
{
|
||||
GlimmerValues.Add(_glimmerSystem.Glimmer);
|
||||
|
||||
_updateIncrementor = 0;
|
||||
}
|
||||
|
||||
NextUpdateTime = curTime + TargetUpdatePeriod;
|
||||
LastUpdateTime = curTime;
|
||||
}
|
||||
|
||||
private void UpdatePassiveGlimmer(float value) => _glimmerLostPerSecond = value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
namespace Content.Server.Psionics.Glimmer
|
||||
{
|
||||
[RegisterComponent]
|
||||
/// <summary>
|
||||
/// Adds to glimmer at regular intervals. We'll use it for glimmer drains too when we get there.
|
||||
/// </summary>
|
||||
public sealed partial class GlimmerSourceComponent : Component
|
||||
{
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[DataField("active")]
|
||||
public bool Active = true;
|
||||
|
||||
/// <summary>
|
||||
/// Since glimmer is an int, we'll do it like this.
|
||||
/// </summary>
|
||||
[DataField("secondsPerGlimmer")]
|
||||
public float SecondsPerGlimmer = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// True if it produces glimmer, false if it subtracts it.
|
||||
/// </summary>
|
||||
[DataField("addToGlimmer")]
|
||||
public bool AddToGlimmer = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.Psionics.Glimmer
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles structures which add/subtract glimmer.
|
||||
/// </summary>
|
||||
public sealed class GlimmerStructuresSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AnomalyVesselComponent, PowerChangedEvent>(OnAnomalyVesselPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<GlimmerSourceComponent, AnomalyPulseEvent>(OnAnomalyPulse);
|
||||
SubscribeLocalEvent<GlimmerSourceComponent, AnomalySupercriticalEvent>(OnAnomalySupercritical);
|
||||
}
|
||||
|
||||
private void OnAnomalyVesselPowerChanged(EntityUid uid, AnomalyVesselComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
if (TryComp<GlimmerSourceComponent>(component.Anomaly, out var glimmerSource))
|
||||
glimmerSource.Active = args.Powered;
|
||||
}
|
||||
|
||||
private void OnAnomalyPulse(EntityUid uid, GlimmerSourceComponent component, ref AnomalyPulseEvent args)
|
||||
{
|
||||
// Anomalies are meant to have GlimmerSource on them with the
|
||||
// active flag set to false, as they will be set to actively
|
||||
// generate glimmer when scanned to an anomaly vessel for
|
||||
// harvesting research points.
|
||||
//
|
||||
// It is not a bug that glimmer increases on pulse or
|
||||
// supercritical with an inactive glimmer source.
|
||||
//
|
||||
// However, this will need to be reworked if a distinction
|
||||
// needs to be made in the future. I suggest a GlimmerAnomaly
|
||||
// component.
|
||||
|
||||
if (TryComp<AnomalyComponent>(uid, out var anomaly))
|
||||
_glimmerSystem.Glimmer += (int) (5f * anomaly.Severity);
|
||||
}
|
||||
|
||||
private void OnAnomalySupercritical(EntityUid uid, GlimmerSourceComponent component, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
_glimmerSystem.Glimmer += 100;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
foreach (var source in EntityQuery<GlimmerSourceComponent>())
|
||||
{
|
||||
if (!_powerReceiverSystem.IsPowered(source.Owner))
|
||||
continue;
|
||||
|
||||
if (!source.Active)
|
||||
continue;
|
||||
|
||||
source.Accumulator += frameTime;
|
||||
|
||||
if (source.Accumulator > source.SecondsPerGlimmer)
|
||||
{
|
||||
source.Accumulator -= source.SecondsPerGlimmer;
|
||||
if (source.AddToGlimmer)
|
||||
{
|
||||
_glimmerSystem.Glimmer++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_glimmerSystem.Glimmer--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Vehicle.Components;
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using Content.Shared.Eye;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Psionics
|
||||
{
|
||||
public sealed class PsionicInvisibilitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
|
||||
[Dependency] private readonly PsionicInvisibilityPowerSystem _invisSystem = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
/// Masking
|
||||
SubscribeLocalEvent<PotentialPsionicComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PsionicInsulationComponent, ComponentInit>(OnInsulInit);
|
||||
SubscribeLocalEvent<PsionicInsulationComponent, ComponentShutdown>(OnInsulShutdown);
|
||||
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnEyeInit);
|
||||
|
||||
/// Layer
|
||||
SubscribeLocalEvent<PsionicallyInvisibleComponent, ComponentInit>(OnInvisInit);
|
||||
SubscribeLocalEvent<PsionicallyInvisibleComponent, ComponentShutdown>(OnInvisShutdown);
|
||||
|
||||
// PVS Stuff
|
||||
SubscribeLocalEvent<PsionicallyInvisibleComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||
SubscribeLocalEvent<PsionicallyInvisibleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PotentialPsionicComponent component, ComponentInit args)
|
||||
{
|
||||
SetCanSeePsionicInvisiblity(uid, false);
|
||||
}
|
||||
|
||||
private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, ComponentInit args)
|
||||
{
|
||||
if (!HasComp<PotentialPsionicComponent>(uid))
|
||||
return;
|
||||
|
||||
if (HasComp<PsionicInvisibilityUsedComponent>(uid))
|
||||
_invisSystem.ToggleInvisibility(uid);
|
||||
|
||||
if (_npcFactonSystem.ContainsFaction(uid, "PsionicInterloper"))
|
||||
{
|
||||
component.SuppressedFactions.Add("PsionicInterloper");
|
||||
_npcFactonSystem.RemoveFaction(uid, "PsionicInterloper");
|
||||
}
|
||||
|
||||
if (_npcFactonSystem.ContainsFaction(uid, "GlimmerMonster"))
|
||||
{
|
||||
component.SuppressedFactions.Add("GlimmerMonster");
|
||||
_npcFactonSystem.RemoveFaction(uid, "GlimmerMonster");
|
||||
}
|
||||
|
||||
SetCanSeePsionicInvisiblity(uid, true);
|
||||
}
|
||||
|
||||
private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!HasComp<PotentialPsionicComponent>(uid))
|
||||
return;
|
||||
|
||||
SetCanSeePsionicInvisiblity(uid, false);
|
||||
|
||||
if (!HasComp<PsionicComponent>(uid))
|
||||
{
|
||||
component.SuppressedFactions.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var faction in component.SuppressedFactions)
|
||||
{
|
||||
_npcFactonSystem.AddFaction(uid, faction);
|
||||
}
|
||||
component.SuppressedFactions.Clear();
|
||||
}
|
||||
|
||||
private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentInit args)
|
||||
{
|
||||
var visibility = EntityManager.EnsureComponent<VisibilityComponent>(uid);
|
||||
|
||||
_visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.PsionicInvisibility, false);
|
||||
_visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(visibility);
|
||||
|
||||
SetCanSeePsionicInvisiblity(uid, true);
|
||||
}
|
||||
|
||||
|
||||
private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp<VisibilityComponent>(uid, out var visibility))
|
||||
{
|
||||
_visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.PsionicInvisibility, false);
|
||||
_visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(visibility);
|
||||
}
|
||||
if (HasComp<PotentialPsionicComponent>(uid) && !HasComp<PsionicInsulationComponent>(uid))
|
||||
SetCanSeePsionicInvisiblity(uid, false);
|
||||
}
|
||||
|
||||
private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args)
|
||||
{
|
||||
if (HasComp<PotentialPsionicComponent>(uid) || HasComp<VehicleComponent>(uid))
|
||||
return;
|
||||
|
||||
//SetCanSeePsionicInvisiblity(uid, true); //JJ Comment - Not allowed to modifies .yml on spawn any longer. See UninitializedSaveTest.
|
||||
}
|
||||
private void OnEntInserted(EntityUid uid, PsionicallyInvisibleComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
DirtyEntity(args.Entity);
|
||||
}
|
||||
|
||||
private void OnEntRemoved(EntityUid uid, PsionicallyInvisibleComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
DirtyEntity(args.Entity);
|
||||
}
|
||||
|
||||
public void SetCanSeePsionicInvisiblity(EntityUid uid, bool set)
|
||||
{
|
||||
if (set == true)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out EyeComponent? eye))
|
||||
{
|
||||
_eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.PsionicInvisibility, eye);
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out EyeComponent? eye))
|
||||
{
|
||||
//_eye.SetVisibilityMask(uid, eye.VisibilityMask & (int) VisibilityFlags.PsionicInvisibility, eye);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Psionics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class PsionicInvisibleContactsComponent : Component
|
||||
{
|
||||
[DataField("whitelist", required: true)]
|
||||
public EntityWhitelist Whitelist = default!;
|
||||
|
||||
/// <summary>
|
||||
/// This tracks how many valid entities are being contacted,
|
||||
/// so when you stop touching one, you don't immediately lose invisibility.
|
||||
/// </summary>
|
||||
[DataField("stages")]
|
||||
public int Stages = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using Content.Shared.Stealth;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Psionics
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows an entity to become psionically invisible when touching certain entities.
|
||||
/// </summary>
|
||||
public sealed class PsionicInvisibleContactsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedStealthSystem _stealth = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PsionicInvisibleContactsComponent, StartCollideEvent>(OnEntityEnter);
|
||||
SubscribeLocalEvent<PsionicInvisibleContactsComponent, EndCollideEvent>(OnEntityExit);
|
||||
|
||||
UpdatesAfter.Add(typeof(SharedPhysicsSystem));
|
||||
}
|
||||
|
||||
private void OnEntityEnter(EntityUid uid, PsionicInvisibleContactsComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
var otherUid = args.OtherEntity;
|
||||
var ourEntity = args.OurEntity;
|
||||
|
||||
if (!component.Whitelist.IsValid(otherUid))
|
||||
return;
|
||||
|
||||
// This will go up twice per web hit, since webs also have a flammable fixture.
|
||||
// It goes down twice per web exit, so everything's fine.
|
||||
++component.Stages;
|
||||
|
||||
if (HasComp<PsionicallyInvisibleComponent>(ourEntity))
|
||||
return;
|
||||
|
||||
EnsureComp<PsionicallyInvisibleComponent>(ourEntity);
|
||||
var stealth = EnsureComp<StealthComponent>(ourEntity);
|
||||
_stealth.SetVisibility(ourEntity, 0.66f, stealth);
|
||||
}
|
||||
|
||||
private void OnEntityExit(EntityUid uid, PsionicInvisibleContactsComponent component, ref EndCollideEvent args)
|
||||
{
|
||||
var otherUid = args.OtherEntity;
|
||||
var ourEntity = args.OurEntity;
|
||||
|
||||
if (!component.Whitelist.IsValid(otherUid))
|
||||
return;
|
||||
|
||||
if (!HasComp<PsionicallyInvisibleComponent>(ourEntity))
|
||||
return;
|
||||
|
||||
if (--component.Stages > 0)
|
||||
return;
|
||||
|
||||
RemComp<PsionicallyInvisibleComponent>(ourEntity);
|
||||
var stealth = EnsureComp<StealthComponent>(ourEntity);
|
||||
// Just to be sure...
|
||||
_stealth.SetVisibility(ourEntity, 1f, stealth);
|
||||
|
||||
RemComp<StealthComponent>(ourEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Content.Server.Psionics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class PsionicallyInvisibleComponent : Component
|
||||
{}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
namespace Content.Server.Psionics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class PotentialPsionicComponent : Component
|
||||
{
|
||||
[DataField("chance")]
|
||||
public float Chance = 0.04f;
|
||||
|
||||
/// <summary>
|
||||
/// YORO (you only reroll once)
|
||||
/// </summary>
|
||||
public bool Rerolled = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace Content.Server.Psionics
|
||||
{
|
||||
/// <summary>
|
||||
/// Will open the 'accept psionics' UI when a player attaches.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PsionicAwaitingPlayerComponent : Component
|
||||
{}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
namespace Content.Server.Psionics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class PsionicBonusChanceComponent : Component
|
||||
{
|
||||
[DataField("multiplier")]
|
||||
public float Multiplier = 1f;
|
||||
[DataField("flatBonus")]
|
||||
public float FlatBonus = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we should warn the user they are about to receive psionics.
|
||||
/// It's here because AddComponentSpecial can't overwrite a component, and this is very role dependent.
|
||||
/// </summary>
|
||||
[DataField("warn")]
|
||||
public bool Warn = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Server.Psionics;
|
||||
|
||||
[AdminCommand(AdminFlags.Logs)]
|
||||
public sealed class ListPsionicsCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lspsionics";
|
||||
public string Description => Loc.GetString("command-lspsionic-description");
|
||||
public string Help => Loc.GetString("command-lspsionic-help");
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
SharedActionsSystem actions = default!;
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery<ActorComponent, MobStateComponent, PsionicComponent, MetaDataComponent>()){
|
||||
// filter out xenos, etc, with innate telepathy
|
||||
actions.TryGetActionData( psionic.PsionicAbility, out var actionData );
|
||||
if (actionData == null || actionData.ToString() == null)
|
||||
return;
|
||||
|
||||
var psiPowerName = actionData.ToString();
|
||||
if (psiPowerName == null)
|
||||
return;
|
||||
|
||||
shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + Loc.GetString(psiPowerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Psionics
|
||||
{
|
||||
public sealed class PsionicsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
|
||||
[Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Unfortunately, since spawning as a normal role and anything else is so different,
|
||||
/// this is the only way to unify them, for now at least.
|
||||
/// </summary>
|
||||
Queue<(PotentialPsionicComponent component, EntityUid uid)> _rollers = new();
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
foreach (var roller in _rollers)
|
||||
{
|
||||
RollPsionics(roller.uid, roller.component, false);
|
||||
}
|
||||
_rollers.Clear();
|
||||
}
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PotentialPsionicComponent, MapInitEvent>(OnStartup);
|
||||
SubscribeLocalEvent<AntiPsionicWeaponComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
SubscribeLocalEvent<AntiPsionicWeaponComponent, StaminaMeleeHitEvent>(OnStamHit);
|
||||
|
||||
SubscribeLocalEvent<PotentialPsionicComponent, MobStateChangedEvent>(OnDeathGasp);
|
||||
|
||||
SubscribeLocalEvent<PsionicComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PsionicComponent, ComponentRemove>(OnRemove);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, PotentialPsionicComponent component, MapInitEvent args)
|
||||
{
|
||||
if (HasComp<PsionicComponent>(uid))
|
||||
return;
|
||||
|
||||
_rollers.Enqueue((component, uid));
|
||||
}
|
||||
|
||||
private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args)
|
||||
{
|
||||
foreach (var entity in args.HitEntities)
|
||||
{
|
||||
if (HasComp<PsionicComponent>(entity))
|
||||
{
|
||||
SoundSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(entity), entity);
|
||||
args.ModifiersList.Add(component.Modifiers);
|
||||
if (_random.Prob(component.DisableChance))
|
||||
_statusEffects.TryAddStatusEffect(entity, "PsionicsDisabled", TimeSpan.FromSeconds(10), true, "PsionicsDisabled");
|
||||
}
|
||||
|
||||
if (TryComp<MindSwappedComponent>(entity, out var swapped))
|
||||
{
|
||||
_mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Punish && HasComp<PotentialPsionicComponent>(entity) && !HasComp<PsionicComponent>(entity) && _random.Prob(0.5f))
|
||||
_electrocutionSystem.TryDoElectrocution(args.User, null, 20, TimeSpan.FromSeconds(5), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeathGasp(EntityUid uid, PotentialPsionicComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead)
|
||||
return;
|
||||
|
||||
string message;
|
||||
|
||||
switch (_glimmerSystem.GetGlimmerTier())
|
||||
{
|
||||
case GlimmerTier.Critical:
|
||||
message = Loc.GetString("death-gasp-high", ("ent", Identity.Entity(uid, EntityManager)));
|
||||
break;
|
||||
case GlimmerTier.Dangerous:
|
||||
message = Loc.GetString("death-gasp-medium", ("ent",Identity.Entity(uid, EntityManager)));
|
||||
break;
|
||||
default:
|
||||
message = Loc.GetString("death-gasp-normal", ("ent", Identity.Entity(uid, EntityManager)));
|
||||
break;
|
||||
}
|
||||
//Was force, changed to ignoreActionBlocker.
|
||||
_chat.TrySendInGameICMessage(uid, message, InGameICChatType.Emote, false, ignoreActionBlocker:true);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args)
|
||||
{
|
||||
if (!component.Removable)
|
||||
return;
|
||||
|
||||
if (!TryComp<NpcFactionMemberComponent>(uid, out var factions))
|
||||
return;
|
||||
|
||||
if (_npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions))
|
||||
return;
|
||||
|
||||
_npcFactonSystem.AddFaction(uid, "PsionicInterloper");
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args)
|
||||
{
|
||||
if (!TryComp<NpcFactionMemberComponent>(uid, out var factions))
|
||||
return;
|
||||
|
||||
_npcFactonSystem.RemoveFaction(uid, "PsionicInterloper");
|
||||
}
|
||||
|
||||
private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, StaminaMeleeHitEvent args)
|
||||
{
|
||||
var bonus = false;
|
||||
foreach (var stam in args.HitList)
|
||||
{
|
||||
if (HasComp<PsionicComponent>(stam.Entity))
|
||||
bonus = true;
|
||||
}
|
||||
|
||||
if (!bonus)
|
||||
return;
|
||||
|
||||
|
||||
args.FlatModifier += component.PsychicStaminaDamage;
|
||||
}
|
||||
|
||||
public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, bool applyGlimmer = true, float multiplier = 1f)
|
||||
{
|
||||
if (HasComp<PsionicComponent>(uid))
|
||||
return;
|
||||
|
||||
if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled))
|
||||
return;
|
||||
|
||||
var chance = component.Chance;
|
||||
var warn = true;
|
||||
if (TryComp<PsionicBonusChanceComponent>(uid, out var bonus))
|
||||
{
|
||||
chance *= bonus.Multiplier;
|
||||
chance += bonus.FlatBonus;
|
||||
warn = bonus.Warn;
|
||||
}
|
||||
|
||||
if (applyGlimmer)
|
||||
chance += ((float) _glimmerSystem.Glimmer / 1000);
|
||||
|
||||
chance *= multiplier;
|
||||
|
||||
chance = Math.Clamp(chance, 0, 1);
|
||||
|
||||
if (_random.Prob(chance))
|
||||
_psionicAbilitiesSystem.AddPsionics(uid, warn);
|
||||
}
|
||||
|
||||
public void RerollPsionics(EntityUid uid, PotentialPsionicComponent? psionic = null, float bonusMuliplier = 1f)
|
||||
{
|
||||
if (!Resolve(uid, ref psionic, false))
|
||||
return;
|
||||
|
||||
if (psionic.Rerolled)
|
||||
return;
|
||||
|
||||
RollPsionics(uid, psionic, multiplier: bonusMuliplier);
|
||||
psionic.Rerolled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(FreeProberRule))]
|
||||
public sealed partial class FreeProberRuleComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
namespace Content.Server.Psionics.Glimmer;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class GlimmerEventComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum glimmer value for event to be eligible. (Should be 100 at lowest.)
|
||||
/// </summary>
|
||||
[DataField("minimumGlimmer")]
|
||||
public int MinimumGlimmer = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum glimmer value for event to be eligible. (Remember 1000 is max glimmer period.)
|
||||
/// </summary>
|
||||
[DataField("maximumGlimmer")]
|
||||
public int MaximumGlimmer = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Will be used for _random.Next and subtracted from glimmer.
|
||||
/// Lower bound.
|
||||
/// </summary>
|
||||
[DataField("glimmerBurnLower")]
|
||||
public int GlimmerBurnLower = 25;
|
||||
|
||||
/// <summary>
|
||||
/// Will be used for _random.Next and subtracted from glimmer.
|
||||
/// Upper bound.
|
||||
/// </summary>
|
||||
[DataField("glimmerBurnUpper")]
|
||||
public int GlimmerBurnUpper = 70;
|
||||
|
||||
[DataField("report")]
|
||||
public string SophicReport = "glimmer-event-report-generic";
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(GlimmerRandomSentienceRule))]
|
||||
public sealed partial class GlimmerRandomSentienceRuleComponent : Component
|
||||
{
|
||||
[DataField("maxMakeSentient")]
|
||||
public int MaxMakeSentient = 4;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(GlimmerRevenantRule))]
|
||||
public sealed partial class GlimmerRevenantRuleComponent : Component
|
||||
{
|
||||
[DataField("prototype")]
|
||||
public string RevenantPrototype = "MobRevenant";
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(GlimmerWispRule))]
|
||||
public sealed partial class GlimmerWispRuleComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MassMindSwapRule))]
|
||||
public sealed partial class MassMindSwapRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The mind swap is only temporary if true.
|
||||
/// </summary>
|
||||
[DataField("isTemporary")]
|
||||
public bool IsTemporary;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MidRoundAntagRule))]
|
||||
public sealed partial class MidRoundAntagRuleComponent : Component
|
||||
{
|
||||
[DataField("antags")]
|
||||
public IReadOnlyList<string> MidRoundAntags = new[]
|
||||
{
|
||||
"SpawnPointGhostRatKing",
|
||||
"SpawnPointGhostVampSpider",
|
||||
"SpawnPointGhostFugitive",
|
||||
"MobEvilTwinSpawn"
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Content.Server.StationEvents
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class MidRoundAntagSpawnLocationComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MundaneDischargeRule))]
|
||||
public sealed partial class MundaneDischargeRuleComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(NoosphericFryRule))]
|
||||
public sealed partial class NoosphericFryRuleComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(NoosphericStormRule))]
|
||||
public sealed partial class NoosphericStormRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How many potential psionics should be awakened at most.
|
||||
/// </summary>
|
||||
[DataField("maxAwaken")]
|
||||
public int MaxAwaken = 3;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[DataField("baseGlimmerAddMin")]
|
||||
public int BaseGlimmerAddMin = 65;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[DataField("baseGlimmerAddMax")]
|
||||
public int BaseGlimmerAddMax = 85;
|
||||
|
||||
/// <summary>
|
||||
/// Multiply the EventSeverityModifier by this to determine how much extra glimmer to add.
|
||||
/// </summary>
|
||||
[DataField("glimmerSeverityCoefficient")]
|
||||
public float GlimmerSeverityCoefficient = 0.25f;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(NoosphericZapRule))]
|
||||
public sealed partial class NoosphericZapRuleComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(PsionicCatGotYourTongueRule))]
|
||||
public sealed partial class PsionicCatGotYourTongueRuleComponent : Component
|
||||
{
|
||||
[DataField("minDuration")]
|
||||
public TimeSpan MinDuration = TimeSpan.FromSeconds(20);
|
||||
|
||||
[DataField("maxDuration")]
|
||||
public TimeSpan MaxDuration = TimeSpan.FromSeconds(80);
|
||||
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg");
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
internal sealed class FreeProberRule : StationEventSystem<FreeProberRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly AnchorableSystem _anchorable = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
|
||||
private static readonly string ProberPrototype = "GlimmerProber";
|
||||
private static readonly int SpawnDirections = 4;
|
||||
|
||||
protected override void Started(EntityUid uid, FreeProberRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<EntityUid> PossibleSpawns = new();
|
||||
|
||||
var query = EntityQueryEnumerator<GlimmerSourceComponent>();
|
||||
while (query.MoveNext(out var glimmerSource, out var glimmerSourceComponent))
|
||||
{
|
||||
if (glimmerSourceComponent.AddToGlimmer && glimmerSourceComponent.Active)
|
||||
{
|
||||
PossibleSpawns.Add(glimmerSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (PossibleSpawns.Count == 0 || _glimmerSystem.Glimmer >= 500 || _robustRandom.Prob(0.25f))
|
||||
{
|
||||
var queryBattery = EntityQueryEnumerator<PowerNetworkBatteryComponent>();
|
||||
while (query.MoveNext(out var battery, out var _))
|
||||
{
|
||||
PossibleSpawns.Add(battery);
|
||||
}
|
||||
}
|
||||
|
||||
if (PossibleSpawns.Count > 0)
|
||||
{
|
||||
_robustRandom.Shuffle(PossibleSpawns);
|
||||
|
||||
foreach (var source in PossibleSpawns)
|
||||
{
|
||||
var xform = Transform(source);
|
||||
|
||||
if (_stationSystem.GetOwningStation(source, xform) == null)
|
||||
continue;
|
||||
|
||||
var coordinates = xform.Coordinates;
|
||||
var gridUid = xform.GridUid;
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
continue;
|
||||
|
||||
var tileIndices = grid.TileIndicesFor(coordinates);
|
||||
|
||||
for (var i = 0; i < SpawnDirections; i++)
|
||||
{
|
||||
var direction = (DirectionFlag) (1 << i);
|
||||
var offsetIndices = tileIndices.Offset(direction.AsDir());
|
||||
|
||||
// This doesn't check against the prober's mask/layer, because it hasn't spawned yet...
|
||||
if (!_anchorable.TileFree(grid, offsetIndices))
|
||||
continue;
|
||||
|
||||
Spawn(ProberPrototype, grid.GridTileToLocal(offsetIndices));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public sealed class GlimmerEventSystem : StationEventSystem<GlimmerEventComponent>
|
||||
{
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
|
||||
protected override void Ended(EntityUid uid, GlimmerEventComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
{
|
||||
base.Ended(uid, component, gameRule, args);
|
||||
|
||||
var glimmerBurned = RobustRandom.Next(component.GlimmerBurnLower, component.GlimmerBurnUpper);
|
||||
_glimmerSystem.Glimmer -= glimmerBurned;
|
||||
|
||||
var reportEv = new GlimmerEventEndedEvent(component.SophicReport, glimmerBurned);
|
||||
RaiseLocalEvent(reportEv);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GlimmerEventEndedEvent : EntityEventArgs
|
||||
{
|
||||
public string Message = "";
|
||||
public int GlimmerBurned = 0;
|
||||
|
||||
public GlimmerEventEndedEvent(string message, int glimmerBurned)
|
||||
{
|
||||
Message = message;
|
||||
GlimmerBurned = glimmerBurned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Glimmer version of the (removed) random sentience event
|
||||
/// </summary>
|
||||
internal sealed class GlimmerRandomSentienceRule : StationEventSystem<GlimmerRandomSentienceRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, GlimmerRandomSentienceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<EntityUid> targetList = new();
|
||||
|
||||
var query = EntityQueryEnumerator<SentienceTargetComponent>();
|
||||
while (query.MoveNext(out var target, out _))
|
||||
{
|
||||
if (HasComp<GhostTakeoverAvailableComponent>(target))
|
||||
continue;
|
||||
|
||||
if (!_mobStateSystem.IsAlive(target))
|
||||
continue;
|
||||
|
||||
targetList.Add(target);
|
||||
}
|
||||
|
||||
RobustRandom.Shuffle(targetList);
|
||||
|
||||
var toMakeSentient = RobustRandom.Next(1, component.MaxMakeSentient);
|
||||
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
if (toMakeSentient-- == 0)
|
||||
break;
|
||||
|
||||
EntityManager.RemoveComponent<SentienceTargetComponent>(target);
|
||||
MetaData(target).EntityName = Loc.GetString("glimmer-event-awakened-prefix", ("entity", target));
|
||||
var comp = EntityManager.EnsureComponent<GhostRoleComponent>(target);
|
||||
comp.RoleName = EntityManager.GetComponent<MetaDataComponent>(target).EntityName;
|
||||
comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName));
|
||||
RemComp<ReplacementAccentComponent>(target);
|
||||
RemComp<MonkeyAccentComponent>(target);
|
||||
EnsureComp<PotentialPsionicComponent>(target);
|
||||
EnsureComp<GhostTakeoverAvailableComponent>(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using Robust.Shared.Random;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
internal sealed class GlimmerRevenantRule : StationEventSystem<GlimmerRevenantRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, GlimmerRevenantRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<EntityUid> glimmerSources = new();
|
||||
|
||||
var query = EntityQueryEnumerator<GlimmerSourceComponent>();
|
||||
while (query.MoveNext(out var source, out _))
|
||||
{
|
||||
glimmerSources.Add(source);
|
||||
}
|
||||
|
||||
if (glimmerSources.Count == 0)
|
||||
return;
|
||||
|
||||
var coords = Transform(_random.Pick(glimmerSources)).Coordinates;
|
||||
|
||||
Sawmill.Info($"Spawning revenant at {coords}");
|
||||
EntityManager.SpawnEntity(component.RevenantPrototype, coords);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using System.Linq;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
internal sealed class GlimmerWispRule : StationEventSystem<GlimmerWispRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
|
||||
private static readonly string WispPrototype = "MobGlimmerWisp";
|
||||
|
||||
protected override void Started(EntityUid uid, GlimmerWispRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var glimmerSources = EntityManager.EntityQuery<GlimmerSourceComponent, TransformComponent>().ToList();
|
||||
var normalSpawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
||||
var hiddenSpawnLocations = EntityManager.EntityQuery<MidRoundAntagSpawnLocationComponent, TransformComponent>().ToList();
|
||||
|
||||
var baseCount = Math.Max(1, EntityManager.EntityQuery<PsionicComponent, NpcFactionMemberComponent>().Count() / 10);
|
||||
int multiplier = Math.Max(1, (int) _glimmerSystem.GetGlimmerTier() - 2);
|
||||
|
||||
var total = baseCount * multiplier;
|
||||
|
||||
int i = 0;
|
||||
while (i < total)
|
||||
{
|
||||
if (glimmerSources.Count != 0 && _robustRandom.Prob(0.4f))
|
||||
{
|
||||
EntityManager.SpawnEntity(WispPrototype, _robustRandom.Pick(glimmerSources).Item2.Coordinates);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalSpawnLocations.Count != 0)
|
||||
{
|
||||
EntityManager.SpawnEntity(WispPrototype, _robustRandom.Pick(normalSpawnLocations).Item2.Coordinates);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hiddenSpawnLocations.Count != 0)
|
||||
{
|
||||
EntityManager.SpawnEntity(WispPrototype, _robustRandom.Pick(hiddenSpawnLocations).Item2.Coordinates);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Forces a mind swap on all non-insulated potential psionic entities.
|
||||
/// </summary>
|
||||
internal sealed class MassMindSwapRule : StationEventSystem<MassMindSwapRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly MindSwapPowerSystem _mindSwap = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, MassMindSwapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<EntityUid> psionicPool = new();
|
||||
List<EntityUid> psionicActors = new();
|
||||
|
||||
var query = EntityQueryEnumerator<PotentialPsionicComponent, MobStateComponent>();
|
||||
while (query.MoveNext(out var psion, out _, out _))
|
||||
{
|
||||
if (_mobStateSystem.IsAlive(psion) && !HasComp<PsionicInsulationComponent>(psion))
|
||||
{
|
||||
psionicPool.Add(psion);
|
||||
|
||||
if (HasComp<ActorComponent>(psion))
|
||||
{
|
||||
// This is so we don't bother mindswapping NPCs with NPCs.
|
||||
psionicActors.Add(psion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle the list of candidates.
|
||||
_random.Shuffle(psionicPool);
|
||||
|
||||
foreach (var actor in psionicActors)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (psionicPool.Count == 0)
|
||||
// We ran out of candidates. Exit early.
|
||||
return;
|
||||
|
||||
// Pop the last entry off.
|
||||
var other = psionicPool[^1];
|
||||
psionicPool.RemoveAt(psionicPool.Count - 1);
|
||||
|
||||
if (other == actor)
|
||||
// Don't be yourself. Find someone else.
|
||||
continue;
|
||||
|
||||
// A valid swap target has been found.
|
||||
// Remove this actor from the pool of swap candidates before they go.
|
||||
psionicPool.Remove(actor);
|
||||
|
||||
// Do the swap.
|
||||
_mindSwap.Swap(actor, other);
|
||||
if (!component.IsTemporary)
|
||||
{
|
||||
_mindSwap.GetTrapped(actor);
|
||||
_mindSwap.GetTrapped(other);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System.Linq;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
internal sealed class MidRoundAntagRule : StationEventSystem<MidRoundAntagRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, MidRoundAntagRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var spawnLocations = EntityManager.EntityQuery<MidRoundAntagSpawnLocationComponent, TransformComponent>().ToList();
|
||||
var backupSpawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
||||
|
||||
TransformComponent? spawn = new();
|
||||
|
||||
if (spawnLocations.Count > 0)
|
||||
{
|
||||
var spawnLoc = _robustRandom.Pick(spawnLocations);
|
||||
spawn = spawnLoc.Item2;
|
||||
} else if (backupSpawnLocations.Count > 0)
|
||||
{
|
||||
var spawnLoc = _robustRandom.Pick(backupSpawnLocations);
|
||||
spawn = spawnLoc.Item2;
|
||||
}
|
||||
|
||||
if (spawn == null)
|
||||
return;
|
||||
|
||||
if (spawn.GridUid == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Spawn(_robustRandom.Pick(component.MidRoundAntags), spawn.Coordinates);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Early anti-ramp event.
|
||||
/// </summary>
|
||||
internal sealed class MundaneDischargeRule : StationEventSystem<MundaneDischargeRuleComponent>
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Fries tinfoil hats and cages
|
||||
/// </summary>
|
||||
internal sealed class NoosphericFryRule : StationEventSystem<NoosphericFryRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
|
||||
[Dependency] private readonly GlimmerReactiveSystem _glimmerReactiveSystem = default!;
|
||||
[Dependency] private readonly AnchorableSystem _anchorableSystem = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, NoosphericFryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<(EntityUid wearer, TinfoilHatComponent worn)> psionicList = new();
|
||||
|
||||
var query = EntityQueryEnumerator<PsionicInsulationComponent, MobStateComponent>();
|
||||
while (query.MoveNext(out var psion, out _, out _))
|
||||
{
|
||||
if (!_mobStateSystem.IsAlive(psion))
|
||||
continue;
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(psion, "head", out var headItem))
|
||||
continue;
|
||||
|
||||
if (!TryComp<TinfoilHatComponent>(headItem, out var tinfoil))
|
||||
continue;
|
||||
|
||||
psionicList.Add((psion, tinfoil));
|
||||
}
|
||||
|
||||
foreach (var pair in psionicList)
|
||||
{
|
||||
if (pair.worn.DestroyOnFry)
|
||||
{
|
||||
QueueDel(pair.worn.Owner);
|
||||
Spawn("Ash", Transform(pair.wearer).Coordinates);
|
||||
_popupSystem.PopupEntity(Loc.GetString("psionic-burns-up", ("item", pair.worn.Owner)), pair.wearer, Filter.Pvs(pair.worn.Owner), true, Shared.Popups.PopupType.MediumCaution);
|
||||
_audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(pair.worn.Owner), pair.worn.Owner, true);
|
||||
} else
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("psionic-burn-resist", ("item", pair.worn.Owner)), pair.wearer, Filter.Pvs(pair.worn.Owner), true, Shared.Popups.PopupType.SmallCaution);
|
||||
_audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(pair.worn.Owner), pair.worn.Owner, true);
|
||||
}
|
||||
|
||||
DamageSpecifier damage = new();
|
||||
damage.DamageDict.Add("Heat", 2.5);
|
||||
damage.DamageDict.Add("Shock", 2.5);
|
||||
|
||||
if (_glimmerSystem.Glimmer > 500 && _glimmerSystem.Glimmer < 750)
|
||||
{
|
||||
damage *= 2;
|
||||
if (TryComp<FlammableComponent>(pair.wearer, out var flammableComponent))
|
||||
{
|
||||
flammableComponent.FireStacks += 1;
|
||||
_flammableSystem.Ignite(pair.wearer, pair.wearer, flammableComponent);
|
||||
}
|
||||
} else if (_glimmerSystem.Glimmer > 750)
|
||||
{
|
||||
damage *= 3;
|
||||
if (TryComp<FlammableComponent>(pair.wearer, out var flammableComponent))
|
||||
{
|
||||
flammableComponent.FireStacks += 2;
|
||||
_flammableSystem.Ignite(pair.wearer, pair.wearer, flammableComponent);
|
||||
}
|
||||
}
|
||||
|
||||
_damageableSystem.TryChangeDamage(pair.wearer, damage, true, true);
|
||||
}
|
||||
|
||||
// for probers:
|
||||
var queryReactive = EntityQueryEnumerator<SharedGlimmerReactiveComponent, TransformComponent, PhysicsComponent>();
|
||||
while (queryReactive.MoveNext(out var reactive, out _, out var xform, out var physics))
|
||||
{
|
||||
// shoot out three bolts of lighting...
|
||||
_glimmerReactiveSystem.BeamRandomNearProber(reactive, 3, 12);
|
||||
|
||||
// try to anchor if we can
|
||||
if (!xform.Anchored)
|
||||
{
|
||||
var coordinates = xform.Coordinates;
|
||||
var gridUid = xform.GridUid;
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
continue;
|
||||
|
||||
var tileIndices = grid.TileIndicesFor(coordinates);
|
||||
|
||||
if (_anchorableSystem.TileFree(grid, tileIndices, physics.CollisionLayer, physics.CollisionMask))
|
||||
_transformSystem.AnchorEntity(reactive, xform);
|
||||
}
|
||||
|
||||
if (!TryComp<ApcPowerReceiverComponent>(reactive, out var power))
|
||||
continue;
|
||||
|
||||
// If it's been turned off, turn it back on.
|
||||
if (power.PowerDisabled)
|
||||
_powerReceiverSystem.TogglePower(reactive, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using Robust.Shared.Random;
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
internal sealed class NoosphericStormRule : StationEventSystem<NoosphericStormRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, NoosphericStormRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<EntityUid> validList = new();
|
||||
|
||||
var query = EntityManager.EntityQueryEnumerator<PotentialPsionicComponent>();
|
||||
while (query.MoveNext(out var potentialPsionic, out var potentialPsionicComponent))
|
||||
{
|
||||
if (_mobStateSystem.IsDead(potentialPsionic))
|
||||
continue;
|
||||
|
||||
// Skip over those who are already psionic or those who are insulated.
|
||||
if (HasComp<PsionicComponent>(potentialPsionic) || HasComp<PsionicInsulationComponent>(potentialPsionic))
|
||||
continue;
|
||||
|
||||
validList.Add(potentialPsionic);
|
||||
}
|
||||
|
||||
// Give some targets psionic abilities.
|
||||
RobustRandom.Shuffle(validList);
|
||||
|
||||
var toAwaken = RobustRandom.Next(1, component.MaxAwaken);
|
||||
|
||||
foreach (var target in validList)
|
||||
{
|
||||
if (toAwaken-- == 0)
|
||||
break;
|
||||
|
||||
_psionicAbilitiesSystem.AddPsionics(target);
|
||||
}
|
||||
|
||||
// Increase glimmer.
|
||||
var baseGlimmerAdd = _robustRandom.Next(component.BaseGlimmerAddMin, component.BaseGlimmerAddMax);
|
||||
var glimmerSeverityMod = 1 + (component.GlimmerSeverityCoefficient * (GetSeverityModifier() - 1f));
|
||||
var glimmerAdded = (int) Math.Round(baseGlimmerAdd * glimmerSeverityMod);
|
||||
|
||||
_glimmerSystem.Glimmer += glimmerAdded;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.StatusEffect;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Zaps everyone, rolling psionics and disorienting them
|
||||
/// </summary>
|
||||
internal sealed class NoosphericZapRule : StationEventSystem<NoosphericZapRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly PsionicsSystem _psionicsSystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, NoosphericZapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var query = EntityQueryEnumerator<PotentialPsionicComponent, MobStateComponent>();
|
||||
|
||||
while (query.MoveNext(out var psion, out var potentialPsionicComponent, out _))
|
||||
{
|
||||
if (!_mobStateSystem.IsAlive(psion) || HasComp<PsionicInsulationComponent>(psion))
|
||||
continue;
|
||||
|
||||
_stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(5), false);
|
||||
_statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent");
|
||||
|
||||
if (HasComp<PsionicComponent>(psion))
|
||||
_popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution);
|
||||
else
|
||||
{
|
||||
if (potentialPsionicComponent.Rerolled)
|
||||
{
|
||||
potentialPsionicComponent.Rerolled = false;
|
||||
_popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution);
|
||||
} else
|
||||
{
|
||||
_psionicsSystem.RollPsionics(psion, potentialPsionicComponent, multiplier: 0.25f);
|
||||
_popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Mutes everyone for a random amount of time.
|
||||
/// </summary>
|
||||
internal sealed class PsionicCatGotYourTongueRule : StationEventSystem<PsionicCatGotYourTongueRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!;
|
||||
|
||||
|
||||
protected override void Started(EntityUid uid, PsionicCatGotYourTongueRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
List<EntityUid> psionicList = new();
|
||||
|
||||
var query = EntityQueryEnumerator<PotentialPsionicComponent, MobStateComponent>();
|
||||
while (query.MoveNext(out var psion, out _, out _))
|
||||
{
|
||||
if (_mobStateSystem.IsAlive(psion) && !HasComp<PsionicInsulationComponent>(psion))
|
||||
psionicList.Add(psion);
|
||||
}
|
||||
|
||||
foreach (var psion in psionicList)
|
||||
{
|
||||
var duration = _robustRandom.Next(component.MinDuration, component.MaxDuration);
|
||||
|
||||
_statusEffectsSystem.TryAddStatusEffect(psion,
|
||||
"Muted",
|
||||
duration,
|
||||
false,
|
||||
"Muted");
|
||||
|
||||
_sharedAudioSystem.PlayGlobal(component.Sound, Filter.Entities(psion), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using Robust.Shared.Map;
|
|||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Abilities.Psionics; //Nyano - Summary: for the telegnostic projection.
|
||||
|
||||
namespace Content.Server.Singularity.EntitySystems;
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ public sealed class EventHorizonSystem : SharedEventHorizonSystem
|
|||
|
||||
SubscribeLocalEvent<MapGridComponent, EventHorizonAttemptConsumeEntityEvent>(PreventConsume);
|
||||
SubscribeLocalEvent<GhostComponent, EventHorizonAttemptConsumeEntityEvent>(PreventConsume);
|
||||
SubscribeLocalEvent<TelegnosticProjectionComponent, EventHorizonAttemptConsumeEntityEvent>(PreventConsume); ///Nyano - Summary: the telegnositic projection has the same trait as ghosts.
|
||||
SubscribeLocalEvent<StationDataComponent, EventHorizonAttemptConsumeEntityEvent>(PreventConsume);
|
||||
SubscribeLocalEvent<EventHorizonComponent, MapInitEvent>(OnHorizonMapInit);
|
||||
SubscribeLocalEvent<EventHorizonComponent, EntityUnpausedEvent>(OnHorizonUnpaused);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ using Robust.Server.Player;
|
|||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
using Content.Server.Psionics.Glimmer;
|
||||
using Content.Shared.Psionics.Glimmer;
|
||||
namespace Content.Server.StationEvents;
|
||||
|
||||
public sealed class EventManagerSystem : EntitySystem
|
||||
|
|
@ -16,6 +17,7 @@ public sealed class EventManagerSystem : EntitySystem
|
|||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] public readonly GameTicker GameTicker = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!; //Nyano - Summary: pulls in the glimmer system.
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
|
|
@ -204,6 +206,17 @@ public sealed class EventManagerSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
// Nyano - Summary: - Begin modified code block: check for glimmer events.
|
||||
// This could not be cleanly done anywhere else.
|
||||
if (_configurationManager.GetCVar(CCVars.GlimmerEnabled) &&
|
||||
prototype.TryGetComponent<GlimmerEventComponent>(out var glimmerEvent) &&
|
||||
(_glimmerSystem.Glimmer < glimmerEvent.MinimumGlimmer ||
|
||||
_glimmerSystem.Glimmer > glimmerEvent.MaximumGlimmer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Nyano - End modified code block.
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,3 +203,14 @@ public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs
|
|||
Store = store;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Nyano/DeltaV Code. For penguin bombs and what not.
|
||||
/// Raised on an item when it is purchased.
|
||||
/// An item may need to set it upself up for its purchaser.
|
||||
/// For example, to make sure it isn't hostile to them or
|
||||
/// to make sure it fits their apperance.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct ItemPurchasedEvent(EntityUid Purchaser);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,26 @@ public sealed partial class ArtifactAnalyzerComponent : Component
|
|||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AnalysisDurationMulitplier = 1;
|
||||
|
||||
// Nyano - Summary - Begin modified code block: tie artifacts to glimmer.
|
||||
/// <summary>
|
||||
/// Ratio of research points to glimmer.
|
||||
/// Each is 150 and added to this, so
|
||||
/// 550 / 700 / 850 / 1000
|
||||
/// </summary>
|
||||
public int ExtractRatio = 400;
|
||||
|
||||
/// <summary>
|
||||
// The machine part that modifies the sacrifice ratio.
|
||||
/// </summary>
|
||||
[DataField("machinePartExtractRatio", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
|
||||
public string MachinePartExtractRatio = "MatterBin";
|
||||
|
||||
/// <summary>
|
||||
/// How many points per glimmer are added to the sacrifice ratio per tier.
|
||||
/// </summary>
|
||||
public int PartRatingExtractRatioMultiplier = 150;
|
||||
// Nyano - End modified code block.
|
||||
|
||||
/// <summary>
|
||||
/// The machine part that modifies analysis duration.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ using Robust.Shared.Audio;
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Psionics.Glimmer; //Nyano - Summary:.
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
|
|||
[Dependency] private readonly PaperSystem _paper = default!;
|
||||
[Dependency] private readonly ResearchSystem _research = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!; //Nyano - Summary: pulls in the glimmer system.
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
|
|
@ -361,6 +363,14 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
|
|||
_research.ModifyServerPoints(server.Value, pointValue, serverComponent);
|
||||
_artifact.AdjustConsumedPoints(artifact.Value, pointValue);
|
||||
|
||||
// Nyano - Summary - Begin modified code block: tie artifacts to glimmer.
|
||||
if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity.Value, out var analyzer) &&
|
||||
analyzer != null)
|
||||
{
|
||||
_glimmerSystem.Glimmer += (int) pointValue / analyzer.ExtractRatio;
|
||||
}
|
||||
// Nyano - End modified code block.
|
||||
|
||||
_audio.PlayPvs(component.ExtractSound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"),
|
||||
|
|
@ -423,11 +433,18 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
|
|||
var analysisRating = args.PartRatings[component.MachinePartAnalysisDuration];
|
||||
|
||||
component.AnalysisDurationMulitplier = MathF.Pow(component.PartRatingAnalysisDurationMultiplier, analysisRating - 1);
|
||||
|
||||
// Nyano - Summary - Begin modified code block: tie artifacts to glimmer.
|
||||
var extractRating = args.PartRatings[component.MachinePartExtractRatio];
|
||||
|
||||
component.ExtractRatio = (400 + (int) (extractRating * component.PartRatingExtractRatioMultiplier));
|
||||
// Nyano - End modified code block.
|
||||
}
|
||||
|
||||
private void OnUpgradeExamine(EntityUid uid, ArtifactAnalyzerComponent component, UpgradeExamineEvent args)
|
||||
{
|
||||
args.AddPercentageUpgrade("analyzer-artifact-component-upgrade-analysis", component.AnalysisDurationMulitplier);
|
||||
args.AddNumberUpgrade("analyzer-artifact-component-upgrade-sacrifice", component.ExtractRatio - 550);
|
||||
}
|
||||
|
||||
private void OnItemPlaced(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemPlacedEvent args)
|
||||
|
|
|
|||
|
|
@ -89,4 +89,6 @@ public enum LogType
|
|||
ItemConfigure = 84,
|
||||
DeviceLinking = 85,
|
||||
Tile = 86,
|
||||
BagOfHolding = 420, //Nyano - Summary: adds bag of holding.
|
||||
Psionics = 421, //Nyano - Summary: ads psionic as a log type.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1783,5 +1783,31 @@ namespace Content.Shared.CCVar
|
|||
/// </summary>
|
||||
public static readonly CVarDef<string> ReplayAutoRecordTempDir =
|
||||
CVarDef.Create("replay.auto_record_temp_dir", "", CVar.SERVERONLY);
|
||||
|
||||
/// DELTA-V CCVARS
|
||||
/*
|
||||
* Glimmer
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Whether glimmer is enabled.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GlimmerEnabled =
|
||||
CVarDef.Create("glimmer.enabled", true, CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Passive glimmer drain per second.
|
||||
/// Note that this is randomized and this is an average value.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> GlimmerLostPerSecond =
|
||||
CVarDef.Create("glimmer.passive_drain_per_second", 0.1f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether random rolls for psionics are allowed.
|
||||
/// Guaranteed psionics will still go through.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> PsionicRollsEnabled =
|
||||
CVarDef.Create("psionics.rolls_enabled", true, CVar.SERVERONLY);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,10 +79,15 @@ namespace Content.Shared.Chat
|
|||
/// </summary>
|
||||
Unspecified = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// Nyano - Summary:: Telepathic channel for all psionic entities.
|
||||
/// </summary>
|
||||
Telepathic = 1 << 14,
|
||||
|
||||
/// <summary>
|
||||
/// Channels considered to be IC.
|
||||
/// </summary>
|
||||
IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual,
|
||||
IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Telepathic, //Nyano - Summary: Adds telepathic as an 'IC' labelled chat..
|
||||
|
||||
AdminRelated = Admin | AdminAlert | AdminChat,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@
|
|||
/// </summary>
|
||||
Admin = ChatChannel.AdminChat,
|
||||
|
||||
/// <summary>
|
||||
/// Nyano - Summary:. Telepathic channel for all psionic entities.
|
||||
/// </summary>
|
||||
Telepathic = ChatChannel.Telepathic,
|
||||
|
||||
Console = ChatChannel.Unspecified
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public abstract class SharedChatSystem : EntitySystem
|
|||
public const char EmotesAltPrefix = '*';
|
||||
public const char AdminPrefix = ']';
|
||||
public const char WhisperPrefix = ',';
|
||||
public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix.
|
||||
public const char DefaultChannelKey = 'h';
|
||||
|
||||
[ValidatePrototypeId<RadioChannelPrototype>]
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="DeltaV\Abilities\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@ namespace Content.Shared.Eye
|
|||
None = 0,
|
||||
Normal = 1 << 0,
|
||||
Ghost = 1 << 1,
|
||||
PsionicInvisibility = 1 << 2, //Nyano - Summary: adds Psionic Invisibility as a visibility layer. Currently does nothing.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue