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 commit c0cf35d03f.

* Update RobustToolbox

* Revert "Update RobustToolbox"

This reverts commit c4dc828df7.

* 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:
PHCodes 2023-10-08 14:07:53 -04:00 committed by GitHub
parent 531d99c064
commit 93c3b03b11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
259 changed files with 6512 additions and 115 deletions

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,6 @@
namespace Content.Client.Psionics.Glimmer;
public enum GlimmerReactiveVisualLayers : byte
{
GlimmerEffect,
}

View File

@ -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();
}
}
}

View File

@ -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"),
})
}
},
}
},
}
});
}
}
}

View File

@ -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++;
}
}
}

View File

@ -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.

View File

@ -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,

View File

@ -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
};
}

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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>

View File

@ -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;

View File

@ -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);

View File

@ -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.
}
}

View File

@ -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!;

View File

@ -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 {}
}

View File

@ -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;
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,5 @@
namespace Content.Server.CartridgeLoader.Cartridges;
[RegisterComponent]
public sealed partial class GlimmerMonitorCartridgeComponent : Component
{ }

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
{
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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 = "";
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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--;
}
}
}
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,6 @@
namespace Content.Server.Psionics
{
[RegisterComponent]
public sealed partial class PsionicallyInvisibleComponent : Component
{}
}

View File

@ -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;
}
}

View File

@ -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
{}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
using Content.Server.StationEvents.Events;
namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(FreeProberRule))]
public sealed partial class FreeProberRuleComponent : Component
{
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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";
}

View File

@ -0,0 +1,8 @@
using Content.Server.StationEvents.Events;
namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(GlimmerWispRule))]
public sealed partial class GlimmerWispRuleComponent : Component
{
}

View File

@ -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;
}

View File

@ -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"
};
}

View File

@ -0,0 +1,8 @@
namespace Content.Server.StationEvents
{
[RegisterComponent]
public sealed partial class MidRoundAntagSpawnLocationComponent : Component
{
}
}

View File

@ -0,0 +1,8 @@
using Content.Server.StationEvents.Events;
namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(MundaneDischargeRule))]
public sealed partial class MundaneDischargeRuleComponent : Component
{
}

View File

@ -0,0 +1,8 @@
using Content.Server.StationEvents.Events;
namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(NoosphericFryRule))]
public sealed partial class NoosphericFryRuleComponent : Component
{
}

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
using Content.Server.StationEvents.Events;
namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(NoosphericZapRule))]
public sealed partial class NoosphericZapRuleComponent : Component
{
}

View File

@ -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");
}

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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>
{
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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)

View File

@ -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.
}

View File

@ -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);
}
}

View File

@ -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,
}

View File

@ -51,6 +51,11 @@
/// </summary>
Admin = ChatChannel.AdminChat,
/// <summary>
/// Nyano - Summary:. Telepathic channel for all psionic entities.
/// </summary>
Telepathic = ChatChannel.Telepathic,
Console = ChatChannel.Unspecified
}
}

View File

@ -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>]

View File

@ -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>

View File

@ -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