Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using Content.Client.Eui;
using Content.Shared.DeadSpace.AntagGearSelector;
using Content.Shared.Eui;
using JetBrains.Annotations;

namespace Content.Client.DeadSpace.AntagGearSelector;

[UsedImplicitly]
public sealed class AntagGearSelectorEui : BaseEui
{
private readonly AntagGearSelectorWindow _window = new();

public AntagGearSelectorEui()
{
_window.OnClose += () => SendMessage(new CloseEuiMessage());
_window.OnConfirmed += (gear, perk) => SendMessage(new AntagGearSelectorSelectedMessage(gear, perk));
}

public override void Opened() => _window.OpenCentered();
public override void Closed() => _window.Close();

public override void HandleState(EuiStateBase state)
{
if (state is AntagGearSelectorEuiState selector)
_window.UpdateState(selector);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using System.Numerics;
using Content.Shared.DeadSpace.AntagGearSelector;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;

namespace Content.Client.DeadSpace.AntagGearSelector;

[GenerateTypedNameReferences]
public sealed partial class AntagGearSelectorWindow : DefaultWindow
{
public event Action<int, int>? OnConfirmed;
private int _gearIndex = -1;
private int _perkIndex = -1;
private AntagGearSelectorOption? _selectedGear;
private readonly ButtonGroup _gearGroup = new();
private readonly ButtonGroup _perkGroup = new();
private readonly IGameTiming _timing;
private TimeSpan _deadline;

public AntagGearSelectorWindow()
{
RobustXamlLoader.Load(this);
_timing = IoCManager.Resolve<IGameTiming>();
Title = "Выбор специализации";
ConfirmButton.OnPressed += _ => OnConfirmed?.Invoke(_gearIndex, _perkIndex);
}

public void UpdateState(AntagGearSelectorEuiState state)
{
GearContainer.RemoveAllChildren();
PerksContainer.RemoveAllChildren();
_deadline = state.Deadline;
AddGearOptions(state.Gear);
}

private void AddGearOptions(List<AntagGearSelectorOption> options)
{
foreach (var option in options)
AddOption(GearContainer, option.Index, option.Name, option.Description, option.SpritePrototype, option);
}

private void AddPerkOptions(List<AntagGearSelectorPerkOption> options)
{
foreach (var option in options)
AddOption(PerksContainer, option.Index, option.Name, option.Description, option.SpritePrototype, null);
}

private void AddOption(BoxContainer container, int index, string name, string description,
string spritePrototype, AntagGearSelectorOption? gear)
{
var button = new ContainerButton
{
HorizontalExpand = true,
MinHeight = 64,
ToggleMode = true,
ToolTip = description,
Group = gear == null ? _perkGroup : _gearGroup,
};

var row = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
SeparationOverride = 8,
Margin = new Thickness(4),
MouseFilter = Control.MouseFilterMode.Ignore,
};
var preview = new EntityPrototypeView
{
SetSize = new Vector2(48, 48),
MouseFilter = Control.MouseFilterMode.Ignore,
};
preview.SetPrototype(spritePrototype);
row.AddChild(preview);
row.AddChild(new Label
{
Text = $"{name}\n{description}",
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
MouseFilter = Control.MouseFilterMode.Ignore,
});
button.AddChild(row);

button.OnPressed += _ =>
{
foreach (var child in container.Children)
{
if (child is ContainerButton other)
other.Pressed = false;
}
button.Pressed = true;

if (gear != null)
{
_gearIndex = index;
_selectedGear = gear;
SelectedLabel.Text = $"Выбран боец: {name}. Выберите перк.";
_perkIndex = -1;
PerksContainer.RemoveAllChildren();
AddPerkOptions(gear.Perks);
}
else
{
_perkIndex = index;
SelectedLabel.Text = $"Выбраны: {_selectedGear?.Name} — {name}";
}

ConfirmButton.Disabled = _gearIndex < 0 || (_selectedGear?.Perks.Count > 0 && _perkIndex < 0);
};
container.AddChild(button);
}

protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
var remaining = _deadline - _timing.CurTime;
if (remaining < TimeSpan.Zero)
remaining = TimeSpan.Zero;
TimerLabel.Text = $"До случайного выбора: {remaining.Minutes:00}:{remaining.Seconds:00}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<DefaultWindow xmlns="https://spacestation14.io" MinSize="780 520" SetSize="820 560">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" SeparationOverride="8" Margin="8">
<PanelContainer StyleClasses="BackgroundPanel" MinWidth="360" VerticalExpand="True">
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="8" SeparationOverride="6">
<Label Text="Выберите бойца" Align="Center" StyleClasses="LabelHeading"/>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Name="GearContainer" Orientation="Vertical" HorizontalExpand="True" SeparationOverride="5"/>
</ScrollContainer>
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="BackgroundPanel" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="8" SeparationOverride="6">
<Label Name="TimerLabel" Text="До случайного выбора: 01:00" Align="Center" StyleClasses="LabelSubText"/>
<Label Name="SelectedLabel" Text="Ничего не выбрано" Align="Center" StyleClasses="LabelSubText"/>
<Label Text="Выберите перк" Align="Center" StyleClasses="LabelHeading"/>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Name="PerksContainer" Orientation="Vertical" HorizontalExpand="True" SeparationOverride="5"/>
</ScrollContainer>
<PanelContainer StyleClasses="LowDivider" MinHeight="2"/>
<Button Name="ConfirmButton" Text="Подтвердить" Disabled="True" MinHeight="42"/>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</DefaultWindow>
50 changes: 44 additions & 6 deletions Content.Client/DeadSpace/ThermalVision/ThermalVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using Content.Shared.DeadSpace.ThermalVision;
using Content.Shared.DeadSpace.TheCircle.Legion;
using Content.Shared.Inventory;
using Robust.Client.Audio;
using Robust.Client.GameObjects;
Expand Down Expand Up @@ -31,6 +32,10 @@ public override void Initialize()
SubscribeLocalEvent<ThermalVisionComponent, ComponentShutdown>(OnActiveShutdown);
SubscribeLocalEvent<ThermalVisionComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ThermalVisionComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<LegionComponent, ComponentInit>(OnLegionInit);
SubscribeLocalEvent<LegionComponent, ComponentShutdown>(OnLegionShutdown);
SubscribeLocalEvent<LegionComponent, LocalPlayerAttachedEvent>(OnLegionAttached);
SubscribeLocalEvent<LegionComponent, LocalPlayerDetachedEvent>(OnLegionDetached);

_overlay = new ThermalVisionOverlay(EntityManager, _spriteSys, _lookup);
}
Expand All @@ -45,7 +50,16 @@ public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var player = _player.LocalEntity;
if (player == null || !TryComp<ThermalVisionComponent>(player, out var comp))
if (player == null)
return;

var pulseTarget = TryComp<LegionComponent>(player, out var legion) && legion.RevealPulseActive ? 1f : 0f;
_overlay.LegionAlpha = Math.Clamp(
_overlay.LegionAlpha + (pulseTarget - _overlay.LegionAlpha) * frameTime * 8f,
0f,
1f);

if (!TryComp<ThermalVisionComponent>(player, out var comp))
return;

if (comp.IsActive && !_wasActive)
Expand Down Expand Up @@ -81,6 +95,27 @@ private void OnPlayerDetached(EntityUid uid, ThermalVisionComponent component, L
RemVision();
}

private void OnLegionInit(EntityUid uid, LegionComponent component, ComponentInit args)
{
if (_player.LocalEntity == uid)
AddVision();
}

private void OnLegionShutdown(EntityUid uid, LegionComponent component, ComponentShutdown args)
{
if (_player.LocalEntity == uid && !HasComp<ThermalVisionComponent>(uid))
RemVision();
}

private void OnLegionAttached(EntityUid uid, LegionComponent component, LocalPlayerAttachedEvent args) => AddVision();

private void OnLegionDetached(EntityUid uid, LegionComponent component, LocalPlayerDetachedEvent args)
{
_overlay.LegionAlpha = 0f;
if (!HasComp<ThermalVisionComponent>(uid))
RemVision();
}

private void AddVision()
{
_overlayMan.AddOverlay(_overlay);
Expand All @@ -94,6 +129,7 @@ private void RemVision()

public sealed class ThermalVisionOverlay : Overlay
{
public float LegionAlpha;
private readonly IEntityManager _entityManager;
private readonly ShaderInstance _vignetteShader;
private readonly SpriteSystem _spriteSys;
Expand All @@ -114,21 +150,23 @@ public ThermalVisionOverlay(IEntityManager entityManager, SpriteSystem spriteSys
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
var player = IoCManager.Resolve<IPlayerManager>().LocalEntity;
if (player == null || !_entityManager.TryGetComponent<ThermalVisionComponent>(player.Value, out var comp))
if (player == null)
return false;

if (!_entityManager.TryGetComponent<EyeComponent>(player.Value, out var eye) || args.Viewport.Eye != eye.Eye)
return false;

return comp.IsActive;
var thermalActive = _entityManager.TryGetComponent<ThermalVisionComponent>(player.Value, out var comp) && comp.IsActive;
return thermalActive || LegionAlpha > 0.01f;
}

protected override void Draw(in OverlayDrawArgs args)
{
if (args.Space == OverlaySpace.ScreenSpace)
{
var player = IoCManager.Resolve<IPlayerManager>().LocalEntity;
if (player == null || !_entityManager.TryGetComponent<ThermalVisionComponent>(player.Value, out var comp) || !comp.UseShader)
if (player == null || !_entityManager.TryGetComponent<ThermalVisionComponent>(player.Value, out var comp) ||
!comp.IsActive || !comp.UseShader)
return;

var screenHandle = (DrawingHandleScreen)args.DrawingHandle;
Expand Down Expand Up @@ -192,9 +230,9 @@ protected override void Draw(in OverlayDrawArgs args)
oldColor.R,
oldColor.G * 0.5f,
oldColor.B * 0.5f,
oldColor.A));
oldColor.A * (LegionAlpha > 0.01f ? LegionAlpha : 1f)));
_spriteSys.RenderSprite((drawUid, drawSprite), worldHandle, eyeRot, worldRot, worldPos);
_spriteSys.SetColor((drawUid, drawSprite), oldColor);
}
}
}
}
26 changes: 19 additions & 7 deletions Content.Server/Antag/AntagSelectionSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,11 +555,16 @@ public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? sessi
var componentsBeforeAssignment = SnapshotComponents(entitiesBeforeAssignment);
// DS14-end

EntityManager.AddComponents(player, def.Components);
// DS14-start: selector-backed antagonists receive their components after the player chooses a body.
var deferredGearSelection = TryComp<Content.Server.DeadSpace.AntagGearSelector.AntagGearSelectorComponent>(ent, out var gearSelector) &&
def.PrefRoles.Any(gearSelector.Roles.Contains);
if (!deferredGearSelection)
EntityManager.AddComponents(player, def.Components);
// DS14-end

// Equip the entity's RoleLoadout and LoadoutGroup
List<ProtoId<StartingGearPrototype>> gear = new();
if (def.StartingGear is not null)
if (!deferredGearSelection && def.StartingGear is not null) // DS14
gear.Add(def.StartingGear.Value);

// DS14-start
Expand All @@ -584,10 +589,13 @@ public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? sessi
}
}

if (selectedAntagLoadout != null && selectedAntagLoadoutPrototype != null)
_loadout.Equip(player, gear, selectedAntagLoadout, selectedAntagLoadoutPrototype);
else
_loadout.Equip(player, gear, def.RoleLoadout);
if (!deferredGearSelection) // DS14
{
if (selectedAntagLoadout != null && selectedAntagLoadoutPrototype != null)
_loadout.Equip(player, gear, selectedAntagLoadout, selectedAntagLoadoutPrototype);
else
_loadout.Equip(player, gear, def.RoleLoadout);
} // DS14
// DS14-end

if (session != null)
Expand All @@ -610,7 +618,11 @@ public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? sessi
// DS14-end
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
ent.Comp.AssignedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing);
// DS14-start: selector-backed antagonists get the proper role briefing
// only after their fighter and perk have been selected.
if (!deferredGearSelection)
SendBriefing(session, def.Briefing);
// DS14-end

Log.Debug($"Assigned {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
_adminLogger.Add(LogType.AntagSelection, $"Assigned {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using Content.Shared.Roles;
using Content.Server.Antag.Components;
using Robust.Shared.Prototypes;

namespace Content.Server.DeadSpace.AntagGearSelector;

[RegisterComponent, Access(typeof(AntagGearSelectorSystem))]
public sealed partial class AntagGearSelectorComponent : Component
{
[DataField]
public TimeSpan SelectionTimeout = TimeSpan.FromMinutes(1);

[DataField(required: true)]
public HashSet<ProtoId<AntagPrototype>> Roles = new();

[DataField(required: true)]
public List<AntagGearSelectorEntry> Gear = new();

}

[DataDefinition]
public sealed partial class AntagGearSelectorEntry
{
[DataField(required: true)] public LocId Name;
[DataField(required: true)] public LocId Description;
[DataField(required: true)] public EntProtoId SpritePrototype;
[DataField] public ProtoId<StartingGearPrototype>? StartingGear;
[DataField] public ComponentRegistry Components = new();
[DataField] public BriefingData? Briefing;
[DataField] public List<AntagGearSelectorEntry> Perks = new();
}
Loading
Loading