feat(bloodcult): Add Blood Cult client UI from Wega

Layer 2 of Blood Cult migration:
- Client system (BloodCultSystem.cs)
- UI Menus: BloodMagic, BloodRites, BloodConstruct, BloodStructure
- UI Menus: Runes, EmpoweringRune, SummoningRune
- UI Controllers for all menus

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-19 03:54:26 +01:00
parent f95db89bc5
commit 300ccb1b88
18 changed files with 1089 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
using System.Numerics;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Client.Blood.Cult
{
public sealed class BloodCultSystem : SharedBloodCultSystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodRuneComponent, AppearanceChangeEvent>(OnRuneAppearanceChanged);
SubscribeLocalEvent<BloodRitualDimensionalRendingComponent, AppearanceChangeEvent>(OnRuneAppearanceChanged);
SubscribeLocalEvent<BloodCultistComponent, GetStatusIconsEvent>(GetCultistIcons);
SubscribeLocalEvent<PentagramDisplayComponent, ComponentStartup>(GetHalo);
SubscribeLocalEvent<PentagramDisplayComponent, ComponentShutdown>(RemoveHalo);
SubscribeLocalEvent<StoneSoulComponent, AppearanceChangeEvent>(OnSoulStoneAppearanceChanged);
}
private void OnRuneAppearanceChanged(Entity<BloodRuneComponent> entity, ref AppearanceChangeEvent args)
{
if (!_appearance.TryGetData(entity, RuneColorVisuals.Color, out Color color))
return;
_sprite.SetColor(entity.Owner, color);
}
private void OnRuneAppearanceChanged(Entity<BloodRitualDimensionalRendingComponent> entity, ref AppearanceChangeEvent args)
{
if (!_appearance.TryGetData(entity, RuneColorVisuals.Color, out Color color))
return;
_sprite.SetColor(entity.Owner, color);
}
private void GetCultistIcons(Entity<BloodCultistComponent> ent, ref GetStatusIconsEvent args)
{
var iconPrototype = _prototype.Index(ent.Comp.StatusIcon);
args.StatusIcons.Add(iconPrototype);
}
private void GetHalo(EntityUid uid, PentagramDisplayComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (_sprite.LayerMapTryGet(uid, PentagramKey.Halo, out _, true))
return;
var haloVariant = _random.Next(1, 6);
var haloState = $"halo{haloVariant}";
var bounds = _sprite.GetLocalBounds((uid, sprite));
var adj = bounds.Height / 2 + 1.0f / 32 * 6.0f;
var layerData = new PrototypeLayerData
{
Shader = "unshaded",
RsiPath = "_Wega/Interface/Misc/bloodcult_halo.rsi",
State = haloState,
Offset = new Vector2(0.0f, adj)
};
var layer = _sprite.AddLayer(uid, layerData, null);
_sprite.LayerMapSet(uid, PentagramKey.Halo, layer);
}
private void RemoveHalo(EntityUid uid, PentagramDisplayComponent component, ComponentShutdown args)
{
if (_sprite.LayerMapTryGet(uid, PentagramKey.Halo, out var layer, true))
{
_sprite.RemoveLayer(uid, layer);
}
}
private void OnSoulStoneAppearanceChanged(EntityUid uid, StoneSoulComponent component, ref AppearanceChangeEvent args)
{
if (!_appearance.TryGetData(uid, StoneSoulVisuals.HasSoul, out bool hasSoul))
hasSoul = false;
_sprite.LayerSetVisible(uid, StoneSoulVisualLayers.Soul, hasSoul);
if (!hasSoul)
{
_sprite.LayerSetVisible(uid, StoneSoulVisualLayers.Base, true);
}
else
{
_sprite.LayerSetVisible(uid, StoneSoulVisualLayers.Base, false);
}
}
private enum PentagramKey
{
Halo
}
}
}

View File

@@ -0,0 +1,34 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Select.Construct.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Select.Construct.UI.BloodConstructMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<!-- Juggernaut -->
<ui:RadialMenuButton Name="BloodJuggernautButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-juggernaut'}" TargetLayerControlName="BloodJuggernaut">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/juggernaut.png"/>
</ui:RadialMenuButton>
<!-- Wraith -->
<ui:RadialMenuButton Name="BloodWraithButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-wraith'}" TargetLayerControlName="BloodWraith">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/wraith.png"/>
</ui:RadialMenuButton>
<!-- Artificer -->
<ui:RadialMenuButton Name="BloodArtificerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-artificer'}" TargetLayerControlName="BloodArtificer">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/artificer.png"/>
</ui:RadialMenuButton>
<!-- Proteon -->
<ui:RadialMenuButton Name="BloodProteonButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-constuct-proteon'}" TargetLayerControlName="BloodProteon">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/proteon.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -0,0 +1,49 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
namespace Content.Client.Select.Construct.UI;
[GenerateTypedNameReferences]
public sealed partial class BloodConstructMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectConstruct;
private NetEntity _constructUid;
private NetEntity _mindUid;
public BloodConstructMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
BloodJuggernautButton.OnButtonUp += _ => HandleRitesSelection("MobConstructJuggernaut");
BloodWraithButton.OnButtonUp += _ => HandleRitesSelection("MobConstructWraith");
BloodArtificerButton.OnButtonUp += _ => HandleRitesSelection("MobConstructArtificer");
BloodProteonButton.OnButtonUp += _ => HandleRitesSelection("MobConstructProteon");
}
public void SetData(NetEntity constructUid, NetEntity mindUid)
{
_constructUid = constructUid;
_mindUid = mindUid;
}
private void HandleRitesSelection(string constructName)
{
OnSelectConstruct?.Invoke(constructName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodConstructMenuClosedEvent(netEntity, _constructUid, _mindUid, constructName));
Close();
}
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Select.Construct.UI
{
public sealed class BloodConstructMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodConstructMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<OpenConstructMenuEvent>(OnConstructMenuReceived);
}
private void OnConstructMenuReceived(OpenConstructMenuEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodConstructMenu>();
_menu.SetData(args.ConstructUid, args.Mind);
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
Timer.Spawn(30000, () =>
{
if (_menu != null)
{
_menu.Close();
}
});
}
}
}
}

View File

@@ -0,0 +1,64 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Blood.Magic.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Blood.Magic.UI.BloodMagicMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<!-- Stun -->
<ui:RadialMenuButton Name="StunButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-stun'}" TargetLayerControlName="Stun">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/stun.png"/>
</ui:RadialMenuButton>
<!-- Teleport -->
<ui:RadialMenuButton Name="TeleportButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-teleport'}" TargetLayerControlName="Teleport">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/teleport.png"/>
</ui:RadialMenuButton>
<!-- Electromagnetic Pulse -->
<ui:RadialMenuButton Name="ElectromagneticPulseButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-electromagnetic-pulse'}" TargetLayerControlName="ElectromagneticPulse">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/electromagneticpulse.png"/>
</ui:RadialMenuButton>
<!-- Shadow Shackles -->
<ui:RadialMenuButton Name="ShadowShacklesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-shadow-shackles'}" TargetLayerControlName="ShadowShackles">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/shadowshackles.png"/>
</ui:RadialMenuButton>
<!-- Twisted Construction -->
<ui:RadialMenuButton Name="TwistedConstructionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-twisted-construction'}" TargetLayerControlName="TwistedConstruction">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/twistedconstruction.png"/>
</ui:RadialMenuButton>
<!-- Summon Equipment -->
<ui:RadialMenuButton Name="SummonEquipmentButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-equipment'}" TargetLayerControlName="SummonEquipment">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/summonequipment.png"/>
</ui:RadialMenuButton>
<!-- Summon Dagger -->
<ui:RadialMenuButton Name="SummonDaggerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-dagger'}" TargetLayerControlName="SummonDagger">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/dagger.png"/>
</ui:RadialMenuButton>
<!-- Hallucinations -->
<ui:RadialMenuButton Name="HallucinationsButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-hallucinations'}" TargetLayerControlName="Hallucinations">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/hallucinations.png"/>
</ui:RadialMenuButton>
<!-- Conceal Presence -->
<ui:RadialMenuButton Name="ConcealPresenceButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-conceal-presence'}" TargetLayerControlName="ConcealPresence">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/concealpresence.png"/>
</ui:RadialMenuButton>
<!-- Blood Rites -->
<ui:RadialMenuButton Name="BloodRitesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-blood-rites'}" TargetLayerControlName="BloodRites">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/blood_rites.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -0,0 +1,48 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
namespace Content.Client.Blood.Magic.UI;
[GenerateTypedNameReferences]
public sealed partial class BloodMagicMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectSpell;
public BloodMagicMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
StunButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultStun");
TeleportButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTeleport");
ElectromagneticPulseButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultElectromagneticPulse");
ShadowShacklesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultShadowShackles");
TwistedConstructionButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTwistedConstruction");
SummonEquipmentButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonEquipment");
SummonDaggerButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonDagger");
HallucinationsButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultHallucinations");
ConcealPresenceButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultConcealPresence");
BloodRitesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultBloodRites");
}
private void HandleSpellSelection(string spellName)
{
OnSelectSpell?.Invoke(spellName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodMagicMenuClosedEvent(netEntity, spellName));
Close();
}
}

View File

@@ -0,0 +1,45 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client.Blood.Magic.UI
{
public sealed class BloodMagicMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodMagicMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BloodMagicPressedEvent>(OnBloodMagicMenuReceived);
}
private void OnBloodMagicMenuReceived(BloodMagicPressedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodMagicMenu>();
_menu.OnClose += OnMenuClosed;
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
}
}
private void OnMenuClosed()
{
_menu = null;
}
}
}

View File

@@ -0,0 +1,34 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Blood.Rites.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Blood.Rites.UI.BloodRitesMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<!-- Blood Orb -->
<ui:RadialMenuButton Name="BloodOrbButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-orb'}" TargetLayerControlName="BloodOrb">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/orb.png"/>
</ui:RadialMenuButton>
<!-- Blood Recharge -->
<ui:RadialMenuButton Name="BloodRechargeButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-recharge'}" TargetLayerControlName="BloodRecharge">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/recharge.png"/>
</ui:RadialMenuButton>
<!-- Blood Spear -->
<ui:RadialMenuButton Name="BloodSpearButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-spear'}" TargetLayerControlName="BloodSpear">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/spear.png"/>
</ui:RadialMenuButton>
<!-- Blood Bolt Barrage -->
<ui:RadialMenuButton Name="BloodBoltBarrageButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-blood-bolt-barrage'}" TargetLayerControlName="BloodBoltBarrage">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/barrage.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -0,0 +1,42 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
namespace Content.Client.Blood.Rites.UI;
[GenerateTypedNameReferences]
public sealed partial class BloodRitesMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectRites;
public BloodRitesMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
BloodOrbButton.OnButtonUp += _ => HandleRitesSelection("ActionBloodCultOrb");
BloodRechargeButton.OnButtonUp += _ => HandleRitesSelection("ActionBloodCultRecharge");
BloodSpearButton.OnButtonUp += _ => HandleRitesSelection("ActionBloodCultSpear");
BloodBoltBarrageButton.OnButtonUp += _ => HandleRitesSelection("ActionBloodCultBoltBarrage");
}
private void HandleRitesSelection(string ritesName)
{
OnSelectRites?.Invoke(ritesName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodRitesMenuClosedEvent(netEntity, ritesName));
Close();
}
}

View File

@@ -0,0 +1,45 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client.Blood.Rites.UI
{
public sealed class BloodRitesMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodRitesMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BloodRitesPressedEvent>(OnBloodMagicMenuReceived);
}
private void OnBloodMagicMenuReceived(BloodRitesPressedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodRitesMenu>();
_menu.OnClose += OnMenuClosed;
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
}
}
private void OnMenuClosed()
{
_menu = null;
}
}
}

View File

@@ -0,0 +1,14 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Structure.UI;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Structure.UI.BloodStructureMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -0,0 +1,82 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Structure.UI;
[GenerateTypedNameReferences]
public sealed partial class BloodStructureMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectItem;
private NetEntity _structure;
public BloodStructureMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void SetData(NetEntity structure)
{
_structure = structure;
InitializeButtons();
}
private void InitializeButtons()
{
var structure = _entityManager.GetEntity(_structure);
if (!_entityManager.TryGetComponent<BloodStructureComponent>(structure, out var structureComp)
|| structureComp.StructureGear.Count == 0)
return;
foreach (var prototypeId in structureComp.StructureGear)
{
if (!_prototypeManager.TryIndex<EntityPrototype>(prototypeId, out var prototype))
continue;
var button = new RadialMenuButton
{
ToolTip = prototype.Name,
SetSize = new Vector2(64, 64),
};
button.StyleClasses.Add("RadialMenuButton");
var entityView = new EntityPrototypeView
{
Scale = new Vector2(2, 2),
SetSize = new Vector2(64, 64),
Margin = new Thickness(4)
};
entityView.SetPrototype(prototype.ID);
button.AddChild(entityView);
button.OnPressed += _ =>
{
HandleItemSelection(prototype.ID);
};
Main.AddChild(button);
}
}
private void HandleItemSelection(string name)
{
OnSelectItem?.Invoke(name);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new BloodStructureMenuClosedEvent(netEntity, name, _structure));
Close();
}
}

View File

@@ -0,0 +1,57 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Structure.UI
{
public sealed class BloodStructureMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private BloodStructureMenu? _menu;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<OpenStructureMenuEvent>(OnStructureMenuReceived);
}
private void OnStructureMenuReceived(OpenStructureMenuEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<BloodStructureMenu>();
_menu.OnClose += OnMenuClosed;
_menu.SetData(args.Structure);
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
Timer.Spawn(30000, () =>
{
if (_menu != null)
{
_menu.Close();
}
});
}
}
private void OnMenuClosed()
{
_menu = null;
}
}
}

View File

@@ -0,0 +1,64 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:local="clr-namespace:Content.Client.Runes.Panel.Ui;assembly=Content.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Runes.Panel.Ui.EmpoweringRuneMenu"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
<!-- Stun -->
<ui:RadialMenuButton Name="StunButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-stun'}" TargetLayerControlName="Stun">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/stun.png"/>
</ui:RadialMenuButton>
<!-- Teleport -->
<ui:RadialMenuButton Name="TeleportButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-teleport'}" TargetLayerControlName="Teleport">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/teleport.png"/>
</ui:RadialMenuButton>
<!-- Electromagnetic Pulse -->
<ui:RadialMenuButton Name="ElectromagneticPulseButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-electromagnetic-pulse'}" TargetLayerControlName="ElectromagneticPulse">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/electromagneticpulse.png"/>
</ui:RadialMenuButton>
<!-- Shadow Shackles -->
<ui:RadialMenuButton Name="ShadowShacklesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-shadow-shackles'}" TargetLayerControlName="ShadowShackles">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/shadowshackles.png"/>
</ui:RadialMenuButton>
<!-- Twisted Construction -->
<ui:RadialMenuButton Name="TwistedConstructionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-twisted-construction'}" TargetLayerControlName="TwistedConstruction">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/twistedconstruction.png"/>
</ui:RadialMenuButton>
<!-- Summon Equipment -->
<ui:RadialMenuButton Name="SummonEquipmentButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-equipment'}" TargetLayerControlName="SummonEquipment">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/summonequipment.png"/>
</ui:RadialMenuButton>
<!-- Summon Dagger -->
<ui:RadialMenuButton Name="SummonDaggerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-summon-dagger'}" TargetLayerControlName="SummonDagger">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/dagger.png"/>
</ui:RadialMenuButton>
<!-- Hallucinations -->
<ui:RadialMenuButton Name="HallucinationsButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-hallucinations'}" TargetLayerControlName="Hallucinations">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/hallucinations.png"/>
</ui:RadialMenuButton>
<!-- Conceal Presence -->
<ui:RadialMenuButton Name="ConcealPresenceButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-conceal-presence'}" TargetLayerControlName="ConcealPresence">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/concealpresence.png"/>
</ui:RadialMenuButton>
<!-- Blood Rites -->
<ui:RadialMenuButton Name="BloodRitesButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-spell-blood-rites'}" TargetLayerControlName="BloodRites">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/blood_rites.png"/>
</ui:RadialMenuButton>
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -0,0 +1,22 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
x:Class="Content.Client.Runes.Panel.Ui.RunesPanelMenu"
Title="{Loc 'runes-ui-default-title'}"
MinSize="256 256"
SetSize="256 256">
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4">
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#80808005" />
</PanelContainer.PanelOverride>
<ScrollContainer Name="Scroll" HScrollEnabled="False" VerticalExpand="True">
<BoxContainer Name="RunesContainer" Orientation="Vertical" VerticalExpand="True">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,162 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Blood.Cult;
using Content.Shared.Blood.Cult.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
namespace Content.Client.Runes.Panel.Ui;
public sealed partial class RunesPanelMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnRuneSelected;
public BoxContainer RunesContainer => this.FindControl<BoxContainer>("RunesContainer");
public RunesPanelMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeRunes();
}
private void InitializeRunes()
{
AddRuneButton(Loc.GetString("offering-rune"), "BloodRuneOffering");
AddRuneButton(Loc.GetString("teleport-rune"), "BloodRuneTeleport");
AddRuneButton(Loc.GetString("empowering-rune"), "BloodRuneEmpowering");
AddRuneButton(Loc.GetString("revive-rune"), "BloodRuneRevive");
AddRuneButton(Loc.GetString("barrier-rune"), "BloodRuneBarrier");
AddRuneButton(Loc.GetString("summoning-rune"), "BloodRuneSummoning");
AddRuneButton(Loc.GetString("bloodboil-rune"), "BloodRuneBloodBoil");
AddRuneButton(Loc.GetString("spiritrealm-rune"), "BloodRuneSpiritealm");
AddRuneButton(Loc.GetString("ritual-dimensional-rending-rune"), "BloodRuneRitualDimensionalRending");
}
private void AddRuneButton(string runeName, string protoId)
{
var button = new Button
{
Text = runeName,
MinSize = new Vector2(300, 32),
MaxSize = new Vector2(300, 32),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
};
button.OnPressed += _ => HandleRuneSelection(protoId);
RunesContainer.AddChild(button);
}
private void HandleRuneSelection(string protoId)
{
OnRuneSelected?.Invoke(protoId);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new RuneSelectEvent(netEntity, protoId));
Close();
}
public new void Close()
{
base.Close();
}
}
[GenerateTypedNameReferences]
public sealed partial class EmpoweringRuneMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<string>? OnSelectSpell;
public EmpoweringRuneMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
StunButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultStun");
TeleportButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTeleport");
ElectromagneticPulseButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultElectromagneticPulse");
ShadowShacklesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultShadowShackles");
TwistedConstructionButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultTwistedConstruction");
SummonEquipmentButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonEquipment");
SummonDaggerButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultSummonDagger");
HallucinationsButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultHallucinations");
ConcealPresenceButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultConcealPresence");
BloodRitesButton.OnButtonUp += _ => HandleSpellSelection("ActionBloodCultBloodRites");
}
private void HandleSpellSelection(string spellName)
{
OnSelectSpell?.Invoke(spellName);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new EmpoweringRuneMenuClosedEvent(netEntity, spellName));
Close();
}
}
public sealed partial class SummoningRunePanelMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public BoxContainer CultistsContainer => this.FindControl<BoxContainer>("CultistsContainer");
public SummoningRunePanelMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
InitializeButtons();
}
private void InitializeButtons()
{
var cultistQuery = _entityManager.EntityQueryEnumerator<BloodCultistComponent, MetaDataComponent>();
while (cultistQuery.MoveNext(out var uid, out _, out var metaData))
{
var entityName = metaData.EntityName;
AddCultistButton(entityName, uid);
}
}
private void AddCultistButton(string cultistName, EntityUid cultistUid)
{
var button = new Button
{
Text = cultistName,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
MinSize = new Vector2(300, 32),
MaxSize = new Vector2(300, 32)
};
button.OnPressed += _ => HandleCultistSelection(cultistUid);
CultistsContainer.AddChild(button);
}
private void HandleCultistSelection(EntityUid cultistUid)
{
var netTargerEntity = _entityManager.GetNetEntity(cultistUid);
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
_entityNetworkManager.SendSystemNetworkMessage(new SummoningSelectedEvent(netEntity, netTargerEntity));
Close();
}
}

View File

@@ -0,0 +1,144 @@
using Content.Shared.Blood.Cult;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Runes.Panel.Ui
{
public sealed class RunesMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private RunesPanelMenu? _panel;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<RunesMenuOpenedEvent>(OnRunesMenuReceived);
}
private void OnRunesMenuReceived(RunesMenuOpenedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_panel is null)
{
_panel = _uiManager.CreateWindow<RunesPanelMenu>();
_panel.OnClose += OnMenuClosed;
_panel.OpenCentered();
}
else
{
_panel.OpenCentered();
}
}
}
private void OnMenuClosed()
{
_panel = null;
}
}
public sealed class EmpoweringRuneMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private EmpoweringRuneMenu? _menu;
private bool _menuDisposed = false;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<EmpoweringRuneMenuOpenedEvent>(OnRuneMenuReceived);
}
private void OnRuneMenuReceived(EmpoweringRuneMenuOpenedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_menu is null)
{
_menu = _uiManager.CreateWindow<EmpoweringRuneMenu>();
_menu.OnClose += OnMenuClosed;
_menu.OpenCentered();
}
else
{
_menu.OpenCentered();
}
}
Timer.Spawn(30000, () =>
{
if (_menu != null && !_menuDisposed)
{
_menu.Close();
}
});
}
private void OnMenuClosed()
{
_menuDisposed = true;
_menu = null;
}
}
public sealed class SummoningRuneMenuUIController : UIController
{
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SummoningRunePanelMenu? _panel;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<SummoningRuneMenuOpenedEvent>(OnRuneMenuReceived);
}
private void OnRuneMenuReceived(SummoningRuneMenuOpenedEvent args, EntitySessionEventArgs eventArgs)
{
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
var userEntity = _entityManager.GetEntity(args.Uid);
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
{
if (_panel is null)
{
_panel = _uiManager.CreateWindow<SummoningRunePanelMenu>();
_panel.OnClose += OnMenuClosed;
_panel.OpenCentered();
}
else
{
_panel.OpenCentered();
}
Timer.Spawn(30000, () =>
{
if (_panel != null)
{
_panel.Close();
}
});
}
}
private void OnMenuClosed()
{
_panel = null;
}
}
}

View File

@@ -0,0 +1,22 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
x:Class="Content.Client.Runes.Panel.Ui.SummoningRunePanelMenu"
Title="{Loc 'runes-ui-default-title'}"
MinSize="512 340"
SetSize="512 340">
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4">
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#80808005" />
</PanelContainer.PanelOverride>
<ScrollContainer Name="Scroll" HScrollEnabled="False" VerticalExpand="True">
<BoxContainer Name="CultistsContainer" Orientation="Vertical" VerticalExpand="True">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</DefaultWindow>