main cult

This commit is contained in:
EnefFlow
2024-01-27 15:19:52 +03:00
committed by Aviu00
parent 6310813ce6
commit 4fab8188f0
429 changed files with 12281 additions and 9 deletions

View File

@@ -2,7 +2,9 @@ using Content.Client.Administration.Managers;
using Content.Client.Ghost;
using Content.Shared.Administration;
using Content.Shared.Chat;
using Content.Shared.White.Cult;
using Robust.Client.Console;
using Robust.Client.Player;
using Robust.Shared.Utility;
namespace Content.Client.Chat.Managers
@@ -12,6 +14,9 @@ namespace Content.Client.Chat.Managers
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IPlayerManager _player = default!;
private ISawmill _sawmill = default!;
@@ -47,6 +52,12 @@ namespace Content.Client.Chat.Managers
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.Cult:
var localEnt = _player.LocalPlayer != null ? _player.LocalPlayer.ControlledEntity : null;
if (_entities.TryGetComponent(localEnt, out CultistComponent? comp))
_consoleHost.ExecuteCommand($"csay \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.Dead:
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
goto case ChatSelectChannel.Local;

View File

@@ -20,6 +20,8 @@ using Content.Shared.Input;
using Content.Shared.Radio;
using Content.Shared.White;
using Content.Shared.White.Utils;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Systems;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
@@ -50,6 +52,8 @@ public sealed class ChatUIController : UIController
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly CultistWordGeneratorManager _wordGenerator = default!;
[UISystemDependency] private readonly ExamineSystem? _examine = default;
[UISystemDependency] private readonly GhostSystem? _ghost = default;
@@ -69,7 +73,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.CultPrefix, ChatSelectChannel.Cult}, //WD EDIT
};
public static readonly Dictionary<ChatSelectChannel, char> ChannelPrefixes = new()
@@ -82,7 +87,9 @@ 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.Cult, SharedChatSystem.CultPrefix} // WD EDIT
};
/// <summary>
@@ -195,6 +202,9 @@ public sealed class ChatUIController : UIController
_input.SetInputCommand(ContentKeyFunctions.FocusAdminChat,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Admin)));
_input.SetInputCommand(ContentKeyFunctions.FocusCultChat,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Cult)));
_input.SetInputCommand(ContentKeyFunctions.FocusRadio,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Radio)));
@@ -210,11 +220,22 @@ public sealed class ChatUIController : UIController
_input.SetInputCommand(ContentKeyFunctions.CycleChatChannelBackward,
InputCmdHandler.FromDelegate(_ => CycleChatChannel(false)));
// WD EDIT
SubscribeLocalEvent<EventCultistComponentState>(OnUpdateCultState);
// WD EDIT END
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
}
// WD EDIT
private void OnUpdateCultState(EventCultistComponentState ev)
{
UpdateChannelPermissions();
}
// WD EDIT END
public void OnScreenLoad()
{
SetMainChat(true);
@@ -512,6 +533,15 @@ public sealed class ChatUIController : UIController
CanSendChannels |= ChatSelectChannel.Admin;
}
// WD EDIT
var localEnt = _player.LocalPlayer != null ? _player.LocalPlayer.ControlledEntity : null;
if (_entities.TryGetComponent(localEnt, out CultistComponent? comp))
{
FilterableChannels |= ChatChannel.Cult;
CanSendChannels |= ChatSelectChannel.Cult;
}
// WD EDIT END
SelectableChannels = CanSendChannels;
// Necessary so that we always have a channel to fall back to.
@@ -801,6 +831,13 @@ public sealed class ChatUIController : UIController
AddSpeechBubble(msg, SpeechBubble.SpeechType.Whisper);
break;
// WD EDIT
case ChatChannel.Cult:
msg.Message = _wordGenerator.GenerateText(msg.Message);
AddSpeechBubble(msg, SpeechBubble.SpeechType.Whisper);
break;
// WD EDIT END
case ChatChannel.Dead:
if (_ghost is not {IsGhost: true})
break;

View File

@@ -22,7 +22,8 @@ public sealed partial class ChannelFilterPopup : Popup
ChatChannel.Admin,
ChatChannel.AdminAlert,
ChatChannel.AdminChat,
ChatChannel.Server
ChatChannel.Server,
ChatChannel.Cult // WD EDIT
};
private readonly Dictionary<ChatChannel, ChannelFilterCheckbox> _filterStates = new();

View File

@@ -64,6 +64,7 @@ public sealed class ChannelSelectorButton : ChatPopupButton<ChannelSelectorPopup
ChatSelectChannel.OOC => Color.LightSkyBlue,
ChatSelectChannel.Dead => Color.MediumPurple,
ChatSelectChannel.Admin => Color.HotPink,
ChatSelectChannel.Cult => Color.DarkRed,
_ => Color.DarkGray
};
}

View File

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

View File

@@ -0,0 +1,86 @@
using System.Numerics;
using Content.Shared.Humanoid;
using Content.Shared.White.Cult;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client._White.Cult;
public sealed class CultHudOverlay : Overlay
{
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _transformSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
protected override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
var spriteQuery = _entityManager.GetEntityQuery<SpriteComponent>();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
const float scale = 1f;
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3.CreateRotation(-rotation);
foreach (var cultist in _entityManager.EntityQuery<CultistComponent>(true))
{
if (!xformQuery.TryGetComponent(cultist.Owner, out var xform) ||
xform.MapID != args.MapId)
{
continue;
}
var worldPosition = _transformSystem.GetWorldPosition(xform);
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
handle.SetTransform(matty);
var cultistIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/White/Cult/cult_hud.rsi"), "cult");
var iconTexture = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(cultistIcon);
float yOffset;
float xOffset;
if (spriteQuery.TryGetComponent(cultist.Owner, out var sprite))
{
yOffset = sprite.Bounds.Height - 10f; //sprite.Bounds.Height + 7f;
xOffset = sprite.Bounds.Width - 40f; //sprite.Bounds.Width + 7f;
}
else
{
yOffset = 1f;
xOffset = 1f;
}
// Position above the entity (we've already applied the matrix transform to the entity itself)
// Offset by the texture size for every do_after we have.
var position = new Vector2(xOffset / EyeManager.PixelsPerMeter, yOffset / EyeManager.PixelsPerMeter);
// Draw the underlying bar texture
if (sprite != null && !sprite.ContainerOccluded)
{
handle.DrawTexture(iconTexture, position);
}
}
handle.UseShader(null);
handle.SetTransform(Matrix3.Identity);
}
public CultHudOverlay(IEntityManager entityManager)
{
_entityManager = entityManager;
_transformSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
}
}

View File

@@ -0,0 +1,65 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Client._White.Cult;
public sealed class CultPentagramSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _robustRandom = default!;
private const string Rsi = "White/Cult/pentagram.rsi";
private static readonly string[] States =
{
"halo1",
"halo2",
"halo3",
"halo4",
"halo5",
"halo6"
};
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PentagramComponent, ComponentStartup>(PentagramAdded);
SubscribeLocalEvent<PentagramComponent, ComponentShutdown>(PentagramRemoved);
}
private void PentagramAdded(EntityUid uid, PentagramComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (sprite.LayerMapTryGet(PentagramKey.Key, out var _))
return;
var adj = sprite.Bounds.Height / 2 + ((1.0f/32) * 10.0f);
var randomIndex = _robustRandom.Next(0, States.Length);
var randomState = States[randomIndex];
var layer = sprite.AddLayer(new SpriteSpecifier.Rsi(new ResPath(Rsi), randomState));
sprite.LayerMapSet(PentagramKey.Key, layer);
sprite.LayerSetOffset(layer, new Vector2(0.0f, adj));
}
private void PentagramRemoved(EntityUid uid, PentagramComponent component, ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (!sprite.LayerMapTryGet(PentagramKey.Key, out var layer))
return;
sprite.RemoveLayer(layer);
}
private enum PentagramKey
{
Key
}
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.White.Cult.Items;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.Items.VeilShifter;
public sealed class VeilVisualizerSystem : VisualizerSystem<VeilVisualsComponent>
{
private const string StateOn = "icon-on";
private const string StateOff = "icon";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VoidTeleportComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, VoidTeleportComponent component, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite)
|| !AppearanceSystem.TryGetData<bool>(uid, VeilVisuals.Activated, out var activated))
return;
sprite.LayerSetState(VeilVisualsLayers.Activated, activated ? StateOn : StateOff);
}
protected override void OnAppearanceChange(EntityUid uid, VeilVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null
|| !AppearanceSystem.TryGetData<bool>(uid, VeilVisuals.Activated, out var activated))
return;
args.Sprite.LayerSetState(VeilVisualsLayers.Activated, activated ? component.StateOn : component.StateOff);
}
}
public enum VeilVisualsLayers : byte
{
Activated
}

View File

@@ -0,0 +1,13 @@
namespace Content.Client._White.Cult.Items.VeilShifter;
[RegisterComponent]
public sealed partial class VeilVisualsComponent : Component
{
[DataField("stateOn")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOn = "icon-on";
[DataField("stateOff")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOff = "icon";
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.White.Cult.Items;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.Items.VoidTorch;
public sealed class VoidTorchVisualizerSystem : VisualizerSystem<VoidTorchVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, VoidTorchVisualsComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);
if (args.Sprite == null
|| !AppearanceSystem.TryGetData<bool>(uid, VoidTorchVisuals.Activated, out var activated))
return;
args.Sprite.LayerSetState(VoidTorchVisualsLayers.Activated, activated ? component.StateOn : component.StateOff);
}
}
public enum VoidTorchVisualsLayers : byte
{
Activated
}

View File

@@ -0,0 +1,13 @@
namespace Content.Client._White.Cult.Items.VoidTorch;
[RegisterComponent]
public sealed partial class VoidTorchVisualsComponent : Component
{
[DataField("stateOn")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOn = "icon-on";
[DataField("stateOff")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOff = "icon";
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client._White.Cult;
public enum NarsieLayer
{
Default
}

View File

@@ -0,0 +1,69 @@
using Content.Shared.White.Cult;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
namespace Content.Client._White.Cult;
public sealed class NarsieVisualizer : VisualizerSystem<NarsieComponent>
{
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NarsieComponent, AnimationCompletedEvent>(OnAnimationCompleted);
}
private void OnAnimationCompleted(EntityUid uid, NarsieComponent component, AnimationCompletedEvent args)
{
SetDefaultState(Comp<SpriteComponent>(uid));
}
protected override void OnAppearanceChange(EntityUid uid, NarsieComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);
if(args.Sprite == null) return;
if (!args.AppearanceData.TryGetValue(NarsieVisualState.VisualState, out var narsieVisualsObject) || narsieVisualsObject is not NarsieVisuals narsieVisual)
return;
switch (narsieVisual)
{
case NarsieVisuals.Spawning:
PlaySpawnAnimation(uid);
break;
case NarsieVisuals.Spawned:
if(_animationSystem.HasRunningAnimation(uid, "narsie_spawn")) break;
SetDefaultState(args.Sprite);
break;
}
}
private void PlaySpawnAnimation(EntityUid uid)
{
_animationSystem.Play(uid, NarsieSpawnAnimation, "narsie_spawn");
}
private void SetDefaultState(SpriteComponent component)
{
component.LayerSetVisible(NarsieLayer.Default, true);
component.LayerSetState(NarsieLayer.Default, new RSI.StateId("narsie"));
component.LayerSetAutoAnimated(NarsieLayer.Default, true);
}
private static readonly Animation NarsieSpawnAnimation = new()
{
Length = TimeSpan.FromSeconds(3.5),
AnimationTracks =
{
new AnimationTrackSpriteFlick()
{
LayerKey = NarsieLayer.Default,
KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(new RSI.StateId("narsie_spawn_anim"), 0f)}
}
}
};
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.White.Cult.Pentagram;
using Robust.Shared.GameStates;
namespace Content.Client._White.Cult;
[NetworkedComponent, RegisterComponent]
public sealed partial class PentagramComponent : SharedPentagramComponent
{
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.White.Cult.Pylon;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.Pylon;
public sealed class PylonVisualizerSystem : VisualizerSystem<PylonVisualsComponent>
{
private const string StateOn = "pylon";
private const string StateOff = "pylon_off";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedPylonComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, SharedPylonComponent component, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite)
|| !AppearanceSystem.TryGetData<bool>(uid, PylonVisualsLayers.Activated, out var activated))
return;
sprite.LayerSetState(PylonVisualsLayers.Activated, activated ? StateOn : StateOff);
}
protected override void OnAppearanceChange(EntityUid uid, PylonVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null
|| !AppearanceSystem.TryGetData<bool>(uid, PylonVisuals.Activated, out var activated))
return;
args.Sprite.LayerSetState(PylonVisualsLayers.Activated, activated ? component.StateOn : component.StateOff);
}
}
public enum PylonVisualsLayers : byte
{
Activated
}

View File

@@ -0,0 +1,15 @@
using Content.Client.Storage.Visualizers;
namespace Content.Client._White.Cult.Pylon;
[RegisterComponent]
public sealed partial class PylonVisualsComponent : Component
{
[DataField("stateOn")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOn = "pylon";
[DataField("stateOff")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOff = "pylon_off";
}

View File

@@ -0,0 +1,48 @@
using Content.Shared.White.Cult;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
namespace Content.Client._White.Cult;
public sealed class ShowCultHudSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
private Overlay _overlay = default!;
public override void Initialize()
{
SubscribeLocalEvent<CultistComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CultistComponent, ComponentRemove>(OnComponentRemoved);
SubscribeLocalEvent<CultistComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<CultistComponent, PlayerDetachedEvent>(OnPlayerDetached);
_overlay = new CultHudOverlay(EntityManager);
}
private void OnComponentInit(EntityUid uid, CultistComponent component, ComponentInit args)
{
if (_player.LocalPlayer?.ControlledEntity != uid) return;
_overlayManager.AddOverlay(_overlay);
}
private void OnComponentRemoved(EntityUid uid, CultistComponent component, ComponentRemove args)
{
if (_player.LocalPlayer?.ControlledEntity != uid) return;
_overlayManager.RemoveOverlay(_overlay);
}
private void OnPlayerAttached(EntityUid uid, CultistComponent component, PlayerAttachedEvent args)
{
_overlayManager.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, CultistComponent component, PlayerDetachedEvent args)
{
_overlayManager.RemoveOverlay(_overlay);
}
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.White.Cult;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.Structures;
public sealed class CultCraftStructureVisualizerSystem : VisualizerSystem<CultCraftStructureVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, CultCraftStructureVisualsComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);
if (args.Sprite == null
|| !AppearanceSystem.TryGetData<bool>(uid, CultCraftStructureVisuals.Activated, out var activated))
return;
args.Sprite.LayerSetState(CultCraftStructureVisualsLayers.Activated, activated ? component.StateOn : component.StateOff);
}
}
public enum CultCraftStructureVisualsLayers : byte
{
Activated
}

View File

@@ -0,0 +1,13 @@
namespace Content.Client._White.Cult.Structures;
[RegisterComponent]
public sealed partial class CultCraftStructureVisualsComponent : Component
{
[DataField("stateOn")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOn = "icon";
[DataField("stateOff")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StateOff = "icon-off";
}

View File

@@ -0,0 +1,56 @@
using Content.Shared.White.Cult.UI;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client._White.Cult.UI.Altar;
[UsedImplicitly]
public sealed class AltarBUI : BoundUserInterface
{
private AltarWindow? _window;
public AltarBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = new AltarWindow();
_window.OnClose += Close;
_window.OnItemSelected += OnItemSelected;
}
private void OnItemSelected(string item)
{
var evt = new AltarBuyRequest(item);
SendMessage(evt);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_window?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is AltarListingBUIState listingState)
{
_window?.SetListing(listingState.Items);
}
else if(state is AltarTimerBUIState timerState)
{
_window?.SetTimer(timerState.NextTimeUse);
}
}
}

View File

@@ -0,0 +1,9 @@
<Control SetSize="50 50" MaxSize="50 50" xmlns="https://spacestation14.io"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#000000FF" />
</PanelContainer.PanelOverride>
<TextureButton Name="BuyListingButton" Access="Public" HorizontalAlignment="Center" VerticalAlignment="Center" SetSize="48 48" MaxSize="48 48"/>
</PanelContainer>
</Control>

View File

@@ -0,0 +1,21 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.Altar;
[GenerateTypedNameReferences]
public partial class AltarListingControl : Control
{
public AltarListingControl(EntityPrototype prototype, Texture icon, Action<string>? clickAction)
{
RobustXamlLoader.Load(this);
ToolTip = $"{prototype.Name}\n{prototype.Description}";
BuyListingButton.TextureNormal = icon;
BuyListingButton.OnButtonDown += _ => clickAction?.Invoke(prototype.ID);
}
}

View File

@@ -0,0 +1,7 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Loc agent-id-menu-title}">
<RichTextLabel Name="TimerLabel"></RichTextLabel>
<BoxContainer Name="ListingContainer" Orientation="Horizontal" SeparationOverride="4" MinWidth="150">
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,92 @@
using Content.Client.GameTicking.Managers;
using Content.Client.TextScreen;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client._White.Cult.UI.Altar;
[GenerateTypedNameReferences]
public partial class AltarWindow : DefaultWindow
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
[Dependency] private readonly PrototypeManager _prototypeManager = default!;
public event Action<string>? OnItemSelected;
private TimeSpan? _nextTimeUse = null!;
private List<AltarListingControl> _listingControls = new();
public AltarWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_nextTimeUse == null) return;
var remainingTime = _nextTimeUse.Value - _gameTiming.CurTime;
if (remainingTime.TotalSeconds < 0)
{
remainingTime = TimeSpan.Zero;
}
var remainingTimeText = TextScreenSystem.TimeToString(remainingTime);
TimerLabel.SetMessage(remainingTimeText);
}
public void SetListing(List<string> prototypes)
{
foreach (var prototypeId in prototypes)
{
var prototype = _prototypeManager.Index<EntityPrototype>(prototypeId);
if(prototype == null) return;
var prototypeIcon = _spriteSystem.GetPrototypeIcon(prototype).Default;
AddListingControl(prototype);
}
}
public void AddListingControl(EntityPrototype entityPrototype)
{
var icon = _spriteSystem.GetPrototypeIcon(entityPrototype).Default;
var control = new AltarListingControl(entityPrototype, icon, OnItemSelected);
ListingContainer.AddChild(control);
_listingControls.Add(control);
}
public void SetTimer(TimeSpan? timer)
{
_nextTimeUse = timer;
if (timer == null)
{
TimerLabel.SetMessage("Алтарь готов к использованию");
SetListingButtonsState(true);
return;
}
SetListingButtonsState(false);
}
private void SetListingButtonsState(bool enabled)
{
foreach (var listingControl in _listingControls)
{
listingControl.BuyListingButton.Disabled = enabled;
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Linq;
using Content.Client._White.UserInterface.Radial;
using Content.Shared.White.Cult.Runes.Components;
using Content.Shared.White.Cult.UI;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.ConstructSelector;
public sealed class ConstructSelectorBui : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SpriteSystem _spriteSystem = default!;
private bool _selected;
public ConstructSelectorBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
protected override void Open()
{
base.Open();
_spriteSystem = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>();
var shellComponent = _entityManager.GetComponent<ConstructShellComponent>(Owner);
var shellSelector = new RadialContainer();
shellSelector.Closed += () =>
{
if(_selected) return;
SendMessage(new ConstructFormSelectedEvent(shellComponent.ConstructForms.First()));
};
foreach (var form in shellComponent.ConstructForms)
{
var formPrototype = _prototypeManager.Index<EntityPrototype>(form);
var button = shellSelector.AddButton(formPrototype.Name, _spriteSystem.GetPrototypeIcon(formPrototype).Default);
button.Controller.OnPressed += _ =>
{
_selected = true;
SendMessage(new ConstructFormSelectedEvent(form));
shellSelector.Close();
};
}
shellSelector.OpenCentered();
}
}

View File

@@ -0,0 +1,89 @@
using Content.Client._White.UserInterface.Radial;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.UI;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.CultistFactory;
public sealed class CultistFactoryBUI : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private RadialContainer? _radialContainer;
private bool _updated = false;
public CultistFactoryBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
private void ResetUI()
{
_radialContainer?.Close();
_radialContainer = null;
_updated = false;
}
protected override void Open()
{
base.Open();
if (_radialContainer != null)
ResetUI();
_radialContainer = new RadialContainer();
if (State != null)
UpdateState(State);
}
private void PopulateRadial(IReadOnlyCollection<string> ids)
{
foreach (var id in ids)
{
if (!_prototypeManager.TryIndex<CultistFactoryProductionPrototype>(id, out var prototype))
return;
if (_radialContainer == null)
continue;
var button = _radialContainer.AddButton(prototype.Name, prototype.Icon);
button.Controller.OnPressed += _ =>
{
Select(id);
};
}
}
private void Select(string id)
{
SendMessage(new CultistFactoryItemSelectedMessage(id));
ResetUI();
Close();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
ResetUI();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_updated)
return;
if (state is CultistFactoryBUIState newState)
{
PopulateRadial(newState.Ids);
}
if (_radialContainer == null)
return;
_radialContainer?.OpenAttachedLocalPlayer();
_updated = true;
}
}

View File

@@ -0,0 +1,54 @@
using Content.Shared.White.Cult.UI;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.ListViewSelector;
public sealed class ListViewSelectorBUI : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private ListViewSelectorWindow? _window;
public ListViewSelectorBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = new ListViewSelectorWindow(_prototypeManager);
_window.OpenCentered();
_window.OnClose += Close;
_window.ItemSelected += (item, index) =>
{
var msg = new ListViewItemSelectedMessage(item, index);
SendMessage(msg);
};
if(State != null)
UpdateState(State);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is ListViewBUIState newState)
{
_window?.PopulateList(newState.Items, newState.IsUsingPrototypes);
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window?.Close();
}
}

View File

@@ -0,0 +1,9 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Loc runes-window-title}"
MinWidth="350"
MinHeight="400">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Name="ItemsContainer" Orientation="Vertical"></BoxContainer>
</ScrollContainer>
</DefaultWindow>

View File

@@ -0,0 +1,45 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.ListViewSelector;
[GenerateTypedNameReferences]
public partial class ListViewSelectorWindow : DefaultWindow
{
public Action<string, int>? ItemSelected;
private readonly IPrototypeManager _prototypeManager;
public ListViewSelectorWindow(IPrototypeManager prototypeManager)
{
RobustXamlLoader.Load(this);
_prototypeManager = prototypeManager;
}
public void PopulateList(List<string> items, bool isPrototypes)
{
ItemsContainer.RemoveAllChildren();
foreach (var item in items)
{
var button = new Button();
var itemName = Loc.GetString($"ent-{item}");
if (isPrototypes)
{
if(_prototypeManager.TryIndex<EntityPrototype>(item, out var itemPrototype))
{
itemName = itemPrototype.Name;
}
}
button.Text = itemName;
button.OnPressed += _ => ItemSelected?.Invoke(item, items.IndexOf(item));
ItemsContainer.AddChild(button);
}
}
}

View File

@@ -0,0 +1,45 @@
using Content.Shared.White.Cult.UI;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.UI.NameSelector;
public sealed class NameSelectorBUI : BoundUserInterface
{
public NameSelectorBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
private NameSelectorWindow? _window;
protected override void Open()
{
base.Open();
_window = new();
_window.OpenCentered();
_window.OnNameChange += OnNameSelected;
_window.OnClose += Close;
}
private void OnNameSelected(string name)
{
SendMessage(new NameSelectorMessage(name));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
if (state is not NameSelectorBuiState cast || _window == null)
{
return;
}
_window.UpdateState(cast.Name);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_window?.Close();
}
}

View File

@@ -0,0 +1,9 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Time to choose..">
<BoxContainer Orientation="Vertical">
<Label Text="Задайте название для метки." />
<LineEdit Name="NameSelector" HorizontalExpand="True" />
<Button Name="NameSelectorSet" Text="Принять" />
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,26 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._White.Cult.UI.NameSelector;
[GenerateTypedNameReferences]
public sealed partial class NameSelectorWindow: DefaultWindow
{
public Action<string>? OnNameChange;
public NameSelectorWindow()
{
RobustXamlLoader.Load(this);
NameSelectorSet.OnPressed += _ =>
{
OnNameChange!(NameSelector.Text);
};
}
public void UpdateState(string name)
{
NameSelector.Text = name;
}
}

View File

@@ -0,0 +1,44 @@
using Content.Client._White.UserInterface.Radial;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Components;
using Robust.Client.GameObjects;
using Robust.Client.Utility;
namespace Content.Client._White.Cult.UI.SpellSelector;
public sealed class SpellSelectorBUI : BoundUserInterface
{
private RadialContainer? _radialContainer;
public SpellSelectorBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_radialContainer = new RadialContainer();
_radialContainer.Closed += Close;
foreach (var action in CultistComponent.CultistActions)
{
var button = _radialContainer.AddButton(action.DisplayName, action.Icon?.Frame0());
button.Controller.OnPressed += _ =>
{
SendMessage(new CultEmpowerSelectedBuiMessage(action));
Close();
};
}
_radialContainer.OpenAttachedLocalPlayer();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_radialContainer?.Close();
}
}

View File

@@ -0,0 +1,129 @@
using Content.Client._White.UserInterface.Radial;
using Content.Client.Construction;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Popups;
using Content.Shared.White.Cult.Structures;
using Robust.Client.GameObjects;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.StructureRadial;
public sealed class StructureCraftBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlacementManager _placement = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
private RadialContainer? _radialContainer;
public StructureCraftBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
private void CreateUI()
{
if (_radialContainer != null)
ResetUI();
_radialContainer = new RadialContainer();
foreach (var prototype in _prototypeManager.EnumeratePrototypes<CultStructurePrototype>())
{
var radialButton = _radialContainer.AddButton(prototype.StructureName, prototype.Icon);
radialButton.Controller.OnPressed += _ =>
{
Select(prototype.StructureId);
};
}
_radialContainer.OpenAttachedLocalPlayer();
}
private void ResetUI()
{
_radialContainer?.Close();
_radialContainer = null;
}
protected override void Open()
{
base.Open();
CreateUI();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
ResetUI();
}
private void Select(string id)
{
CreateBlueprint(id);
ResetUI();
Close();
}
private void CreateBlueprint(string id)
{
var newObj = new PlacementInformation
{
Range = 2,
IsTile = false,
EntityType = id,
PlacementOption = "SnapgridCenter"
};
_prototypeManager.TryIndex<ConstructionPrototype>(id, out var construct);
if (construct == null)
return;
var player = _player.LocalPlayer?.ControlledEntity;
if (player == null)
return;
if (construct.ID == "CultPylon" && CheckForStructure(player, id))
{
var popup = _entMan.System<SharedPopupSystem>();
popup.PopupClient(Loc.GetString("cult-structure-craft-another-structure-nearby"), player.Value, player.Value);
return;
}
var constructSystem = _systemManager.GetEntitySystem<ConstructionSystem>();
var hijack = new ConstructionPlacementHijack(constructSystem, construct);
_placement.BeginPlacing(newObj, hijack);
}
private bool CheckForStructure(EntityUid? uid, string id)
{
if (uid == null)
return false;
if (!_entMan.TryGetComponent<TransformComponent>(uid, out var transform))
return false;
var lookupSystem = _entMan.System<EntityLookupSystem>();
var entities = lookupSystem.GetEntitiesInRange(transform.Coordinates, 15f);
foreach (var ent in entities)
{
if (!_entMan.TryGetComponent<MetaDataComponent>(ent, out var metadata))
continue;
if (metadata.EntityPrototype?.ID == id)
return true;
}
return false;
}
}

View File

@@ -0,0 +1,9 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Loc 'Choose'}"
MinWidth="300"
MinHeight="400">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Name="ItemsContainer" Orientation="Vertical"></BoxContainer>
</ScrollContainer>
</DefaultWindow>

View File

@@ -0,0 +1,36 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._White.Cult.UI.SummonCultistList;
[GenerateTypedNameReferences]
public partial class SummonCultistListWindow : DefaultWindow
{
public Action<int, int>? ItemSelected;
public SummonCultistListWindow()
{
RobustXamlLoader.Load(this);
}
public void PopulateList(List<int> items, List<string> labels)
{
ItemsContainer.RemoveAllChildren();
var count = Math.Min(items.Count, labels.Count);
for (var i = 0; i < count; i++)
{
var item = items[i];
var button = new Button();
button.Text = labels[i];
button.OnPressed += _ => ItemSelected?.Invoke(item, items.IndexOf(item));
ItemsContainer.AddChild(button);
}
}
}

View File

@@ -0,0 +1,43 @@
using Content.Shared.White.Cult.UI;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.UI.SummonCultistList;
public sealed class SummonCultistListWindowBUI : BoundUserInterface
{
private SummonCultistListWindow? _window;
public SummonCultistListWindowBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = new();
_window.OpenCentered();
_window.OnClose += Close;
_window.ItemSelected += (item, index) =>
{
var msg = new SummonCultistListWindowItemSelectedMessage(item, index);
SendMessage(msg);
_window.Close();
};
if (State != null)
UpdateState(State);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is SummonCultistListWindowBUIState newState)
{
_window?.PopulateList(newState.Items, newState.Label);
}
}
}

View File

@@ -0,0 +1,9 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Loc 'Teleport to'}"
MinWidth="300"
MinHeight="400">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Name="ItemsContainer" Orientation="Vertical"></BoxContainer>
</ScrollContainer>
</DefaultWindow>

View File

@@ -0,0 +1,41 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._White.Cult.UI.TeleportRunesList;
[GenerateTypedNameReferences]
public partial class TeleportRunesListWindow : DefaultWindow
{
public Action<int, int>? ItemSelected;
public TeleportRunesListWindow()
{
RobustXamlLoader.Load(this);
}
public void PopulateList(List<int> items, List<string> labels)
{
ItemsContainer.RemoveAllChildren();
var count = Math.Min(items.Count, labels.Count);
for (var i = 0; i < count; i++)
{
var item = items[i];
var button = new Button();
button.Text = labels[i];
button.OnPressed += _ => ItemSelected?.Invoke(item, items.IndexOf(item));
ItemsContainer.AddChild(button);
}
}
public void Clear()
{
ItemsContainer.RemoveAllChildren();
}
}

View File

@@ -0,0 +1,43 @@
using Content.Shared.White.Cult.UI;
using Robust.Client.GameObjects;
namespace Content.Client._White.Cult.UI.TeleportRunesList;
public sealed class TeleportRunesListWindowBUI : BoundUserInterface
{
private TeleportRunesListWindow? _window;
public TeleportRunesListWindowBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = new();
_window.OpenCentered();
_window.OnClose += Close;
_window.ItemSelected += (item, index) =>
{
var msg = new TeleportRunesListWindowItemSelectedMessage(item, index);
SendMessage(msg);
_window.Close();
};
if (State != null)
UpdateState(State);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is TeleportRunesListWindowBUIState newState)
{
_window?.PopulateList(newState.Items, newState.Label);
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Linq;
using Content.Client.Eui;
using Content.Client._White.Cult.UI.TeleportRunesList;
using Content.Shared.Eui;
using Content.Shared.White.Cult.UI;
namespace Content.Client._White.Cult.UI.TeleportSpell;
public sealed class TeleportSpellEui : BaseEui
{
private TeleportRunesListWindow _window;
public TeleportSpellEui()
{
_window = new TeleportRunesListWindow();
}
public override void Opened()
{
_window.OpenCentered();
_window.ItemSelected += (index, _) => SendMessage(new TeleportSpellTargetRuneSelected(){RuneUid = index});
_window.OnClose += () => SendMessage(new CloseEuiMessage());
base.Opened();
}
public override void Closed()
{
base.Closed();
_window.Close();
}
public override void HandleState(EuiStateBase state)
{
if(state is not TeleportSpellEuiState cast) return;
_window.Clear();
_window.PopulateList(cast.Runes.Keys.ToList(), cast.Runes.Values.ToList());
}
}

View File

@@ -0,0 +1,9 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Loc 'cult-torch-window-title'}"
MinWidth="300"
MinHeight="400">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Name="ItemsContainer" Orientation="Vertical"></BoxContainer>
</ScrollContainer>
</DefaultWindow>

View File

@@ -0,0 +1,35 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.Torch;
[GenerateTypedNameReferences]
public partial class TorchWindow : DefaultWindow
{
public Action<string, string>? ItemSelected;
public TorchWindow()
{
RobustXamlLoader.Load(this);
}
public void PopulateList(Dictionary<string, string> items)
{
ItemsContainer.RemoveAllChildren();
foreach (var item in items.Keys)
{
var button = new Button();
var itemName = items[item];
button.Text = itemName;
button.OnPressed += _ => ItemSelected?.Invoke(item, items[item]);
ItemsContainer.AddChild(button);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Data;
using Content.Shared.White.Cult.Items;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client._White.Cult.UI.Torch;
public sealed class TorchWindowBUI : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private TorchWindow? _window;
public TorchWindowBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = new();
_window.OpenCentered();
_window.OnClose += Close;
_window.ItemSelected += (uid, item) =>
{
var msg = new TorchWindowItemSelectedMessage(uid, item);
SendMessage(msg);
_window.Close();
};
if (State != null)
UpdateState(State);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is TorchWindowBUIState newState)
{
_window?.PopulateList(newState.Items);
}
}
}

View File

@@ -0,0 +1,18 @@
<Control
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Radial">
<BoxContainer>
<TextureButton
Name="Controller"
Access="Public"
VerticalExpand="True"
HorizontalExpand="True">
<TextureRect Access="Public" Name="BackgroundTexture"
VerticalExpand="True"
HorizontalExpand="True"
VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="KeepAspect">
</TextureRect>
</TextureButton>
</BoxContainer>
</Control>

View File

@@ -0,0 +1,51 @@
using System.Numerics;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Animations;
using Robust.Shared.Timing;
namespace Content.Client._White.UserInterface.Radial;
[GenerateTypedNameReferences, Virtual, PublicAPI]
public sealed partial class RadialButton : Control
{
[Animatable] public Vector2 Offset { get; set; }
public string? Content { get; set; }
public string Texture
{
set => Controller.TexturePath = value;
}
public string? Tooltip
{
set => Controller.ToolTip = value;
get => Controller.ToolTip;
}
public float? TooltipDelay
{
set => Controller.TooltipDelay = value;
get => Controller.TooltipDelay;
}
[Animatable]
public Vector2 ButtonSize
{
get => this.Size;
set => this.SetSize = value;
}
public RadialButton()
{
RobustXamlLoader.Load(this);
Offset = Vector2.Zero;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
}
}

View File

@@ -0,0 +1,17 @@
<controls:RadialContainer
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Radial"
Visible="False"
MaxSize="0 0">
<BoxContainer>
<controls:RadialButton
Name="CloseButton"
Access="Public"
VerticalExpand="True"
HorizontalExpand="True"
Content="{Loc 'radial-ui-close'}"
Texture="/Textures/Interface/Default/blocked.png"/>
<LayoutContainer Access="Public" Name="Layout" HorizontalExpand="True" VerticalExpand="True"></LayoutContainer>
<RichTextLabel Access="Public" Name="ActionLabel" HorizontalExpand="True" Visible="False"/>
</BoxContainer>
</controls:RadialContainer>

View File

@@ -0,0 +1,428 @@
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Content.Client.Message;
using Content.Client.Resources;
using Robust.Client.Animations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Animations;
using Robust.Shared.Console;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Client._White.UserInterface.Radial;
public sealed class RadialContainerCommandTest : LocalizedCommands
{
public override string Command => "radialtest";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
string[] tips =
{
"Testovый туултип. Здесь можете расписать разную инфу о кнопке/действии",
"Из окна дуло. Штирлиц закрыл окно. Дуло исчезло.",
"Негры пидорасы Негры пидорасы Негры пидорасы Негры пидорасы"
};
var radial = new RadialContainer();
for (int i = 0; i < 8; i++)
{
var testButton = radial.AddButton("Action " + i, "/Textures/Interface/emotions.svg.192dpi.png");
testButton.Tooltip = tips[IoCManager.Resolve<IRobustRandom>().Next(0, 2)];
testButton.Controller.OnPressed += (_) => { Logger.Debug("Press gay"); };
}
radial.CloseButton.Controller.OnPressed += (_) =>
{
Logger.Debug("Close event for your own logic");
};
radial.OpenAttachedLocalPlayer();
}
}
[GenerateTypedNameReferences, Virtual]
public partial class RadialContainer : Control
{
private EntityUid? _attachedEntity;
private bool _isAttached = false;
private bool _isOpened = false;
private Vector2 _focusSize = new Vector2(64, 64);
private Vector2 _normalSize = new Vector2(50, 50);
private float _moveAniTime = 0.3f;
private float _focusAniTime = 0.25f;
private string _backgroundTexture = "/Textures/Interface/Default/SlotBackground.png";
private const int MaxButtons = 8;
public const string MoveAnimationKey = "move";
public const string InSizeAnimationKey = "insize";
public const string OutSizeAnimationKey = "outsize";
public Action? Closed;
public float FocusSize
{
get => _focusSize.Y;
set => _focusSize = new Vector2(value, value);
}
public float NormalSize
{
get => _normalSize.Y;
set => _normalSize = new Vector2(value, value);
}
public float MoveAnimationTime
{
get => _moveAniTime;
set => _moveAniTime = value;
}
public float FocusAnimationTime
{
get => _focusAniTime;
set => _focusAniTime = value;
}
public bool IsAction = true;
public float VerticalOffset = 0.0f;
public float DistanceAvaible = 10.0f;
public RadialContainer() : base()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void Open(Vector2 position)
{
AddToRoot();
LayoutContainer.SetPosition(this, position);
UpdateButtons();
}
public void OpenCentered()
{
AddToRoot();
if (Parent != null)
LayoutContainer.SetPosition(this, (Parent.Size/2) - (this.Size/2));
else
LayoutContainer.SetPosition(this, (UserInterfaceManager.MainViewport.Size/2) - (this.Size/2));
UpdateButtons();
}
public void OpenCenteredLeft() => OpenCenteredAt(new Vector2(0.37f, 0.5f));
public void OpenCenteredAt(Vector2 position)
{
AddToRoot();
if (Parent == null)
return;
LayoutContainer.SetPosition(this, (Parent.Size * position) - (this.Size/2));
UpdateButtons();
}
/// <summary>
/// Open on attached entity in the world.
/// </summary>
public void OpenAttached(EntityUid uid)
{
if (uid == EntityUid.Invalid)
return;
AddToRoot();
_attachedEntity = uid;
_isAttached = true;
UpdateButtons();
}
/// <summary>
/// Open on our (player) attached entity.
/// </summary>
public void OpenAttachedLocalPlayer()
{
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
if (localPlayer == null)
return;
AddToRoot();
_attachedEntity = localPlayer.ControlledEntity;
_isAttached = true;
UpdateButtons();
}
public void Close(bool canDispose = true)
{
Parent?.RemoveChild(this);
Visible = false;
_isOpened = false;
Closed?.Invoke();
if (canDispose)
Dispose();
}
public RadialButton AddButton(string action, string? texture = null)
{
var button = new RadialButton();
button.Content = action;
button.Controller.TextureNormal = IoCManager.Resolve<IResourceCache>().GetTexture(_backgroundTexture);
if (texture != null)
button.BackgroundTexture.Texture = IoCManager.Resolve<IResourceCache>().GetTexture(texture);
Layout.AddChild(button);
return button;
}
public RadialButton AddButton(string action, Texture? texture)
{
var button = new RadialButton();
button.Content = action;
button.Controller.TextureNormal = IoCManager.Resolve<IResourceCache>().GetTexture(_backgroundTexture);
if (texture != null)
button.BackgroundTexture.Texture = texture;
Layout.AddChild(button);
return button;
}
private void AddToRoot()
{
if (_isOpened)
return;
UserInterfaceManager.WindowRoot.AddChild(this);
_isOpened = !_isOpened;
}
private void UpdateButtons()
{
Visible = true;
var angleDegrees = 360/Layout.ChildCount;
var stepAngle = -angleDegrees + -90;
var distance = GetDistance();
foreach (var child in Layout.Children)
{
var button = (RadialButton)child;
button.ButtonSize = _normalSize;
stepAngle += angleDegrees;
var pos = GetPointFromPolar(stepAngle, distance);
PlayRadialAnimation(button, pos, MoveAnimationKey);
button.Controller.OnMouseEntered += (_) =>
{
PlaySizeAnimation(button, _focusSize, OutSizeAnimationKey, InSizeAnimationKey);
ActionLabel.SetMarkup(button.Content ?? string.Empty);
ActionLabel.Visible = IsAction;
};
button.Controller.OnMouseExited += (_) =>
{
PlaySizeAnimation(button, _normalSize, InSizeAnimationKey, OutSizeAnimationKey);
ActionLabel.Visible = false;
};
}
CloseButton.ButtonSize = _normalSize;
CloseButton.Controller.OnMouseEntered += (_) =>
{
PlaySizeAnimation(CloseButton, _focusSize, OutSizeAnimationKey, InSizeAnimationKey);
ActionLabel.SetMarkup(CloseButton.Content ?? string.Empty);
ActionLabel.Visible = true;
};
CloseButton.Controller.OnMouseExited += (_) =>
{
PlaySizeAnimation(CloseButton, _normalSize, InSizeAnimationKey, OutSizeAnimationKey);
ActionLabel.Visible = false;
};
CloseButton.Controller.OnPressed += (_) =>
{
Close();
};
}
private void PlayRadialAnimation(Control button, Vector2 pos, string playKey)
{
var anim = new Animation
{
Length = TimeSpan.FromMilliseconds(_moveAniTime * 1000),
AnimationTracks =
{
new AnimationTrackControlProperty
{
Property = nameof(RadialButton.Offset),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(new Vector2(0,0), 0f),
new AnimationTrackProperty.KeyFrame(pos, _moveAniTime)
}
}
}
};
if (!button.HasRunningAnimation(playKey))
button.PlayAnimation(anim, playKey);
}
private void PlaySizeAnimation(Control button, Vector2 size, string playKey, string? stopKey)
{
var anim = new Animation
{
Length = TimeSpan.FromMilliseconds(_focusAniTime * 1000),
AnimationTracks =
{
new AnimationTrackControlProperty
{
Property = nameof(RadialButton.ButtonSize),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(button.Size, 0f),
new AnimationTrackProperty.KeyFrame(size, _focusAniTime)
}
}
}
};
if (stopKey != null && button.HasRunningAnimation(stopKey))
button.StopAnimation(stopKey);
if (!button.HasRunningAnimation(playKey))
button.PlayAnimation(anim, playKey);
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
foreach (var child in Layout.Children)
{
var button = (RadialButton)child;
LayoutContainer.SetPosition(child, button.Offset - (button.Size/2));
}
LayoutContainer.SetPosition(CloseButton, CloseButton.Offset - (CloseButton.Size/2));
LayoutContainer.SetPosition(ActionLabel,
new Vector2(0 - (GetTextWidth(ActionLabel) / 4), GetDistance(4.5f) ));
}
protected override void FrameUpdate(FrameEventArgs args)
{
if (!_isAttached)
return;
base.FrameUpdate(args);
var entityManager = IoCManager.Resolve<IEntityManager>();
var eyeManager = IoCManager.Resolve<IEyeManager>();
if (entityManager.Deleted(_attachedEntity))
{
Timer.Spawn(0, Die);
return;
}
if (!entityManager.TryGetComponent<TransformComponent>(_attachedEntity, out var xform) || xform.MapID != eyeManager.CurrentMap)
{
return;
}
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
if (localPlayer == null)
return;
// Check distance beetween entities
if (entityManager.TryGetComponent<TransformComponent>(localPlayer.ControlledEntity, out var myxform))
{
var onePoint = xform.WorldPosition;
var twoPoint = myxform.WorldPosition;
var distance = (onePoint - twoPoint).Length();
if (DistanceAvaible < distance)
{
Timer.Spawn(0, Die);
return;
}
}
var offset = (-eyeManager.CurrentEye.Rotation).ToWorldVec() * -VerticalOffset;
var worldPos = xform.WorldPosition + offset;
var lowerCenter = eyeManager.WorldToScreen(worldPos) / UIScale;
var screenPos = lowerCenter - new Vector2(DesiredSize.X / 2, DesiredSize.Y / 2);
// Round to nearest 0.5
screenPos = (screenPos * 2).Rounded() / 2;
LayoutContainer.SetPosition(this, screenPos);
}
private int GetTextWidth(RichTextLabel text)
{
var font = GetFont(text);
var msg = text.GetMessage();
if (msg == null)
return 0;
var width = 0;
foreach (var t in msg)
{
var metrics = font.GetCharMetrics(new Rune(t), 1);
if (metrics != null)
width += metrics.Value.Width + metrics.Value.Advance;
}
return width;
}
private void Die()
{
if (Disposed)
return;
Close();
}
private Font GetFont(Control element)
{
if (element.TryGetStyleProperty<Font>("font", out var font))
{
return font;
}
return UserInterfaceManager.ThemeDefaults.DefaultFont;
}
private float GetDistance(float offset = 0.0f)
{
var distance = FocusSize * 1.2f;
if (Layout.Children.Count() <= MaxButtons)
return distance + offset;
for (var i = 0; i < (Layout.Children.Count() - MaxButtons); i++)
{
distance += (NormalSize/3);
}
return distance + offset;
}
private static Vector2 GetPointFromPolar(double angleDegrees, double distance)
{
var angleRadians = angleDegrees * (Math.PI / 180.0);
var x = distance * Math.Cos(angleRadians);
var y = distance * Math.Sin(angleRadians);
return new Vector2((int)Math.Round(x), (int)Math.Round(y));
}
}

View File

@@ -337,7 +337,7 @@ public sealed class BloodstreamSystem : EntitySystem
/// <summary>
/// Attempts to modify the blood level of this entity directly.
/// </summary>
public bool TryModifyBloodLevel(EntityUid uid, FixedPoint2 amount, BloodstreamComponent? component = null)
public bool TryModifyBloodLevel(EntityUid uid, FixedPoint2 amount, BloodstreamComponent? component = null, bool createPuddle = true) // WD EDIT
{
if (!Resolve(uid, ref component, false))
return false;
@@ -358,7 +358,7 @@ public sealed class BloodstreamSystem : EntitySystem
tempSolution.AddSolution(newSol, _prototypeManager);
if (tempSolution.Volume > component.BleedPuddleThreshold)
if (tempSolution.Volume > component.BleedPuddleThreshold && createPuddle) //WD EDIT
{
// Pass some of the chemstream into the spilled blood.
if (_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution))

View File

@@ -0,0 +1,48 @@
using Content.Server.Chat.Systems;
using Content.Shared.Administration;
using Content.Shared.Chat;
using Content.Shared.White.Cult;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Enums;
namespace Content.Server.Chat.Commands
{
[AnyCommand]
internal sealed class CultCommand : IConsoleCommand
{
public string Command => "csay";
public string Description => "Send cult message";
public string Help => "csay <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.AttachedEntity is not { Valid: true } entity)
return;
if (player.Status != SessionStatus.InGame)
return;
if (args.Length < 1)
return;
var entityManager = IoCManager.Resolve<EntityManager>();
if (!entityManager.HasComponent<CultistComponent>(entity))
{
return;
}
var message = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(message))
return;
EntitySystem.Get<ChatSystem>().TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Cult, false, shell, player);
}
}
}

View File

@@ -30,6 +30,7 @@ using Content.Shared.Players;
using Content.Shared.Radio;
using Content.Shared.White;
using Content.Shared.Speech;
using Content.Shared.White.Cult;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
@@ -343,6 +344,9 @@ public sealed partial class ChatSystem : SharedChatSystem
case InGameOOCChatType.Looc:
SendLOOC(source, player, message, hideChat);
break;
case InGameOOCChatType.Cult:
SendCultChat(source, player, message, hideChat);
break;
}
}
@@ -701,6 +705,32 @@ public sealed partial class ChatSystem : SharedChatSystem
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}");
}
// WD EDIT
private void SendCultChat(EntityUid source, ICommonSession player, string message, bool hideChat)
{
var clients = GetCultChatClients();
var playerName = Name(source);
string wrappedMessage;
wrappedMessage = Loc.GetString("chat-manager-send-cult-chat-wrap-message",
("channelName", Loc.GetString("chat-manager-cult-channel-name")),
("player", playerName),
("message", FormattedMessage.EscapeText(message)));
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Cult chat from {player:Player}: {message}");
_chatManager.ChatMessageToMany(ChatChannel.Cult, message, wrappedMessage, source, hideChat, false, clients.ToList());
}
private IEnumerable<INetChannel> GetCultChatClients()
{
return Filter.Empty()
.AddWhereAttachedEntity(HasComp<GhostComponent>)
.AddWhereAttachedEntity(HasComp<CultistComponent>)
.Recipients
.Union(_adminManager.ActiveAdmins)
.Select(p => p.ConnectedClient);
}
private void SendDeadChat(EntityUid source, ICommonSession player, string message, bool hideChat)
{
var clients = GetDeadChatClients();
@@ -1104,7 +1134,8 @@ public enum InGameICChatType : byte
public enum InGameOOCChatType : byte
{
Looc,
Dead
Dead,
Cult
}
/// <summary>

View File

@@ -0,0 +1,88 @@
using System.Diagnostics;
using System.Linq;
using Content.Server.Mind;
using Content.Server.White.Cult.GameRule;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions;
[DataDefinition]
public sealed partial class KillCultistTarget : IObjectiveCondition
{
private IEntityManager EntityManager => IoCManager.Resolve<IEntityManager>();
protected EntityUid? TargetMindId;
protected MindComponent? TargetMind => EntityManager.GetComponentOrNull<MindComponent>(TargetMindId);
protected SharedJobSystem Jobs => EntityManager.System<SharedJobSystem>();
public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
var cultistRule = EntityManager.EntityQuery<CultRuleComponent>().FirstOrDefault();
Debug.Assert(cultistRule != null, nameof(cultistRule) + " != null");
var target = cultistRule.CultTarget;
return new KillCultistTarget()
{
TargetMindId = target
};
}
public string Title
{
get
{
var targetName = string.Empty;
var jobName = Jobs.MindTryGetJobName(TargetMindId) ?? "Unknown";
if (TargetMindId == null)
return Loc.GetString("objective-condition-kill-person-title", ("targetName", targetName), ("job", jobName));
if (TargetMind?.OwnedEntity is {Valid: true} owned)
targetName = EntityManager.GetComponent<MetaDataComponent>(owned).EntityName;
return Loc.GetString("objective-condition-kill-person-title", ("targetName", targetName), ("job", jobName));
}
}
public string Description => Loc.GetString("objective-condition-kill-person-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Weapons/Guns/Pistols/viper.rsi"), "icon");
public float Progress
{
get
{
var entityManager = IoCManager.Resolve<EntityManager>();
var mindSystem = entityManager.System<MindSystem>();
return TargetMindId == null || TargetMind == null || mindSystem.IsCharacterDeadIc(TargetMind!) ? 1f : 0f;
}
}
public float Difficulty => 2f;
public bool Equals(IObjectiveCondition? other)
{
return other is KillCultistTarget kpc && Equals(TargetMindId, kpc.TargetMindId);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
return obj.GetType() == GetType() && Equals((KillCultistTarget) obj);
}
public override int GetHashCode()
{
return TargetMindId?.GetHashCode() ?? 0;
}
}

View File

@@ -424,6 +424,34 @@ namespace Content.Server.RoundEnd
SetAutoCallTime();
}
}
//WD start
public void DelayCursedShuttle(TimeSpan delay)
{
if (_gameTicker.RunLevel != GameRunLevel.InRound)
return;
if (_countdownTokenSource == null)
return;
var countdown = ExpectedCountdownEnd - _gameTiming.CurTime + delay;
ExpectedCountdownEnd = _gameTiming.CurTime + countdown;
_countdownTokenSource.Cancel();
_countdownTokenSource = new ();
if (countdown != null)
Timer.Spawn(countdown.Value, _shuttle.CallEmergencyShuttle, _countdownTokenSource.Token);
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("round-end-system-shuttle-curse-delayed-announcement"),
Loc.GetString("Station"), colorOverride: Color.Gold);
}
public bool ShuttleCalled()
{
return ExpectedCountdownEnd != null;
}
//WD end
}
public sealed class RoundEndSystemChangedEvent : EntityEventArgs

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
namespace Content.Server.White.BecomeDustOnDeathSystem;
[RegisterComponent]
public sealed partial class BecomeDustOnDeathComponent : Component
{
[DataField("sprite", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SpawnOnDeathPrototype = "Ectoplasm";
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.Mobs;
namespace Content.Server.White.BecomeDustOnDeathSystem;
public sealed class BecomeDustOnDeathSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<BecomeDustOnDeathComponent, MobStateChangedEvent>(OnMobStateChanged);
}
private void OnMobStateChanged(EntityUid uid, BecomeDustOnDeathComponent component, MobStateChangedEvent args)
{
var xform = Transform(uid);
Spawn(component.SpawnOnDeathPrototype, xform.Coordinates);
QueueDel(uid);
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.White.Cult;
[RegisterComponent]
public sealed partial class ConstructComponent : Component
{
[DataField("actions", customTypeSerializer: typeof(PrototypeIdListSerializer<InstantActionPrototype>))]
public List<string> Actions = new();
}

View File

@@ -0,0 +1,74 @@
using Content.Server.GameTicking.Presets;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.White.Cult;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.White.Cult.GameRule;
[RegisterComponent]
public sealed partial class CultRuleComponent : Component
{
public readonly SoundSpecifier GreatingsSound = new SoundPathSpecifier("/Audio/White/Cult/blood_cult_greeting.ogg");
[DataField("cultPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<GamePresetPrototype>))]
public static string CultGamePresetPrototype = "Cult";
[DataField("cultistPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public static string CultistPrototypeId = "Cultist";
[DataField("reaperPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public static string ReaperPrototype = "ReaperConstruct";
[ViewVariables(VVAccess.ReadOnly), DataField("tileId")]
public static string CultFloor = "CultFloor";
[DataField("eyeColor")]
public static Color EyeColor = Color.FromHex("#f80000");
public static string HolyWaterReagent = "HolyWater";
[DataField("redEyeThreshold")]
public static int ReadEyeThreshold = 5;
[DataField("pentagramThreshold")]
public static int PentagramThreshold = 8;
public Dictionary<IPlayerSession, HumanoidCharacterProfile> StarCandidates = new();
[DataField("cultistStartingItems", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> StartingItems = new();
[DataField("cultistRolePrototype", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public string CultistRolePrototype = "Cultist";
/// <summary>
/// Players who played as an cultist at some point in the round.
/// </summary>
public Dictionary<string, string> CultistsList = new();
public EntityUid? CultTarget;
public List<CultistComponent> Cultists = new();
public List<ConstructComponent> Constructs = new();
public CultWinCondition WinCondition;
}
public enum CultWinCondition : byte
{
CultWin,
CultFailure
}
public sealed class CultNarsieSummoned : EntityEventArgs
{
}

View File

@@ -0,0 +1,492 @@
using System.Linq;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.NPC.Systems;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Body.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Objectives;
using Content.Shared.Players;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Shared._White;
using Content.Shared.Mind;
namespace Content.Server.White.Cult.GameRule;
public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly StorageSystem _storageSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly NpcFactionSystem _factionSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly JobSystem _jobSystem = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
private ISawmill _sawmill = default!;
private int _minimalCultists;
private int _cultGameRuleMinimapPlayers;
public override void Initialize()
{
base.Initialize();
_sawmill = Logger.GetSawmill("preset");
_minimalCultists = _cfg.GetCVar(WhiteCVars.CultMinStartingPlayers);
_cultGameRuleMinimapPlayers = _cfg.GetCVar(WhiteCVars.CultMinPlayers);
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersSpawned);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
SubscribeLocalEvent<CultNarsieSummoned>(OnNarsieSummon);
SubscribeLocalEvent<CultistComponent, ComponentInit>(OnCultistComponentInit);
SubscribeLocalEvent<CultistComponent, ComponentRemove>(OnCultistComponentRemoved);
SubscribeLocalEvent<CultistComponent, MobStateChangedEvent>(OnCultistsStateChanged);
}
private void OnCultistsStateChanged(EntityUid uid, CultistComponent component, MobStateChangedEvent ev)
{
if (ev.NewMobState == MobState.Dead)
{
CheckRoundShouldEnd();
}
}
public MindComponent? GetTarget()
{
var querry = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
if (cultRuleComponent.CultTarget.HasValue && TryComp<MindComponent>(cultRuleComponent.CultTarget.Value, out var mind))
{
return mind;
}
}
return null!;
}
public bool CanSummonNarsie()
{
var querry = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
var cultistsAmount = cultRuleComponent.Cultists.Count;
var constructsAmount = cultRuleComponent.Constructs.Count;
var enoughCultists = cultistsAmount + constructsAmount > 10;
if (!enoughCultists)
{
return false;
}
var target = GetTarget();
var targetKilled = target == null || _mindSystem.IsCharacterDeadIc(target);
if (targetKilled)
return true;
}
return false;
}
private void CheckRoundShouldEnd()
{
var querry = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
var aliveCultistsCount = 0;
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
foreach (var cultistComponent in cultRuleComponent.Cultists)
{
var owner = cultistComponent.Owner;
if (!TryComp<MobStateComponent>(owner, out var mobState))
continue;
if (_mobStateSystem.IsAlive(owner, mobState))
{
aliveCultistsCount++;
}
}
}
if (aliveCultistsCount == 0)
{
_roundEndSystem.EndRound();
}
}
private void OnCultistComponentInit(EntityUid uid, CultistComponent component, ComponentInit args)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!GameTicker.IsGameRuleAdded(ruleEnt))
continue;
if (!TryComp<MindContainerComponent>(uid, out var mindComponent))
return;
if (!mindComponent.HasMind)
return;
cultRuleComponent.Cultists.Add(component);
if (TryComp<ActorComponent>(component.Owner, out var actor))
{
cultRuleComponent.CultistsList.Add(MetaData(component.Owner).EntityName, actor.PlayerSession.Name);
}
var traitorRole = new TraitorRoleComponent()
{
PrototypeId = cultRuleComponent.CultistRolePrototype
};
_roleSystem.MindAddRole(mindComponent.Mind.Value, traitorRole);
UpdateCultistsAppearance(cultRuleComponent);
}
}
private void OnCultistComponentRemoved(EntityUid uid, CultistComponent component, ComponentRemove args)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!GameTicker.IsGameRuleAdded(ruleEnt))
continue;
cultRuleComponent.Cultists.Remove(component);
RemoveCultistAppearance(component);
CheckRoundShouldEnd();
}
}
private void RemoveCultistAppearance(CultistComponent component)
{
if (TryComp<HumanoidAppearanceComponent>(component.Owner, out var appearanceComponent))
{
//Потому что я так сказал
appearanceComponent.EyeColor = Color.White;
Dirty(appearanceComponent);
}
RemComp<PentagramComponent>(component.Owner);
}
private void UpdateCultistsAppearance(CultRuleComponent cultRuleComponent)
{
var cultistsCount = cultRuleComponent.Cultists.Count;
var constructsCount = cultRuleComponent.Constructs.Count;
var totalCultMembers = cultistsCount + constructsCount;
if (totalCultMembers < CultRuleComponent.ReadEyeThreshold)
return;
foreach (var cultistComponent in cultRuleComponent.Cultists)
{
if (TryComp<HumanoidAppearanceComponent>(cultistComponent.Owner, out var appearanceComponent))
{
appearanceComponent.EyeColor = CultRuleComponent.EyeColor;
Dirty(appearanceComponent);
}
if (totalCultMembers < CultRuleComponent.PentagramThreshold)
return;
EnsureComp<PentagramComponent>(cultistComponent.Owner);
}
}
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
var querry = EntityQuery<CultRuleComponent>();
foreach (var cultRuleComponent in querry)
{
var winText = Loc.GetString($"cult-cond-{cultRuleComponent.WinCondition.ToString().ToLower()}");
ev.AddLine(winText);
ev.AddLine(Loc.GetString("cultists-list-start"));
foreach (var (entityName, ckey) in cultRuleComponent.CultistsList)
{
var lising = Loc.GetString("cultists-list-name", ("name", entityName), ("user", ckey));
ev.AddLine(lising);
}
}
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
continue;
var minPlayers = _cultGameRuleMinimapPlayers;
if (!ev.Forced && ev.Players.Length < minPlayers)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players",
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
ev.Cancel();
continue;
}
if (ev.Players.Length == 0)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
ev.Cancel();
}
}
}
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var cultRule, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
continue;
foreach (var player in ev.Players)
{
if (!ev.Profiles.ContainsKey(player.UserId))
continue;
cultRule.StarCandidates[player] = ev.Profiles[player.UserId];
}
var potentialCultists = FindPotentialCultist(cultRule.StarCandidates);
var pickedCultist = PickCultists(potentialCultists);
var potentialTargets = FindPotentialTargets(pickedCultist);
cultRule.CultTarget = _random.PickAndTake(potentialTargets).Mind;
foreach (var pickerCultist in pickedCultist)
{
MakeCultist(pickerCultist);
}
}
}
private List<MindContainerComponent> FindPotentialTargets(List<IPlayerSession> exclude = null!)
{
var querry = EntityManager.EntityQuery<MindContainerComponent, HumanoidAppearanceComponent, ActorComponent>();
var potentialTargets = new List<MindContainerComponent>();
foreach (var (mind, _, actor) in querry)
{
var entity = mind.Mind;
if (entity == default)
continue;
if (exclude?.Contains(actor.PlayerSession) is true)
{
continue;
}
potentialTargets.Add(mind);
}
return potentialTargets;
}
private List<IPlayerSession> FindPotentialCultist(in Dictionary<IPlayerSession, HumanoidCharacterProfile> candidates)
{
var list = new List<IPlayerSession>();
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
foreach (var player in candidates.Keys)
{
// Role prevents antag.
if (!_jobSystem.CanBeAntag(player)) continue;
// Latejoin
if (player.AttachedEntity != null && pendingQuery.HasComponent(player.AttachedEntity.Value))
continue;
list.Add(player);
}
var prefList = new List<IPlayerSession>();
foreach (var player in list)
{
var profile = candidates[player];
if (profile.AntagPreferences.Contains(CultRuleComponent.CultistPrototypeId))
{
prefList.Add(player);
}
}
if (prefList.Count == 0)
{
_sawmill.Info("Insufficient preferred cultists, picking at random.");
prefList = list;
}
if (prefList.Count >= _minimalCultists)
{
return prefList;
}
var playersToAdd = _minimalCultists - prefList.Count;
foreach (var prefPlayer in prefList)
{
list.Remove(prefPlayer);
}
for (var i = 0; i < playersToAdd; i++)
{
var randomPlayer = _random.PickAndTake(list);
prefList.Add(randomPlayer);
}
return prefList;
}
private List<IPlayerSession> PickCultists(List<IPlayerSession> prefList)
{
var result = new List<IPlayerSession>();
if (prefList.Count == 0)
{
_sawmill.Info("Insufficient ready players to fill up with cultists, stopping the selection.");
return result;
}
var minCultists = _cfg.GetCVar(WhiteCVars.CultMinPlayers);
var maxCultists = _cfg.GetCVar(WhiteCVars.CultMaxStartingPlayers);
var actualCultistCount = prefList.Count > maxCultists ? maxCultists : minCultists;
for (var i = 0; i < actualCultistCount; i++)
{
result.Add(_random.PickAndTake(prefList));
}
return result;
}
public bool MakeCultist(IPlayerSession cultist)
{
var cultistRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
if (cultistRule == null)
{
GameTicker.StartGameRule(CultRuleComponent.CultGamePresetPrototype, out var ruleEntity);
cultistRule = Comp<CultRuleComponent>(ruleEntity);
}
var mind = cultist.Data.ContentData()?.Mind;
if (mind == null)
{
_sawmill.Info("Failed getting mind for picked cultist.");
return false;
}
var playerEntity = cultist.AttachedEntity;
if (!playerEntity.HasValue)
{
_sawmill.Error("Mind picked for cultist did not have an attached entity.");
return false;
}
var mindComponent = Comp<MindComponent>(mind.Value);
DebugTools.AssertNotNull(playerEntity.Value);
EnsureComp<CultistComponent>(playerEntity.Value);
_factionSystem.RemoveFaction(playerEntity.Value, "NanoTrasen", false);
_factionSystem.AddFaction(playerEntity.Value, "Cultist");
if (_inventorySystem.TryGetSlotEntity(playerEntity.Value, "back", out var backPack))
{
foreach (var itemPrototype in cultistRule.StartingItems)
{
var itemEntity = Spawn(itemPrototype, Transform(playerEntity.Value).Coordinates);
if (backPack != null)
{
_storageSystem.Insert(backPack.Value, itemEntity);
}
}
}
_audioSystem.PlayGlobal(cultistRule.GreatingsSound, Filter.Empty().AddPlayer(cultist), false,
AudioParams.Default);
_chatManager.DispatchServerMessage(cultist, Loc.GetString("cult-role-greeting"));
if (_prototypeManager.TryIndex<ObjectivePrototype>("CultistKillObjective", out var cultistObjective))
{
_mindSystem.TryAddObjective(mind.Value, mindComponent,cultistObjective);
}
return true;
}
private void OnNarsieSummon(CultNarsieSummoned ev)
{
var query = EntityQuery<MobStateComponent, MindContainerComponent, CultistComponent>().ToList();
foreach (var (mobState, mindContainer, _) in query)
{
if (!mindContainer.HasMind || mindContainer.Mind is null)
{
continue;
}
var reaper = Spawn(CultRuleComponent.ReaperPrototype, Transform(mobState.Owner).Coordinates);
_mindSystem.TransferTo(mindContainer.Mind.Value, reaper);
_bodySystem.GibBody(mobState.Owner);
}
_roundEndSystem.EndRound();
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.HolyWater;
[RegisterComponent]
public sealed partial class BibleWaterConvertComponent : Component
{
[DataField("convertedId"), ViewVariables(VVAccess.ReadWrite)]
public string ConvertedId = "Water";
[DataField("ConvertedToId"), ViewVariables(VVAccess.ReadWrite)]
public string ConvertedToId = "HolyWater";
}

View File

@@ -0,0 +1,54 @@
using System.Threading;
using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.IdentityManagement;
using Content.Shared.White.Cult;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.White.Cult.HolyWater;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public sealed partial class DeconvertCultist : ReagentEffect
{
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("reagent-effect-guidebook-deconvert-cultist");
}
public override void Effect(ReagentEffectArgs args)
{
var uid = args.SolutionEntity;
if (!args.EntityManager.TryGetComponent(uid, out CultistComponent? component))
return;
if (component.HolyConvertToken != null)
return;
args.EntityManager.System<StunSystem>()
.TryParalyze(uid, TimeSpan.FromSeconds(component.HolyConvertTime + 5f), true);
var target = Identity.Name(uid, args.EntityManager);
args.EntityManager.System<PopupSystem>()
.PopupEntity(Loc.GetString("holy-water-started-converting", ("target", target)), uid);
component.HolyConvertToken = new CancellationTokenSource();
Timer.Spawn(TimeSpan.FromSeconds(component.HolyConvertTime), () => ConvertCultist(uid, args.EntityManager),
component.HolyConvertToken.Token);
}
private void ConvertCultist(EntityUid uid, IEntityManager entityManager)
{
if (!entityManager.TryGetComponent<CultistComponent>(uid, out var cultist))
return;
cultist.HolyConvertToken = null;
entityManager.RemoveComponent<CultistComponent>(uid);
entityManager.RemoveComponent<PentagramComponent>(uid);
}
}

View File

@@ -0,0 +1,54 @@
using System.Linq;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Stunnable;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.HolyWater;
public sealed class HolyWaterSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly AudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BibleWaterConvertComponent, AfterInteractEvent>(OnBibleInteract);
}
private void OnBibleInteract(EntityUid uid, BibleWaterConvertComponent component, AfterInteractEvent args)
{
if (HasComp<MobStateComponent>(uid))
return;
if (!TryComp<SolutionContainerManagerComponent>(args.Target, out var container))
return;
foreach (var solution in container.Solutions.Values.Where(solution => solution.ContainsReagent(component.ConvertedId, null)))
{
foreach (var reagent in solution.Contents)
{
if (reagent.Reagent.Prototype != component.ConvertedId)
continue;
var amount = reagent.Quantity;
solution.RemoveReagent(reagent.Reagent.Prototype, reagent.Quantity);
solution.AddReagent(component.ConvertedToId, amount);
if (args.Target == null)
return;
_popup.PopupEntity(Loc.GetString("holy-water-converted"), args.Target.Value, args.User);
_audio.PlayPvs("/Audio/Effects/holy.ogg", args.Target.Value);
return;
}
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class CultRobeModifierComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("speedModifier")]
public float SpeedModifier = 1.45f;
[ViewVariables(VVAccess.ReadOnly), DataField("damageModifierSetId")]
public string DamageModifierSetId = "CultRobe";
public string? StoredDamageSetId { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class ReturnItemOnThrowComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("stunTime")]
public float StunTime = 1f;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class ShuttleCurseComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("delayTime")]
public TimeSpan DelayTime = TimeSpan.FromSeconds(120);
[ViewVariables(VVAccess.ReadWrite), DataField("cooldown")]
public TimeSpan Cooldown = TimeSpan.FromSeconds(180);
}

View File

@@ -0,0 +1,27 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult.Items;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class TorchCultistsProviderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CultTeleporterUiKey.Key);
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? ItemSelected;
[ViewVariables(VVAccess.ReadWrite), DataField("cooldown")]
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
[ViewVariables(VVAccess.ReadWrite), DataField("usesLeft")]
public int UsesLeft = 3;
[ViewVariables(VVAccess.ReadOnly)]
public TimeSpan NextUse = TimeSpan.Zero;
[ViewVariables(VVAccess.ReadOnly)]
public bool Active = true;
}

View File

@@ -0,0 +1,81 @@
using Content.Server.White.Cult.Items.Components;
using Content.Shared.Damage;
using Content.Shared.Inventory.Events;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class CultRobeModifierSystem : EntitySystem
{
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CultRobeModifierComponent, GotEquippedEvent>(OnEquip);
SubscribeLocalEvent<CultRobeModifierComponent, GotUnequippedEvent>(OnUnequip);
}
private void OnEquip(EntityUid uid, CultRobeModifierComponent component, GotEquippedEvent args)
{
if (!HasComp<CultistComponent>(args.Equipee))
return;
if (args.Slot != "outerClothing")
return;
ModifySpeed(args.Equipee, component, true);
ModifyDamage(args.Equipee, component, true);
}
private void OnUnequip(EntityUid uid, CultRobeModifierComponent component, GotUnequippedEvent args)
{
if (!HasComp<CultistComponent>(args.Equipee))
return;
if (args.Slot != "outerClothing")
return;
ModifySpeed(args.Equipee, component, false);
ModifyDamage(args.Equipee, component, false);
}
private void ModifySpeed(EntityUid uid, CultRobeModifierComponent comp, bool increase)
{
if (!TryComp<MovementSpeedModifierComponent>(uid, out var move))
return;
var walkSpeed = increase ? move.BaseWalkSpeed * comp.SpeedModifier : move.BaseWalkSpeed / comp.SpeedModifier;
var sprintSpeed =
increase ? move.BaseSprintSpeed * comp.SpeedModifier : move.BaseSprintSpeed / comp.SpeedModifier;
_movement.ChangeBaseSpeed(uid, walkSpeed, sprintSpeed, move.Acceleration, move);
}
private void ModifyDamage(EntityUid uid, CultRobeModifierComponent comp, bool increase)
{
var damageSet = string.Empty;
if (increase)
{
if (!TryComp<DamageableComponent>(uid, out var damage))
return;
comp.StoredDamageSetId = damage.DamageModifierSetId;
damageSet = comp.DamageModifierSetId;
}
else
{
if (comp.StoredDamageSetId != null)
damageSet = comp.StoredDamageSetId;
comp.StoredDamageSetId = null;
}
_damageable.SetDamageModifierSetId(uid, damageSet);
}
}

View File

@@ -0,0 +1,42 @@
using Content.Server.Hands.Systems;
using Content.Server.Stunnable;
using Content.Server.White.Cult.Items.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Throwing;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class ReturnItemOnThrowSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly HandsSystem _hands = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReturnItemOnThrowComponent, ThrowDoHitEvent>(OnThrowHit);
}
private void OnThrowHit(EntityUid uid, ReturnItemOnThrowComponent component, ThrowDoHitEvent args)
{
var isCultist = HasComp<CultistComponent>(args.Target);
var thrower = args.Component.Thrower;
if (!HasComp<CultistComponent>(thrower))
return;
if (!HasComp<MobStateComponent>(args.Target))
return;
if (!_stun.IsParalyzed(args.Target))
{
if (!isCultist)
{
_stun.TryParalyze(args.Target, TimeSpan.FromSeconds(component.StunTime), true);
}
}
_hands.PickupOrDrop(thrower, uid);
}
}

View File

@@ -0,0 +1,81 @@
using Content.Server.Chat.Systems;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Systems;
using Content.Server.White.Cult.Items.Components;
using Content.Shared.GameTicking;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.White.Cult;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class ShuttleCurseSystem : EntitySystem
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
private const int MaxCurses = 3;
private int _currentCurses = 0;
private TimeSpan? _nextCurse = TimeSpan.Zero;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ShuttleCurseComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<RoundEndedEvent>(OnRoundEnd);
}
private void OnRoundEnd(RoundEndedEvent ev)
{
_currentCurses = 0;
_nextCurse = TimeSpan.Zero;
}
private void OnUse(EntityUid uid, ShuttleCurseComponent component, UseInHandEvent args)
{
if (!HasComp<CultistComponent>(args.User))
{
_hands.TryDrop(args.User);
_popup.PopupEntity(Loc.GetString("shuttle-curse-not-cultist"), args.User, args.User);
return;
}
if (!_roundEnd.ShuttleCalled())
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-shuttle-not-called"), args.User, args.User);
return;
}
if (_currentCurses >= MaxCurses)
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-max-curses"), args.User, args.User);
return;
}
if (_nextCurse > _gameTiming.CurTime)
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-cooldown"), args.User, args.User);
return;
}
var shuttle = _entMan.System<EmergencyShuttleSystem>();
if (shuttle.EmergencyShuttleArrived)
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-shuttle-arrived"), args.User, args.User);
return;
}
_roundEnd.DelayCursedShuttle(component.DelayTime);
_popup.PopupEntity(Loc.GetString("shuttle-curse-shuttle-delayed"), args.User, args.User);
_currentCurses++;
_nextCurse = _gameTiming.CurTime + component.Cooldown;
}
}

View File

@@ -0,0 +1,274 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Popups;
using Content.Server.Pulling;
using Content.Server.Station.Systems;
using Content.Server.Station.Components;
using Content.Server.White.Cult.Items.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Mind.Components;
using Content.Shared.Physics;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Items;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class TorchCultistsProviderSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TorchCultistsProviderComponent, AfterInteractEvent>(OnInteract);
SubscribeLocalEvent<TorchCultistsProviderComponent, TorchWindowItemSelectedMessage>(OnCultistSelected);
SubscribeLocalEvent<TorchCultistsProviderComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, TorchCultistsProviderComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
private void OnInteract(EntityUid uid, TorchCultistsProviderComponent comp, AfterInteractEvent args)
{
if (!args.Target.HasValue)
{
return;
}
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target.Value))
{
return;
}
if (!TryComp<TorchCultistsProviderComponent>(uid, out var provider))
return;
if (!HasComp<CultistComponent>(args.User))
{
_hands.TryDrop(args.User);
_popup.PopupEntity(Loc.GetString("cult-torch-not-cultist"), args.User, args.User);
return;
}
if (!provider.Active || provider.UsesLeft <= 0)
{
_popup.PopupEntity(Loc.GetString("cult-torch-drained"), args.User, args.User);
return;
}
if (provider.NextUse > _timing.CurTime)
{
_popup.PopupEntity(Loc.GetString("cult-torch-cooldown"), args.User, args.User);
return;
}
if (HasComp<MindContainerComponent>(args.Target))
{
TeleportToRandomLocation(uid, args, comp);
return;
}
if (!HasComp<ItemComponent>(args.Target))
{
return;
}
if (provider.UserInterface == null)
return;
provider.ItemSelected = args.Target;
var cultists = EntityQuery<CultistComponent>();
var list = new Dictionary<string, string>();
foreach (var cultist in cultists)
{
if (!TryComp<MetaDataComponent>(cultist.Owner, out var meta))
return;
if (cultist.Owner == args.User)
continue;
list.Add(meta.Owner.ToString(), meta.EntityName);
}
if (list.Count == 0)
{
_popup.PopupEntity(Loc.GetString("cult-torch-cultists-not-found"), args.User, args.User);
return;
}
UserInterfaceSystem.SetUiState(provider.UserInterface, new TorchWindowBUIState(list));
if (!TryComp<ActorComponent>(args.User, out var actorComponent))
return;
_ui.ToggleUi(provider.UserInterface, actorComponent.PlayerSession);
}
private void OnCultistSelected(
EntityUid uid,
TorchCultistsProviderComponent component,
TorchWindowItemSelectedMessage args)
{
var entityUid = args.Session.AttachedEntity;
var cultists = EntityQuery<CultistComponent>();
foreach (var cultist in cultists)
{
if (cultist.Owner.ToString() == args.EntUid)
entityUid = cultist.Owner;
}
if (entityUid == args.Session.AttachedEntity && entityUid != null)
{
_popup.PopupEntity(Loc.GetString("cult-torch-no-cultist"), entityUid.Value, entityUid.Value);
return;
}
if (component.ItemSelected != null)
{
var item = component.ItemSelected.Value;
if (!TryComp<TransformComponent>(entityUid, out var xForm))
return;
_xform.SetCoordinates(item, xForm.Coordinates);
_hands.PickupOrDrop(entityUid, item);
}
UpdateUsesCount(uid, args.Session.AttachedEntity, component);
}
private void UpdateAppearance(EntityUid uid, TorchCultistsProviderComponent component)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, VoidTorchVisuals.Activated, component.Active, appearance);
}
private void TeleportToRandomLocation(EntityUid torch, InteractEvent args, TorchCultistsProviderComponent component)
{
if (!args.Target.HasValue)
{
return;
}
if (_pulling.GetPulled(args.User) != args.Target.Value)
{
return;
}
var ownerTransform = Transform(args.User);
if (_station.GetStationInMap(ownerTransform.MapID) is not { } station ||
!TryComp<StationDataComponent>(station, out var data) ||
_station.GetLargestGrid(data) is not { } grid)
{
if (ownerTransform.GridUid == null)
return;
grid = ownerTransform.GridUid.Value;
}
if (!TryComp<MapGridComponent>(grid, out var gridComp))
{
return;
}
var gridTransform = Transform(grid);
var gridBounds = gridComp.LocalAABB.Scale(0.7f); // чтобы не заспавнить на самом краю станции
var targetCoords = gridTransform.Coordinates;
for (var i = 0; i < 25; i++)
{
var randomX = _random.Next((int) gridBounds.Left, (int) gridBounds.Right);
var randomY = _random.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
var tile = new Vector2i(randomX, randomY);
// no air-blocked areas.
if (_atmosphere.IsTileSpace(grid, gridTransform.MapUid, tile, mapGridComp: gridComp) ||
_atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp))
{
continue;
}
// don't spawn inside of solid objects
var physQuery = GetEntityQuery<PhysicsComponent>();
var valid = true;
foreach (var ent in gridComp.GetAnchoredEntities(tile))
{
if (!physQuery.TryGetComponent(ent, out var body))
continue;
if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.LargeMobMask) == 0)
continue;
valid = false;
break;
}
if (!valid)
continue;
targetCoords = gridComp.GridTileToLocal(tile);
break;
}
_xform.SetCoordinates(args.User, targetCoords);
_xform.SetCoordinates(args.Target.Value, targetCoords);
UpdateUsesCount(torch, args.User, component);
}
private void UpdateUsesCount(EntityUid torch, EntityUid? user, TorchCultistsProviderComponent component)
{
component.ItemSelected = null;
component.NextUse = _timing.CurTime + component.Cooldown;
component.UsesLeft--;
if (user.HasValue)
{
_popup.PopupEntity(Loc.GetString("cult-torch-item-send"), user.Value);
}
if (component.UsesLeft <= 0)
{
component.Active = false;
UpdateAppearance(torch, component);
if (!TryComp<PointLightComponent>(torch, out var light))
return;
_pointLight.SetEnabled(torch, false, light);
}
}
}

View File

@@ -0,0 +1,160 @@
using System.Threading;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Pulling.Components;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Items;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class VoidTeleportSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VoidTeleportComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<VoidTeleportComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, VoidTeleportComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
private void OnUseInHand(EntityUid uid, VoidTeleportComponent component, UseInHandEvent args)
{
if (!HasComp<CultistComponent>(args.User))
{
_hands.TryDrop(args.User);
_popup.PopupEntity(Loc.GetString("void-teleport-not-cultist"), args.User, args.User);
return;
}
if (!component.Active || component.UsesLeft <= 0)
{
_popup.PopupEntity(Loc.GetString("void-teleport-drained"), args.User, args.User);
return;
}
if (component.NextUse > _timing.CurTime)
{
_popup.PopupEntity(Loc.GetString("void-teleport-cooldown"), args.User, args.User);
return;
}
if (!TryComp<TransformComponent>(args.User, out var transform))
return;
var oldCoords = transform.Coordinates;
EntityCoordinates coords = default;
var attempts = 10;
//Repeat until proper place for tp is found
while (attempts <= 10)
{
attempts--;
//Get coords to where tp
var random = new Random().Next(component.MinRange, component.MaxRange);
var offset = transform.LocalRotation.ToWorldVec().Normalized();
var direction = transform.LocalRotation.GetDir().ToVec();
var newOffset = offset + direction * random;
coords = transform.Coordinates.Offset(newOffset).SnapToGrid(EntityManager);
var tile = coords.GetTileRef();
//Check for walls
if (tile != null && _turf.IsTileBlocked(tile.Value, CollisionGroup.AllMask))
continue;
break;
}
CreatePulse(uid, component);
_xform.SetCoordinates(args.User, coords);
transform.AttachToGridOrMap();
var pulled = GetPulledEntity(args.User);
if (pulled != null)
{
_xform.SetCoordinates(pulled.Value, coords);
if (TryComp<TransformComponent>(pulled.Value, out var pulledTransform))
pulledTransform.AttachToGridOrMap();
}
//Play tp sound
_audio.PlayPvs(component.TeleportInSound, coords);
_audio.PlayPvs(component.TeleportOutSound,oldCoords);
//Create tp effect
_entMan.SpawnEntity(component.TeleportInEffect, coords);
_entMan.SpawnEntity(component.TeleportOutEffect, oldCoords);
component.UsesLeft--;
component.NextUse = _timing.CurTime + component.Cooldown;
}
private void UpdateAppearance(EntityUid uid, VoidTeleportComponent comp)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, VeilVisuals.Activated, comp.Active, appearance);
}
private EntityUid? GetPulledEntity(EntityUid user)
{
EntityUid? pulled = null;
if (TryComp<SharedPullerComponent>(user, out var puller))
pulled = puller.Pulling;
return pulled;
}
private void CreatePulse(EntityUid uid, VoidTeleportComponent component)
{
if (TryComp<PointLightComponent>(uid, out var light))
light.Energy = 5f;
Timer.Spawn(component.TimerDelay, () => TurnOffPulse(uid, component), component.Token.Token);
}
private void TurnOffPulse(EntityUid uid ,VoidTeleportComponent comp)
{
if (!TryComp<PointLightComponent>(uid, out var light))
return;
light.Energy = 1f;
comp.Token = new CancellationTokenSource();
if (comp.UsesLeft <= 0)
{
comp.Active = false;
UpdateAppearance(uid, comp);
light.Enabled = false;
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.White.Cult.Pentagram;
using Robust.Shared.GameStates;
namespace Content.Server.White.Cult;
[NetworkedComponent, RegisterComponent]
public sealed partial class PentagramComponent : SharedPentagramComponent
{
}

View File

@@ -0,0 +1,264 @@
using System.Linq;
using System.Numerics;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Maps;
using Content.Shared.Damage;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Mobs.Systems;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Tag;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Pylon;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.Pylon;
public sealed class PylonSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly BloodstreamSystem _blood = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedPylonComponent, InteractHandEvent>(OnInteract);
SubscribeLocalEvent<SharedPylonComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, SharedPylonComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var pylonsQuery = EntityQuery<SharedPylonComponent>();
foreach (var comp in pylonsQuery)
{
if (comp.NextTileConvert == TimeSpan.Zero)
comp.NextTileConvert = _timing.CurTime + TimeSpan.FromSeconds(comp.TileConvertCooldown);
if (comp.NextHealTime == TimeSpan.Zero)
comp.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(comp.HealingAuraCooldown);
if (_timing.CurTime >= comp.NextHealTime)
{
comp.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(comp.HealingAuraCooldown);
if (comp.Activated)
HealPlayersInRange(comp);
}
if (_timing.CurTime >= comp.NextTileConvert)
{
comp.NextTileConvert = _timing.CurTime + TimeSpan.FromSeconds(comp.TileConvertCooldown);
if (comp.Activated)
ConvertNearbyTiles(comp);
}
}
}
private void ConvertNearbyTiles(SharedPylonComponent comp)
{
var tilesConverted = 0;
var random = new Random().Next(1, 3);
var uid = comp.Owner;
var gridUid = Transform(uid).GridUid;
var pylonPos = Transform(uid).Coordinates;
if (!_mapManager.TryGetGrid(gridUid, out var grid))
return;
var radius = comp.TileConvertRange;
var tilesRefs = grid.GetLocalTilesIntersecting(new Box2(pylonPos.Position + new Vector2(-radius, -radius),
pylonPos.Position + new Vector2(radius, radius)));
var tiles = ShuffleTiles(tilesRefs);
if (comp.ConvertEverything)
ConvertEverything(comp, tiles);
var cultTileDef = (ContentTileDefinition) _tileDefinition[$"{comp.TileId}"];
var cultTile = new Tile(cultTileDef.TileId);
foreach (var tile in tiles)
{
if (tilesConverted >= random)
return;
var tilePos = _turf.GetTileCenter(tile);
if (pylonPos.InRange(EntityManager, tilePos, comp.TileConvertRange))
{
if (tile.Tile.TypeId == cultTile.TypeId)
continue;
_tile.ReplaceTile(tile, cultTileDef);
_entMan.SpawnEntity(comp.TileConvertEffect, tilePos);
_audio.PlayPvs(comp.ConvertTileSound, tilePos, AudioParams.Default.WithVolume(-5));
tilesConverted++;
}
}
}
private void ConvertEverything(SharedPylonComponent comp, IEnumerable<TileRef> tiles)
{
foreach (var tile in tiles)
{
if (!_turf.IsTileBlocked(tile, CollisionGroup.WallLayer)
|| !_turf.IsTileBlocked(tile, CollisionGroup.AirlockLayer))
continue;
var posss = _turf.GetTileCenter(tile);
foreach (var entity in _lookup.GetEntitiesIntersecting(posss))
{
if (TryComp<TagComponent>(entity, out var tag)
&& tag.Tags.Contains("Wall")
&& MetaData(entity).EntityPrototype?.ID != comp.WallId)
{
_entMan.SpawnEntity(comp.WallId, Transform(entity).Coordinates);
_entMan.SpawnEntity(comp.WallConvertEffect, Transform(entity).Coordinates);
_entMan.DeleteEntity(entity);
_audio.PlayPvs(comp.ConvertTileSound, posss, AudioParams.Default.WithVolume(-10));
return;
}
if (HasComp<AirlockComponent>(entity) && MetaData(entity).EntityPrototype?.ID != comp.AirlockId)
{
_entMan.SpawnEntity(comp.AirlockId, Transform(entity).Coordinates);
_entMan.SpawnEntity(comp.AirlockConvertEffect, Transform(entity).Coordinates);
_entMan.DeleteEntity(entity);
_audio.PlayPvs(comp.ConvertTileSound, posss, AudioParams.Default.WithVolume(-10));
return;
}
}
}
}
private void HealPlayersInRange(SharedPylonComponent comp)
{
foreach (var player in _playerManager.Sessions)
{
if (player.AttachedEntity is not { Valid: true } playerEntity)
continue;
if (!EntityManager.TryGetComponent<CultistComponent>(playerEntity, out _))
continue;
if (_mobStateSystem.IsDead(playerEntity))
continue;
var playerDamageComp = EntityManager.TryGetComponent<DamageableComponent>(playerEntity, out var damageComp)
? damageComp
: null;
if (playerDamageComp == null || playerDamageComp.Damage.Total == 0)
continue;
var uid = comp.Owner;
var pylonXForm = Transform(uid);
var playerXForm = Transform(playerEntity);
if (pylonXForm.Coordinates.InRange(EntityManager, playerXForm.Coordinates, comp.HealingAuraRange))
{
var damage = comp.HealingAuraDamage;
_damageSystem.TryChangeDamage(playerEntity, damage, true);
if (!TryComp<BloodstreamComponent>(playerEntity, out var bloodstream))
continue;
if (bloodstream.IsBleeding)
{
_blood.TryModifyBleedAmount(playerEntity, -comp.BleedReductionAmount, bloodstream);
}
if (_blood.GetBloodLevelPercentage(playerEntity, bloodstream) < bloodstream.BloodMaxVolume)
{
_blood.TryModifyBloodLevel(playerEntity, comp.BloodRefreshAmount, bloodstream);
}
}
}
}
private void OnInteract(EntityUid uid, SharedPylonComponent comp, InteractHandEvent args)
{
var user = args.User;
var pylon = args.Target;
if (HasComp<CultistComponent>(user))
{
comp.Activated = !comp.Activated;
UpdateAppearance(uid, comp);
if (!TryComp<PointLightComponent>(uid, out var light))
return;
light.Enabled = comp.Activated;
var toggleMsg = Loc.GetString(comp.Activated ? "pylon-toggle-on" : "pylon-toggle-off");
_popupSystem.PopupEntity(toggleMsg, uid);
return;
}
var damage = comp.BurnDamageOnInteract;
var burnMsg = Loc.GetString("powered-light-component-burn-hand");
_audio.Play(comp.BurnHandSound, Filter.Pvs(pylon), pylon, true);
_popupSystem.PopupEntity(burnMsg, pylon, user);
_damageSystem.TryChangeDamage(user, damage, true);
}
private IEnumerable<TileRef> ShuffleTiles(IEnumerable<TileRef> collection)
{
var random = new Random();
var shuffledList = collection.ToList();
var n = shuffledList.Count;
while (n > 1)
{
n--;
var k = random.Next(n + 1);
(shuffledList[k], shuffledList[n]) = (shuffledList[n], shuffledList[k]);
}
return shuffledList;
}
private void UpdateAppearance(EntityUid uid, SharedPylonComponent comp)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, PylonVisuals.Activated, comp.Activated, appearance);
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultBarrierComponent : Component
{
[DataField("activated")] public bool Activated;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneApocalypseComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 1.2f;
[ViewVariables(VVAccess.ReadWrite), DataField("summonMinCount")]
public uint SummonMinCount = 10;
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBarrierComponent : Component { }

View File

@@ -0,0 +1,17 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBaseComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("invokersMinCount")]
public uint InvokersMinCount = 1;
[ViewVariables(VVAccess.ReadWrite), DataField("gatherInvokers")]
public bool GatherInvokers = true;
[ViewVariables(VVAccess.ReadWrite), DataField("cultistGatheringRange")]
public float CultistGatheringRange = 0.7f;
[ViewVariables(VVAccess.ReadWrite), DataField("invokePhrase")]
public string InvokePhrase = "";
}

View File

@@ -0,0 +1,26 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBloodBoilComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("summonMinCount")]
public uint SummonMinCount = 3;
[DataField("projectilePrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string ProjectilePrototype = default!;
[DataField("projectileSpeed"), ViewVariables(VVAccess.ReadWrite)]
public float ProjectileSpeed = 20f;
[DataField("minProjectiles"), ViewVariables(VVAccess.ReadWrite)]
public int MinProjectiles = 3;
[DataField("maxProjectiles"), ViewVariables(VVAccess.ReadWrite)]
public int MaxProjectiles = 9;
[DataField("projectileRange"), ViewVariables(VVAccess.ReadWrite)]
public float ProjectileRange = 50f;
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBuffComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
}

View File

@@ -0,0 +1,17 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneOfferingComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("sacrificeDeadMinCount")]
public uint SacrificeDeadMinCount = 1;
[ViewVariables(VVAccess.ReadWrite), DataField("convertMinCount")]
public uint ConvertMinCount = 2;
[ViewVariables(VVAccess.ReadWrite), DataField("sacrificeMinCount")]
public uint SacrificeMinCount = 3;
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneReviveComponent : Component
{
public static uint ChargesLeft = 3;
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneSummoningComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("summonMinCount")]
public uint SummonMinCount = 2;
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneSummoningProviderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? BaseRune;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneTeleportComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
[ViewVariables(VVAccess.ReadWrite), DataField("label")]
public string? Label;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultTeleportRuneProviderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? BaseRune;
[ViewVariables(VVAccess.ReadOnly)]
public List<EntityUid>? Targets;
}

View File

@@ -0,0 +1,15 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
namespace Content.Shared.White.Cult;
[RegisterComponent]
public sealed partial class RuneDrawerProviderComponent : Component
{
[ViewVariables]
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(ListViewSelectorUiKey.Key);
[DataField("runePrototypes")]
public List<string> RunePrototypes = new();
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class SoulShardComponent : Component
{
}

View File

@@ -0,0 +1,244 @@
using System.Linq;
using Content.Server.Body.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Emp;
using Content.Server.EUI;
using Content.Server.White.Cult.UI;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.Inventory;
using Content.Shared.Stacks;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Actions;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
[Dependency] private readonly EmpSystem _empSystem = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public void InitializeActions()
{
SubscribeLocalEvent<CultistComponent, CultTwistedConstructionActionEvent>(OnTwistedConstructionAction);
SubscribeLocalEvent<CultistComponent, CultSummonDaggerActionEvent>(OnSummonDaggerAction);
SubscribeLocalEvent<CultistComponent, CultShadowShacklesTargetActionEvent>(OnShadowShackles);
SubscribeLocalEvent<CultistComponent, CultElectromagneticPulseTargetActionEvent>(OnElectromagneticPulse);
SubscribeLocalEvent<CultistComponent, CultSummonCombatEquipmentTargetActionEvent>(OnSummonCombatEquipment);
SubscribeLocalEvent<CultistComponent, CultConcealPresenceWorldActionEvent>(OnConcealPresence);
SubscribeLocalEvent<CultistComponent, CultBloodRitesInstantActionEvent>(OnBloodRites);
SubscribeLocalEvent<CultistComponent, CultTeleportTargetActionEvent>(OnTeleport);
SubscribeLocalEvent<CultistComponent, CultStunTargetActionEvent>(OnStunTarget);
}
private void OnStunTarget(EntityUid uid, CultistComponent component, CultStunTargetActionEvent args)
{
if (args.Target == uid || !HasComp<StatusEffectsComponent>(args.Target))
return;
if (_stunSystem.TryStun(args.Target, TimeSpan.FromSeconds(6), true))
{
args.Handled = true;
}
}
private void OnTeleport(EntityUid uid, CultistComponent component, CultTeleportTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _) || !TryComp<ActorComponent>(uid, out var actor))
return;
var eui = new TeleportSpellEui(args.Performer, args.Target);
_euiManager.OpenEui(eui, actor.PlayerSession);
eui.StateDirty();
args.Handled = true;
}
private void OnBloodRites(EntityUid uid, CultistComponent component, CultBloodRitesInstantActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out var bloodstreamComponent))
return;
var bruteDamageGroup = _prototypeManager.Index<DamageGroupPrototype>("Brute");
var burnDamageGroup = _prototypeManager.Index<DamageGroupPrototype>("Burn");
var xform = Transform(uid);
var entitiesInRange = _lookup.GetEntitiesInRange(xform.MapPosition, 1.5f);
FixedPoint2 totalBloodAmount = 0f;
var breakLoop = false;
foreach (var solutionEntity in entitiesInRange.ToList())
{
if (breakLoop)
break;
if (!TryComp<PuddleComponent>(solutionEntity, out var puddleComponent))
continue;
if (!_solutionSystem.TryGetSolution(solutionEntity, puddleComponent.SolutionName, out var solution))
continue;
foreach (var solutionContent in solution.Contents.ToList())
{
if (solutionContent.Reagent.Prototype != "Blood")
continue;
totalBloodAmount += solutionContent.Quantity;
_bloodstreamSystem.TryModifyBloodLevel(uid, solutionContent.Quantity / 6f);
_solutionSystem.RemoveReagent(solutionEntity, solution, "Blood", FixedPoint2.MaxValue);
if (GetMissingBloodValue(bloodstreamComponent) == 0)
{
breakLoop = true;
}
}
}
if (totalBloodAmount == 0f)
{
return;
}
_audio.PlayPvs("/Audio/White/Cult/enter_blood.ogg", uid, AudioParams.Default);
_damageableSystem.TryChangeDamage(uid, new DamageSpecifier(bruteDamageGroup, -20));
_damageableSystem.TryChangeDamage(uid, new DamageSpecifier(burnDamageGroup, -20));
args.Handled = true;
}
private static FixedPoint2 GetMissingBloodValue(BloodstreamComponent bloodstreamComponent)
{
return bloodstreamComponent.BloodMaxVolume - bloodstreamComponent.BloodSolution.Volume;
}
private void OnConcealPresence(EntityUid uid, CultistComponent component, CultConcealPresenceWorldActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
}
private void OnSummonCombatEquipment(
EntityUid uid,
CultistComponent component,
CultSummonCombatEquipmentTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
var coordinates = Transform(uid).Coordinates;
var helmet = Spawn("ClothingHeadHelmetCult", coordinates);
var armor = Spawn("ClothingOuterArmorCult", coordinates);
var shoes = Spawn("ClothingShoesCult", coordinates);
var blade = Spawn("EldritchBlade", coordinates);
var bola = Spawn("CultBola", coordinates);
_inventorySystem.TryUnequip(uid, "head");
_inventorySystem.TryUnequip(uid, "outerClothing");
_inventorySystem.TryUnequip(uid, "shoes");
_inventorySystem.TryEquip(uid, helmet, "head", force: true);
_inventorySystem.TryEquip(uid, armor, "outerClothing", force: true);
_inventorySystem.TryEquip(uid, shoes, "shoes", force: true);
_handsSystem.PickupOrDrop(uid, blade);
_handsSystem.PickupOrDrop(uid, bola);
args.Handled = true;
}
private void OnElectromagneticPulse(
EntityUid uid,
CultistComponent component,
CultElectromagneticPulseTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
var xform = Transform(uid);
_empSystem.EmpPulse(xform.MapPosition, 10, 100000, 10f);
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
args.Handled = true;
}
private void OnShadowShackles(EntityUid uid, CultistComponent component, CultShadowShacklesTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
var cuffs = Spawn("CultistCuffs", Transform(uid).Coordinates);
_handsSystem.TryPickupAnyHand(uid, cuffs);
args.Handled = true;
}
private void OnTwistedConstructionAction(
EntityUid uid,
CultistComponent component,
CultTwistedConstructionActionEvent args)
{
if (args.Handled)
return;
if (!TryComp<BloodstreamComponent>(args.Performer, out var bloodstreamComponent))
return;
if (!_entityManager.TryGetComponent<StackComponent>(args.Target, out var stack))
return;
if (stack.StackTypeId != SteelPrototypeId)
return;
var transform = Transform(args.Target).Coordinates;
var count = stack.Count;
_entityManager.DeleteEntity(args.Target);
var material = _entityManager.SpawnEntity(RunicMetalPrototypeId, transform);
_bloodstreamSystem.TryModifyBloodLevel(args.Performer, -15, bloodstreamComponent, false);
if (!_entityManager.TryGetComponent<StackComponent>(material, out var stackNew))
return;
stackNew.Count = count;
_popupSystem.PopupEntity(Loc.GetString("Конвертируем сталь в руиник металл!"), args.Performer, args.Performer);
args.Handled = true;
}
private void OnSummonDaggerAction(EntityUid uid, CultistComponent component, CultSummonDaggerActionEvent args)
{
if (args.Handled)
return;
if (!TryComp<BloodstreamComponent>(args.Performer, out var bloodstreamComponent))
return;
var xform = Transform(args.Performer).Coordinates;
var dagger = _entityManager.SpawnEntity(RitualDaggerPrototypeId, xform);
_bloodstreamSystem.TryModifyBloodLevel(args.Performer, -30, bloodstreamComponent, false);
_handsSystem.TryPickupAnyHand(args.Performer, dagger);
args.Handled = true;
}
}

View File

@@ -0,0 +1,83 @@
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Interaction;
using Content.Shared.Stealth.Components;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
public void InitializeBarrierSystem()
{
SubscribeLocalEvent<CultBarrierComponent, ActivateInWorldEvent>(OnActivateBarrier);
SubscribeLocalEvent<CultBarrierComponent, InteractUsingEvent>(OnInteract);
}
[Dependency] private readonly PhysicsSystem _physicsSystem = default!;
private void OnActivateBarrier(EntityUid uid, CultBarrierComponent component, ActivateInWorldEvent args)
{
if (!HasComp<CultistComponent>(args.User))
return;
if (component.Activated)
Deactivate(args.Target);
else
Activate(args.Target);
}
private void Activate(EntityUid barrier)
{
if (!TryComp<CultBarrierComponent>(barrier, out var barrierComponent))
return;
if (HasComp<StealthOnMoveComponent>(barrier))
RemComp<StealthOnMoveComponent>(barrier);
if (HasComp<StealthComponent>(barrier))
RemComp<StealthComponent>(barrier);
_physicsSystem.SetCanCollide(barrier, true);
barrierComponent.Activated = true;
}
private void Deactivate(EntityUid barrier)
{
if (!TryComp<CultBarrierComponent>(barrier, out var barrierComponent))
return;
EnsureComp<StealthComponent>(barrier);
EnsureComp<StealthOnMoveComponent>(barrier);
_physicsSystem.SetCanCollide(barrier, false);
barrierComponent.Activated = false;
}
private void OnInteract(EntityUid uid, CultBarrierComponent component, InteractUsingEvent args)
{
var entityPrototype = _entityManager.GetComponent<MetaDataComponent>(args.Used).EntityPrototype;
if (entityPrototype == null)
return;
var used = entityPrototype.ID;
var user = args.User;
var target = args.Target;
if (used != RitualDaggerPrototypeId)
return;
if (!HasComp<CultistComponent>(user))
return;
_popupSystem.PopupEntity("Вы уничтожаете барьер", user, user);
_entityManager.DeleteEntity(target);
}
}

View File

@@ -0,0 +1,113 @@
using System.Linq;
using System.Numerics;
using Content.Server.White.Cult.GameRule;
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Alert;
using Content.Shared.Maps;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Components;
using Robust.Shared.Map;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
public void InitializeBuffSystem()
{
SubscribeLocalEvent<CultBuffComponent, ComponentAdd>(OnAdd);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateBuffTimers(frameTime);
AnyCultistNearTile();
RemoveExpiredBuffs();
}
private void AnyCultistNearTile()
{
var cultists = EntityQuery<CultistComponent>();
foreach (var cultist in cultists)
{
var uid = cultist.Owner;
if (HasComp<CultBuffComponent>(uid))
continue;
if (!AnyCultTilesNearby(uid))
continue;
var comp = EnsureComp<CultBuffComponent>(uid);
comp.BuffTime = CultBuffComponent.CultTileBuffTime;
}
}
private void OnAdd(EntityUid uid, CultBuffComponent comp, ComponentAdd args)
{
_alertsSystem.ShowAlert(uid, AlertType.CultBuffed);
}
private void UpdateBuffTimers(float frameTime)
{
var buffs = EntityQuery<CultBuffComponent>();
foreach (var buff in buffs)
{
var uid = buff.Owner;
var remainingTime = buff.BuffTime;
remainingTime -= TimeSpan.FromSeconds(frameTime);
if (TryComp<CultistComponent>(uid, out var cultist))
{
if (remainingTime < CultBuffComponent.CultTileBuffTime && AnyCultTilesNearby(uid))
remainingTime = CultBuffComponent.CultTileBuffTime;
}
buff.BuffTime = remainingTime;
}
}
private bool AnyCultTilesNearby(EntityUid uid)
{
var localpos = Transform(uid).Coordinates.Position;
if (!TryComp<CultistComponent>(uid, out var cultist))
return false;
var radius = CultBuffComponent.NearbyTilesBuffRadius;
if (!_mapManager.TryGetGrid(Transform(uid).GridUid, out var grid))
return false;
var tilesRefs = grid.GetLocalTilesIntersecting(new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius)));
var cultTileDef = (ContentTileDefinition) _tileDefinition[$"{CultRuleComponent.CultFloor}"];
var cultTile = new Tile(cultTileDef.TileId);
return tilesRefs.Any(tileRef => tileRef.Tile.TypeId == cultTile.TypeId);
}
private void RemoveExpiredBuffs()
{
var buffs = EntityQuery<CultBuffComponent>();
foreach (var buff in buffs)
{
var uid = buff.Owner;
var remainingTime = buff.BuffTime;
if (remainingTime <= TimeSpan.Zero)
{
RemComp<CultBuffComponent>(uid);
_alertsSystem.ClearAlert(uid, AlertType.CultBuffed);
}
}
}
}

View File

@@ -0,0 +1,60 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.White.Cult.Runes.Components;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly ItemSlotsSystem _slotsSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public void InitializeConstructs()
{
SubscribeLocalEvent<ConstructShellComponent, ContainerIsInsertingAttemptEvent>(OnShardInsertAttempt);
SubscribeLocalEvent<ConstructShellComponent, ComponentInit>(OnShellInit);
SubscribeLocalEvent<ConstructShellComponent, ComponentRemove>(OnShellRemove);
SubscribeLocalEvent<ConstructShellComponent, ConstructFormSelectedEvent>(OnShellSelected);
}
private void OnShellSelected(EntityUid uid, ConstructShellComponent component, ConstructFormSelectedEvent args)
{
var construct = Spawn(args.SelectedForm, Transform(args.Entity).MapPosition);
var mind = Comp<MindContainerComponent>(args.Session.AttachedEntity!.Value);
if(!mind.HasMind)
return;
_mindSystem.TransferTo(mind.Mind.Value, construct);
Del(args.Entity);
}
private void OnShellInit(EntityUid uid, ConstructShellComponent component, ComponentInit args)
{
_slotsSystem.AddItemSlot(uid, component.ShardSlotId, component.ShardSlot);
}
private void OnShellRemove(EntityUid uid, ConstructShellComponent component, ComponentRemove args)
{
_slotsSystem.RemoveItemSlot(uid, component.ShardSlot);
}
private void OnShardInsertAttempt(EntityUid uid, ConstructShellComponent component, ContainerIsInsertingAttemptEvent args)
{
if (!TryComp<MindContainerComponent>(args.EntityUid, out var mindContainer) || !mindContainer.HasMind || !TryComp<ActorComponent>(args.EntityUid, out var actor))
{
_popupSystem.PopupEntity("Нет души", uid);
args.Cancel();
return;
}
_slotsSystem.SetLock(uid, component.ShardSlotId, true);
_ui.TryOpen(uid, SelectConstructUi.Key, actor.PlayerSession);
}
}

View File

@@ -0,0 +1,203 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Maps;
using Content.Server.Popups;
using Content.Server.White.Cult.GameRule;
using Content.Server.White.IncorporealSystem;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Interaction.Events;
using Content.Shared.Maps;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.StatusEffect;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly TileSystem _tileSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
public void InitializeConstructsAbilities()
{
SubscribeLocalEvent<ArtificerCreateSoulStoneActionEvent>(OnArtificerCreateSoulStone);
SubscribeLocalEvent<ArtificerCreateConstructShellActionEvent>(OnArtificerCreateConstructShell);
SubscribeLocalEvent<ArtificerConvertCultistFloorActionEvent>(OnArtificerConvertCultistFloor);
SubscribeLocalEvent<ArtificerCreateCultistWallActionEvent>(OnArtificerCreateCultistWall);
SubscribeLocalEvent<ArtificerCreateCultistAirlockActionEvent>(OnArtificerCreateCultistAirlock);
SubscribeLocalEvent<WraithPhaseActionEvent>(OnWraithPhase);
SubscribeLocalEvent<IncorporealComponent, AttackAttemptEvent>(OnAttackAttempt);
SubscribeLocalEvent<JuggernautCreateWallActionEvent>(OnJuggernautCreateWall);
SubscribeLocalEvent<ConstructComponent, ComponentInit>(OnConstructInit);
SubscribeLocalEvent<ConstructComponent, ComponentRemove>(OnConstructComponentRemoved);
}
private void OnConstructInit(EntityUid uid, ConstructComponent component, ComponentInit args)
{
foreach (var action in component.Actions)
{
var actionPrototype = _prototypeManager.Index<InstantActionPrototype>(action);
_actionsSystem.AddAction(uid, new InstantAction(actionPrototype), uid);
}
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!_gameTicker.IsGameRuleAdded(ruleEnt))
continue;
cultRuleComponent.Constructs.Add(component);
}
}
private void OnConstructComponentRemoved(EntityUid uid, ConstructComponent component, ComponentRemove args)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!_gameTicker.IsGameRuleAdded(ruleEnt))
continue;
cultRuleComponent.Constructs.Remove(component);
}
}
private void OnArtificerCreateSoulStone(ArtificerCreateSoulStoneActionEvent ev)
{
var transform = Transform(ev.Performer);
Spawn(ev.SoulStonePrototypeId, transform.Coordinates);
ev.Handled = true;
}
private void OnArtificerCreateConstructShell(ArtificerCreateConstructShellActionEvent ev)
{
var transform = Transform(ev.Performer);
Spawn(ev.ShellPrototypeId, transform.Coordinates);
ev.Handled = true;
}
private void OnArtificerConvertCultistFloor(ArtificerConvertCultistFloorActionEvent ev)
{
var transform = Transform(ev.Performer);
var gridUid = transform.GridUid;
if (!gridUid.HasValue)
{
_popupSystem.PopupEntity("Нельзя строить в космосе...", ev.Performer, ev.Performer);
return;
}
var tileRef = transform.Coordinates.GetTileRef();
if (!tileRef.HasValue)
{
_popupSystem.PopupEntity("Нельзя строить в космосе...", ev.Performer, ev.Performer);
return;
}
var cultistTileDefinition = (ContentTileDefinition) _tileDefinition[ev.FloorTileId];
_tileSystem.ReplaceTile(tileRef.Value, cultistTileDefinition);
Spawn("CultTileSpawnEffect", transform.Coordinates);
ev.Handled = true;
}
private void OnArtificerCreateCultistWall(ArtificerCreateCultistWallActionEvent ev)
{
if (!TrySpawnWall(ev.Performer, ev.WallPrototypeId))
{
return;
}
ev.Handled = true;
}
private void OnArtificerCreateCultistAirlock(ArtificerCreateCultistAirlockActionEvent ev)
{
if (!TrySpawnWall(ev.Performer, ev.AirlockPrototypeId))
{
return;
}
ev.Handled = true;
}
private void OnWraithPhase(WraithPhaseActionEvent ev)
{
if (_statusEffectsSystem.HasStatusEffect(ev.Performer, ev.StatusEffectId))
{
_popupSystem.PopupEntity("Вы уже в потустороннем мире", ev.Performer, ev.Performer);
return;
}
_statusEffectsSystem.TryAddStatusEffect<IncorporealComponent>(ev.Performer, ev.StatusEffectId,
TimeSpan.FromSeconds(ev.Duration), false);
ev.Handled = true;
}
private void OnAttackAttempt(EntityUid uid, IncorporealComponent component, AttackAttemptEvent args)
{
if (_statusEffectsSystem.HasStatusEffect(args.Uid, "Incorporeal"))
{
_statusEffectsSystem.TryRemoveStatusEffect(args.Uid, "Incorporeal");
}
}
private void OnJuggernautCreateWall(JuggernautCreateWallActionEvent ev)
{
if (!TrySpawnWall(ev.Performer, ev.WallPrototypeId))
{
return;
}
ev.Handled = true;
}
private bool TrySpawnWall(EntityUid performer, string wallPrototypeId)
{
var xform = Transform(performer);
var offsetValue = xform.LocalRotation.ToWorldVec().Normalized();
var coords = xform.Coordinates.Offset(offsetValue).SnapToGrid(EntityManager);
var tile = coords.GetTileRef();
if (tile == null)
return false;
// Check there are no walls there
if (_turf.IsTileBlocked(tile.Value, CollisionGroup.Impassable))
{
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), performer, performer);
return false;
}
// Check there are no mobs there
foreach (var entity in _lookupSystem.GetEntitiesIntersecting(tile.Value))
{
if (HasComp<MobStateComponent>(entity) && entity != performer)
{
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), performer, performer);
return false;
}
}
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", performer)), performer);
// Make sure we set the invisible wall to despawn properly
Spawn(wallPrototypeId, coords);
return true;
}
}

View File

@@ -0,0 +1,26 @@
using System.Threading;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
private void InitializeNarsie()
{
SubscribeLocalEvent<NarsieComponent, ComponentInit>(OnNarsieComponentInit);
}
private void OnNarsieComponentInit(EntityUid uid, NarsieComponent component, ComponentInit args)
{
_appearanceSystem.SetData(uid, NarsieVisualState.VisualState, NarsieVisuals.Spawning);
Timer.Spawn(TimeSpan.FromSeconds(6), () =>
{
_appearanceSystem.SetData(uid, NarsieVisualState.VisualState, NarsieVisuals.Spawned);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
using Content.Server.Mind;
using Content.Server.Roles;
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Humanoid;
using Content.Shared.Interaction;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Roles;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Items;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly SharedPointLightSystem _lightSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
public void InitializeSoulShard()
{
SubscribeLocalEvent<SoulShardComponent, AfterInteractEvent>(OnShardInteractUse);
SubscribeLocalEvent<SoulShardComponent, MindAddedMessage>(OnShardMindAdded);
SubscribeLocalEvent<SoulShardComponent, MindRemovedMessage>(OnShardMindRemoved);
}
private void OnShardInteractUse(EntityUid uid, SoulShardComponent component, AfterInteractEvent args)
{
var target = args.Target;
if (!HasComp<CultistComponent>(args.User)) return;
if (!TryComp<MobStateComponent>(target, out var state) || state.CurrentState != MobState.Dead) return;
if (!TryComp<MindContainerComponent>(target, out var mindComponent) || !mindComponent.Mind.HasValue || !TryComp<HumanoidAppearanceComponent>(target, out _)) return;
_mindSystem.TransferTo(mindComponent.Mind.Value, uid);
var targetName = MetaData(target.Value).EntityName;
_metaDataSystem.SetEntityName(uid, Loc.GetString("soul-shard-description", ("soul", targetName)));
_metaDataSystem.SetEntityDescription(uid, Loc.GetString("soul-shard-description", ("soul", targetName)));
}
private void OnShardMindAdded(EntityUid uid, SoulShardComponent component, MindAddedMessage args)
{
if (!TryComp<MindContainerComponent>(uid, out var mindContainer) || !mindContainer.HasMind)
{
return;
}
if (_roleSystem.MindHasRole<TraitorRoleComponent>(mindContainer.Mind.Value))
{
_roleSystem.MindRemoveRole<TraitorRoleComponent>(mindContainer.Mind.Value);
}
_appearanceSystem.SetData(uid, SoulShardVisualState.State, true);
_lightSystem.SetEnabled(uid, true);
}
private void OnShardMindRemoved(EntityUid uid, SoulShardComponent component, MindRemovedMessage args)
{
_appearanceSystem.SetData(uid, SoulShardVisualState.State, false);
_lightSystem.SetEnabled(uid, false);
}
}

View File

@@ -0,0 +1,34 @@
using Content.Shared.Interaction.Events;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
using Robust.Server.Player;
namespace Content.Server.White.Cult.Structures;
public sealed class CultStructureCraftSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RunicMetalComponent, UseInHandEvent>(OnUseInHand);
}
private void OnUseInHand(EntityUid uid, RunicMetalComponent component, UseInHandEvent args)
{
if (!HasComp<CultistComponent>(args.User))
return;
if (!_playerManager.TryGetSessionByEntity(args.User, out var session) || session is not IPlayerSession playerSession)
return;
if (component.UserInterface != null)
{
_uiSystem.CloseUi(component.UserInterface, playerSession);
_uiSystem.OpenUi(component.UserInterface, playerSession);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.Cult.Structures;
[RegisterComponent]
public sealed partial class RunicDoorComponent : Component
{
}

View File

@@ -0,0 +1,74 @@
using Content.Server.Doors.Systems;
using Content.Shared.Doors;
using Content.Shared.Humanoid;
using Content.Shared.Stunnable;
using Content.Shared.White.Cult;
using Robust.Shared.Physics.Systems;
namespace Content.Server.White.Cult.Structures;
public sealed class RunicDoorSystem : EntitySystem
{
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RunicDoorComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
SubscribeLocalEvent<RunicDoorComponent, BeforeDoorClosedEvent>(OnBeforeDoorClosed);
}
private void OnBeforeDoorOpened(EntityUid uid, RunicDoorComponent component, BeforeDoorOpenedEvent args)
{
args.Uncancel();
if (!args.User.HasValue)
{
return;
}
if (!Process(uid, args.User.Value))
{
args.Cancel();
}
}
private void OnBeforeDoorClosed(EntityUid uid, RunicDoorComponent component, BeforeDoorClosedEvent args)
{
args.Uncancel();
if (!args.User.HasValue)
{
return;
}
if (!Process(uid, args.User.Value))
{
args.Cancel();
}
}
private bool Process(EntityUid airlock, EntityUid user)
{
if (HasComp<CultistComponent>(user) || HasComp<ConstructComponent>(user))
{
return true;
}
_doorSystem.Deny(airlock);
if (!HasComp<HumanoidAppearanceComponent>(user))
return false;
var direction = Transform(user).MapPosition.Position - Transform(airlock).MapPosition.Position;
var impulseVector = direction * 7000;
_physics.ApplyLinearImpulse(user, impulseVector);
_stunSystem.TryParalyze(user, TimeSpan.FromSeconds(3), true);
return false;
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Structures;
[RegisterComponent]
public sealed partial class RunicGirderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public string UsedItemID = "RitualDagger";
[ViewVariables(VVAccess.ReadOnly)]
public string DropItemID = "CultRunicMetal1";
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Interaction;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Structures;
public sealed class RunicGirderSystem : EntitySystem
{
[Dependency] private readonly EntityManager _entMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RunicGirderComponent, AfterInteractUsingEvent>(OnInteract);
}
private void OnInteract(EntityUid uid, RunicGirderComponent component, AfterInteractUsingEvent args)
{
if (!HasComp<CultistComponent>(args.User))
return;
if (MetaData(args.Used).EntityPrototype?.ID != component.UsedItemID)
return;
if (args.Target == null)
return;
var pos = Transform(args.Target.Value).Coordinates;
_entMan.DeleteEntity(args.Target.Value);
_entMan.SpawnEntity(component.DropItemID, pos);
}
}

View File

@@ -0,0 +1,15 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult.Structures;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.Structures;
[RegisterComponent]
public sealed partial class RunicMetalComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CultStructureCraftUiKey.Key);
[ViewVariables(VVAccess.ReadWrite), DataField("delay")]
public float Delay = 1;
}

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