Изменение экрана КПБ во время раунда (#3342)

This commit is contained in:
Krosus777
2025-08-20 15:38:14 +02:00
committed by GitHub
parent 968cd62fa4
commit 79c44377cf
14 changed files with 343 additions and 6 deletions

View File

@@ -0,0 +1,76 @@
using System;
using System.Numerics;
using Content.Shared.Corvax.Ipc;
using Content.Shared.Humanoid.Markings;
using Content.Client.UserInterface.Controls;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Corvax.Ipc;
public sealed class IpcFaceMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
private readonly SpriteSystem _sprite;
private readonly ItemList _list;
public event Action<string>? FaceSelected;
private bool _suppressSelection;
public IpcFaceMenu()
{
IoCManager.InjectDependencies(this);
_sprite = _entMan.System<SpriteSystem>();
Title = Loc.GetString("ipc-face-menu-title");
MinSize = new Vector2(300, 400);
_list = new ItemList
{
VerticalExpand = true,
HorizontalExpand = true
};
_list.OnItemSelected += args =>
{
if (_suppressSelection)
return;
if (_list[args.ItemIndex].Metadata is string id)
FaceSelected?.Invoke(id);
};
ContentsContainer.AddChild(_list);
}
public void Populate(string profileId, string selected)
{
_suppressSelection = true;
_list.Clear();
var profile = _prototype.Index<IpcFaceProfilePrototype>(profileId);
foreach (var face in profile.Faces)
{
if (!_prototype.TryIndex(face, out MarkingPrototype? marking) || marking.Sprites.Count == 0)
continue;
if (marking.Sprites[0] is not SpriteSpecifier.Rsi rsi)
continue;
var texture = _sprite.Frame0(rsi);
var item = _list.AddItem(Loc.GetString($"marking-{marking.ID}"), texture);
item.Metadata = marking.ID;
if (marking.ID == selected)
item.Selected = true;
}
_suppressSelection = false;
}
}

View File

@@ -0,0 +1,46 @@
using System;
using Content.Shared.Corvax.Ipc;
using Robust.Client.GameObjects;
using Content.Shared.UserInterface;
namespace Content.Client.Corvax.Ipc;
public sealed class IpcFaceUserInterface : BoundUserInterface
{
private IpcFaceMenu? _menu;
public IpcFaceUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = new IpcFaceMenu();
_menu.OnClose += Close;
_menu.FaceSelected += state =>
{
SendPredictedMessage(new IpcFaceSelectMessage(state));
Close();
};
_menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState? state)
{
if (state != null)
base.UpdateState(state);
if (_menu == null || state is not IpcFaceBuiState msg)
return;
_menu.Populate(msg.Profile, msg.Selected);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
_menu = null;
}
}

View File

@@ -12,11 +12,19 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.Sound.Components;
using Content.Shared.UserInterface;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
using Robust.Shared.Prototypes;
using Content.Shared.Humanoid;
using Content.Server.Humanoid;
using Content.Shared.Humanoid.Markings;
using Robust.Shared.Player;
namespace Content.Server.Corvax.Ipc;
public sealed class IpcSystem : EntitySystem
public sealed partial class IpcSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _action = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
@@ -26,9 +34,10 @@ public sealed class IpcSystem : EntitySystem
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly MarkingManager _markingManager = default!;
public override void Initialize()
{
base.Initialize();
@@ -40,18 +49,33 @@ public sealed class IpcSystem : EntitySystem
SubscribeLocalEvent<IpcComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<IpcComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
SubscribeLocalEvent<IpcComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<IpcComponent, OpenIpcFaceActionEvent>(OnOpenFaceAction);
Subs.BuiEvents<IpcComponent>(IpcFaceUiKey.Face, subs =>
{
subs.Event<IpcFaceSelectMessage>(OnFaceSelected);
});
}
private void OnMapInit(EntityUid uid, IpcComponent component, MapInitEvent args)
{
UpdateBatteryAlert((uid, component));
_action.AddAction(uid, ref component.ActionEntity, component.DrainBatteryAction);
_action.AddAction(uid, ref component.ChangeFaceActionEntity, component.ChangeFaceAction);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
if (TryComp<HumanoidAppearanceComponent>(uid, out var appearance) &&
appearance.MarkingSet.TryGetCategory(MarkingCategories.Snout, out var markings) &&
markings.Count > 0)
{
component.SelectedFace = markings[0].MarkingId;
Dirty(uid, component);
}
}
private void OnComponentShutdown(EntityUid uid, IpcComponent component, ComponentShutdown args)
{
_action.RemoveAction(uid, component.ActionEntity);
_action.RemoveAction(uid, component.ChangeFaceActionEntity);
}
private void OnPowerCellChanged(EntityUid uid, IpcComponent component, PowerCellChangedEvent args)
@@ -90,7 +114,7 @@ public sealed class IpcSystem : EntitySystem
if (!_powerCell.TryGetBatteryFromSlot(ent, out var battery, slot) || battery.CurrentCharge / battery.MaxCharge < 0.01f)
{
_alerts.ClearAlert(ent, ent.Comp.BatteryAlert);
_alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert);
_alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert);
_movementSpeedModifier.RefreshMovementSpeedModifiers(ent.Owner);
return;
@@ -116,6 +140,40 @@ public sealed class IpcSystem : EntitySystem
}
}
private void OnOpenFaceAction(EntityUid uid, IpcComponent comp, OpenIpcFaceActionEvent args)
{
if (args.Handled)
return;
if (!TryComp<ActorComponent>(uid, out var actor))
return;
_ui.SetUiState(uid, IpcFaceUiKey.Face, new IpcFaceBuiState(comp.FaceProfile, comp.SelectedFace));
_ui.TryToggleUi(uid, IpcFaceUiKey.Face, actor.PlayerSession);
args.Handled = true;
}
private void OnFaceSelected(Entity<IpcComponent> ent, ref IpcFaceSelectMessage msg)
{
if (TryComp<HumanoidAppearanceComponent>(ent.Owner, out var appearance))
{
var category = MarkingCategories.Snout;
if (appearance.MarkingSet.TryGetCategory(category, out var markings) && markings.Count > 0)
{
_humanoid.SetMarkingId(ent.Owner, category, 0, msg.State, appearance);
}
else if (_markingManager.Markings.TryGetValue(msg.State, out var proto))
{
appearance.MarkingSet.AddBack(category, proto.AsMarking());
Dirty(ent.Owner, appearance);
}
}
ent.Comp.SelectedFace = msg.State;
Dirty(ent);
_ui.CloseUi(ent.Owner, IpcFaceUiKey.Face);
}
private void OnEmpPulse(EntityUid uid, IpcComponent component, ref EmpPulseEvent args)
{
args.Affected = true;

View File

@@ -9,6 +9,7 @@ namespace Content.Shared.Corvax.Ipc;
/// This is used for...
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
public sealed partial class IpcComponent : Component
{
[DataField]
@@ -20,9 +21,21 @@ public sealed partial class IpcComponent : Component
[DataField]
public EntProtoId DrainBatteryAction = "ActionDrainBattery";
[DataField]
public EntProtoId ChangeFaceAction = "ActionIpcChangeFace";
[DataField]
public EntityUid? ActionEntity;
[DataField]
public EntityUid? ChangeFaceActionEntity;
[DataField, AutoNetworkedField]
public string SelectedFace = string.Empty;
[DataField, AutoNetworkedField]
public ProtoId<IpcFaceProfilePrototype> FaceProfile = "DefaultIpcFaces";
public bool DrainActivated;
}
@@ -30,3 +43,7 @@ public sealed partial class ToggleDrainActionEvent : InstantActionEvent
{
}
public sealed partial class OpenIpcFaceActionEvent : InstantActionEvent
{
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.Corvax.Ipc;
[Serializable, NetSerializable]
public sealed class IpcFaceSelectMessage : BoundUserInterfaceMessage
{
public readonly string State;
public IpcFaceSelectMessage(string state)
{
State = state;
}
}
[Serializable, NetSerializable]
public sealed class IpcFaceBuiState : BoundUserInterfaceState
{
public readonly string Profile;
public readonly string Selected;
public IpcFaceBuiState(string profile, string selected)
{
Profile = profile;
Selected = selected;
}
}
[NetSerializable, Serializable]
public enum IpcFaceUiKey : byte
{
Face
}

View File

@@ -0,0 +1,21 @@
using Robust.Shared.Prototypes;
using Content.Shared.Humanoid.Markings;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Corvax.Ipc;
/// <summary>
/// Prototype defining a collection of IPC face sprites.
/// </summary>
[Prototype("ipcFaceProfile")]
public sealed partial class IpcFaceProfilePrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
/// <summary>
/// Available face markings for this profile.
/// </summary>
[DataField("faces", required: true)]
public List<ProtoId<MarkingPrototype>> Faces { get; private set; } = new();
}

View File

@@ -0,0 +1,3 @@
ipc-component-ready = Ready to drain
ipc-component-disabled = Battery charging disabled.
ipc-face-menu-title = IPC monitor

View File

@@ -0,0 +1,32 @@
marking-BreakoutS = Breakout
marking-NatureS = Nature
marking-EightS = Eight
marking-OrangeHespAltS = Orange (Hesp alt.)
marking-PinkHespAltS = Pink (Hesp alt.)
marking-SmokingS = Smoking
marking-RainbowS = Rainbow
marking-HeartS = Heart
marking-ScrollS = Scroll
marking-ScrollHespAltS = Scroll (Hesp alt.)
marking-StaticS = Static
marking-GreenS = Green
marking-YellowS = Yellow
marking-GolGliderS = Gold glider
marking-RgbHespAltS = RGB (Hesp alt.)
marking-GogglesHespAltS = Goggles (Hesp alt.)
marking-OffHespAltS = Off (Hesp alt.)
marking-PinkS = Pink
marking-RedS = Red
marking-ShowerS = Shower
marking-RainbowHespAltS = Rainbow (Hesp alt.)
marking-GogglesS = Goggles
marking-MonoeyeS = Mono-eye
marking-BlueS = Blue
marking-TestS = Test
marking-RgbS = RGB
marking-MusicS = Music
marking-ConsoleS = Console
marking-PurpleS = Purple
marking-OrangeS = Orange
marking-WaitingS = Waiting

View File

@@ -1,2 +1,4 @@
ent-ActionDrainBattery = Drain battery
.desc = Drain battery
ent-ActionIpcChangeFace = Change face
.desc = Change the IPC monitor face

View File

@@ -1,2 +1,3 @@
ipc-component-ready = Готов к разрядке
ipc-component-disabled = Зарядка батареи отключена.
ipc-face-menu-title = Выбор экрана

View File

@@ -1,2 +1,4 @@
ent-ActionDrainBattery = Включить заряд
.desc = Разрядить батарею
ent-ActionIpcChangeFace = Сменить экран
.desc = Изменить экран КПБ

View File

@@ -9,3 +9,14 @@
itemIconStyle: NoItem
- type: InstantAction
event: !type:ToggleDrainActionEvent
- type: entity
id: ActionIpcChangeFace
name: Change face
description: Change the IPC monitor face
components:
- type: Action
icon: { sprite: Interface/Actions/actions_ai.rsi, state: crew_monitor }
itemIconStyle: NoItem
- type: InstantAction
event: !type:OpenIpcFaceActionEvent

View File

@@ -337,6 +337,8 @@
type: HumanoidMarkingModifierBoundUserInterface
enum.StrippingUiKey.Key:
type: StrippableBoundUserInterface
enum.IpcFaceUiKey.Face:
type: IpcFaceUserInterface
- type: Puller
- type: Speech
speechSounds: Alto
@@ -383,7 +385,6 @@
normalBodyTemperature: 310.15
thermalRegulationTemperatureThreshold: 2
- type: entity
save: false
name: Urist McHands

View File

@@ -0,0 +1,35 @@
- type: ipcFaceProfile
id: DefaultIpcFaces
faces:
- BreakoutS
- NatureS
- EightS
- OrangeHespAltS
- PinkHespAltS
- SmokingS
- RainbowS
- HeartS
- ScrollS
- ScrollHespAltS
- StaticS
- GreenS
- YellowS
- GolGliderS
- RgbHespAltS
- GogglesHespAltS
- OffHespAltS
- PinkS
- RedS
- ShowerS
- RainbowHespAltS
- GogglesS
- MonoeyeS
- BlueS
- TestS
- RgbS
- MusicS
- ConsoleS
- PurpleS
- OrangeS
- WaitingS