main cult
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 /.
|
||||
};
|
||||
|
||||
86
Content.Client/_White/Cult/CultHudOverlay.cs
Normal file
86
Content.Client/_White/Cult/CultHudOverlay.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
65
Content.Client/_White/Cult/CultPentagramSystem.cs
Normal file
65
Content.Client/_White/Cult/CultPentagramSystem.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
6
Content.Client/_White/Cult/Narsie/NarsieLayer.cs
Normal file
6
Content.Client/_White/Cult/Narsie/NarsieLayer.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Content.Client._White.Cult;
|
||||
|
||||
public enum NarsieLayer
|
||||
{
|
||||
Default
|
||||
}
|
||||
69
Content.Client/_White/Cult/Narsie/NarsieVisualizer.cs
Normal file
69
Content.Client/_White/Cult/Narsie/NarsieVisualizer.cs
Normal 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)}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
9
Content.Client/_White/Cult/PentagramComponent.cs
Normal file
9
Content.Client/_White/Cult/PentagramComponent.cs
Normal 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
|
||||
{
|
||||
}
|
||||
40
Content.Client/_White/Cult/Pylon/PylonVisualizerSystem.cs
Normal file
40
Content.Client/_White/Cult/Pylon/PylonVisualizerSystem.cs
Normal 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
|
||||
}
|
||||
15
Content.Client/_White/Cult/Pylon/PylonVisualsComponent.cs
Normal file
15
Content.Client/_White/Cult/Pylon/PylonVisualsComponent.cs
Normal 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";
|
||||
}
|
||||
48
Content.Client/_White/Cult/ShowCultHudSystem.cs
Normal file
48
Content.Client/_White/Cult/ShowCultHudSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
56
Content.Client/_White/Cult/UI/Altar/AltarBUI.cs
Normal file
56
Content.Client/_White/Cult/UI/Altar/AltarBUI.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
7
Content.Client/_White/Cult/UI/Altar/AltarWindow.xaml
Normal file
7
Content.Client/_White/Cult/UI/Altar/AltarWindow.xaml
Normal 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>
|
||||
92
Content.Client/_White/Cult/UI/Altar/AltarWindow.xaml.cs
Normal file
92
Content.Client/_White/Cult/UI/Altar/AltarWindow.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
9
Content.Client/_White/Cult/UI/Torch/TorchWindow.xaml
Normal file
9
Content.Client/_White/Cult/UI/Torch/TorchWindow.xaml
Normal 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>
|
||||
35
Content.Client/_White/Cult/UI/Torch/TorchWindow.xaml.cs
Normal file
35
Content.Client/_White/Cult/UI/Torch/TorchWindow.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Content.Client/_White/Cult/UI/Torch/TorchWindowBUI.cs
Normal file
47
Content.Client/_White/Cult/UI/Torch/TorchWindowBUI.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Content.Client/_White/UserInterface/Radial/RadialButton.xaml
Normal file
18
Content.Client/_White/UserInterface/Radial/RadialButton.xaml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
48
Content.Server/Chat/Commands/CultCommand.cs
Normal file
48
Content.Server/Chat/Commands/CultCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
88
Content.Server/Objectives/Conditions/KillCultistTarget.cs
Normal file
88
Content.Server/Objectives/Conditions/KillCultistTarget.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
11
Content.Server/_White/Cult/ConstructComponent.cs
Normal file
11
Content.Server/_White/Cult/ConstructComponent.cs
Normal 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();
|
||||
}
|
||||
74
Content.Server/_White/Cult/GameRule/CultRuleComponent.cs
Normal file
74
Content.Server/_White/Cult/GameRule/CultRuleComponent.cs
Normal 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
|
||||
{
|
||||
}
|
||||
492
Content.Server/_White/Cult/GameRule/CultRuleSystem.cs
Normal file
492
Content.Server/_White/Cult/GameRule/CultRuleSystem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
54
Content.Server/_White/Cult/HolyWater/DeconvertCultist.cs
Normal file
54
Content.Server/_White/Cult/HolyWater/DeconvertCultist.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
54
Content.Server/_White/Cult/HolyWater/HolyWaterSystem.cs
Normal file
54
Content.Server/_White/Cult/HolyWater/HolyWaterSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
Content.Server/_White/Cult/Items/Systems/VoidTeleportSystem.cs
Normal file
160
Content.Server/_White/Cult/Items/Systems/VoidTeleportSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Content.Server/_White/Cult/PentagramComponent.cs
Normal file
9
Content.Server/_White/Cult/PentagramComponent.cs
Normal 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
|
||||
{
|
||||
}
|
||||
264
Content.Server/_White/Cult/Pylon/PylonSystem.cs
Normal file
264
Content.Server/_White/Cult/Pylon/PylonSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.White.Cult.Runes.Comps;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CultBarrierComponent : Component
|
||||
{
|
||||
[DataField("activated")] public bool Activated;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Content.Server.White.Cult.Runes.Comps;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CultRuneBarrierComponent : Component { }
|
||||
@@ -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 = "";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Content.Server.White.Cult.Runes.Comps;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CultRuneSummoningProviderComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public EntityUid? BaseRune;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.White.Cult.Runes.Comps;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class SoulShardComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
244
Content.Server/_White/Cult/Runes/Systems/CultSystem.Actions.cs
Normal file
244
Content.Server/_White/Cult/Runes/Systems/CultSystem.Actions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
113
Content.Server/_White/Cult/Runes/Systems/CultSystem.Buff.cs
Normal file
113
Content.Server/_White/Cult/Runes/Systems/CultSystem.Buff.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
1301
Content.Server/_White/Cult/Runes/Systems/CultSystem.Rune.cs
Normal file
1301
Content.Server/_White/Cult/Runes/Systems/CultSystem.Rune.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server.White.Cult.Structures;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class RunicDoorComponent : Component
|
||||
{
|
||||
}
|
||||
74
Content.Server/_White/Cult/Structures/RunicDoorSystem.cs
Normal file
74
Content.Server/_White/Cult/Structures/RunicDoorSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
30
Content.Server/_White/Cult/Structures/RunicGirderSystem.cs
Normal file
30
Content.Server/_White/Cult/Structures/RunicGirderSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
15
Content.Server/_White/Cult/Structures/RunicMetalComponent.cs
Normal file
15
Content.Server/_White/Cult/Structures/RunicMetalComponent.cs
Normal 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
Reference in New Issue
Block a user