+++ /dev/null
-using Content.Shared.APC;
-using Robust.Client.GameObjects;
-
-namespace Content.Client.Power.APC
-{
- public sealed class ApcVisualizer : AppearanceVisualizer
- {
- public static readonly Color LackColor = Color.FromHex("#d1332e");
- public static readonly Color ChargingColor = Color.FromHex("#2e8ad1");
- public static readonly Color FullColor = Color.FromHex("#3db83b");
- public static readonly Color EmagColor = Color.FromHex("#1f48d6");
-
- [Obsolete("Subscribe to AppearanceChangeEvent instead.")]
- public override void OnChangeData(AppearanceComponent component)
- {
- base.OnChangeData(component);
-
- var ent = IoCManager.Resolve<IEntityManager>();
- var sprite = ent.GetComponent<SpriteComponent>(component.Owner);
- if (component.TryGetData<ApcChargeState>(ApcVisuals.ChargeState, out var chargeState))
- {
- if (ent.TryGetComponent(component.Owner, out SharedPointLightComponent? light))
- {
- light.Color = chargeState switch
- {
- ApcChargeState.Lack => LackColor,
- ApcChargeState.Charging => ChargingColor,
- ApcChargeState.Full => FullColor,
- ApcChargeState.Emag => EmagColor,
- _ => LackColor
- };
- }
- }
- }
-
- enum ApcVisualLayers : byte
- {
- ChargeState,
- Lock,
- Equipment,
- Lighting,
- Environment,
- }
- }
-}
--- /dev/null
+using Content.Shared.APC;
+
+namespace Content.Client.Power.APC;
+
+[RegisterComponent]
+[Access(typeof(ApcVisualizerSystem))]
+public sealed class ApcVisualsComponent : Component
+{
+#region Indicators
+
+#region Locks
+
+ /// <summary>
+ /// The number of lock indicators on the APC.
+ /// </summary>
+ [DataField("numLockIndicators")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public byte LockIndicators = 2;
+
+ /// <summary>
+ /// The prefix used for the sprite state suffix of the lock indicator lights.
+ /// Valid states are of the form \<BASE\>\<PREFIX\>\<IDX>\-\<STATE\>
+ /// </summary>
+ [DataField("lockIndicatorPrefix")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string LockPrefix = "lock";
+
+ /// <summary>
+ /// The suffixes used for the sprite state suffix of the lock indicator lights.
+ /// Valid states are of the form \<PREFIX\>\<IDX\>-\<STATE\>
+ /// </summary>
+ [DataField("lockIndicatorSuffixes")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string[] LockSuffixes = new string[(byte)(2 << (sbyte)ApcLockState.LogWidth)]{"unlocked", "locked"};
+
+#endregion Locks
+
+#region Channels
+
+ /// <summary>
+ /// The number of output channel indicator lights on the APC.
+ /// </summary>
+ [DataField("numChannelIndicators")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public byte ChannelIndicators = 3;
+
+ /// <summary>
+ /// The prefix used for the sprite state suffix of the channel indicator lights.
+ /// Valid states are of the form \<BASE\>\<PREFIX\>\<IDX\>-\<STATE\>
+ /// </summary>
+ [DataField("channelIndicatorPrefix")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string ChannelPrefix = "channel";
+
+ /// <summary>
+ /// The suffixes used for the sprite state suffix of the channel indicator lights.
+ /// Valid states are of the form \<PREFIX\>\<IDX\>-\<STATE\>
+ /// </summary>
+ [DataField("channelIndicatorSuffixes")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string[] ChannelSuffixes = new string[(byte)(2 << (sbyte)ApcChannelState.LogWidth)]{"auto_off", "manual_off", "auto_on", "manual_on"};
+
+#endregion Channels
+
+#endregion Indicators
+
+#region Screen
+
+ /// <summary>
+ /// The prefix used to construct the sprite state suffix used for the screen overlay.
+ /// Valid sprite states are of the form \<PREFIX\>-\<SUFFIX\>.
+ /// </summary>
+ [DataField("screenStatePrefix")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string ScreenPrefix = "display";
+
+ /// <summary>
+ /// The suffix used to construct the sprite state suffix used for the screen overlay.
+ /// Valid sprite states are of the form \<PREFIX\>-\<STATE\>.
+ /// </summary>
+ [DataField("screenStateSuffixes")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string[] ScreenSuffixes = new string[(byte)ApcChargeState.NumStates]{"lack", "charging", "full", "remote"};
+
+ /// <summary>
+ /// The colors of the light emitted by the APC given a particular display state.
+ /// </summary>
+ [DataField("screenColors")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public Color[] ScreenColors = new Color[(byte)ApcChargeState.NumStates]{Color.FromHex("#d1332e"), Color.FromHex("#2e8ad1"), Color.FromHex("#3db83b"), Color.FromHex("#ffac1c")};
+
+ /// <summary>
+ /// The sprite state of the unlit overlay used for the APC screen when the APC has been emagged.
+ /// </summary>
+ [DataField("emaggedScreenState")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string EmaggedScreenState = "emag-unlit";
+
+ /// <summary>
+ /// The sprite state of the unlit overlay used for the APC screen when the APC has been emagged.
+ /// </summary>
+ [DataField("emaggedScreenColor")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public Color EmaggedScreenColor = Color.FromHex("#1f48d6");
+
+#endregion Screen
+}
--- /dev/null
+using Content.Shared.APC;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Power.APC;
+
+public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
+{
+ protected override void OnAppearanceChange(EntityUid uid, ApcVisualsComponent comp, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null)
+ return;
+
+ // Handle APC screen overlay:
+ if(!AppearanceSystem.TryGetData<ApcChargeState>(uid, ApcVisuals.ChargeState, out var chargeState, args.Component))
+ chargeState = ApcChargeState.Lack;
+
+ if (chargeState >= 0 && chargeState < ApcChargeState.NumStates)
+ {
+ args.Sprite.LayerSetState(ApcVisualLayers.ChargeState, $"{comp.ScreenPrefix}-{comp.ScreenSuffixes[(sbyte)chargeState]}");
+
+ // LockState does nothing currently. The backend doesn't exist.
+ if (AppearanceSystem.TryGetData<byte>(uid, ApcVisuals.LockState, out var lockStates, args.Component))
+ {
+ for(var i = 0; i < comp.LockIndicators; ++i)
+ {
+ var layer = ((byte)ApcVisualLayers.LockIndicatorOverlayStart + i);
+ sbyte lockState = (sbyte)((lockStates >> (i << (sbyte)ApcLockState.LogWidth)) & (sbyte)ApcLockState.All);
+ args.Sprite.LayerSetState(layer, $"{comp.LockPrefix}{i}-{comp.LockSuffixes[lockState]}");
+ args.Sprite.LayerSetVisible(layer, true);
+ }
+ }
+
+ // ChannelState does nothing currently. The backend doesn't exist.
+ if (AppearanceSystem.TryGetData<byte>(uid, ApcVisuals.ChannelState, out var channelStates, args.Component))
+ {
+ for(var i = 0; i < comp.ChannelIndicators; ++i)
+ {
+ var layer = ((byte)ApcVisualLayers.ChannelIndicatorOverlayStart + i);
+ sbyte channelState = (sbyte)((channelStates >> (i << (sbyte)ApcChannelState.LogWidth)) & (sbyte)ApcChannelState.All);
+ args.Sprite.LayerSetState(layer, $"{comp.ChannelPrefix}{i}-{comp.ChannelSuffixes[channelState]}");
+ args.Sprite.LayerSetVisible(layer, true);
+ }
+ }
+
+ if (TryComp<SharedPointLightComponent>(uid, out var light))
+ light.Color = comp.ScreenColors[(sbyte)chargeState];
+ }
+ else
+ {
+ /// Overrides all of the lock and channel indicators.
+ args.Sprite.LayerSetState(ApcVisualLayers.ChargeState, comp.EmaggedScreenState);
+ for(var i = 0; i < comp.LockIndicators; ++i)
+ {
+ var layer = ((byte)ApcVisualLayers.LockIndicatorOverlayStart + i);
+ args.Sprite.LayerSetVisible(layer, false);
+ }
+ for(var i = 0; i < comp.ChannelIndicators; ++i)
+ {
+ var layer = ((byte)ApcVisualLayers.ChannelIndicatorOverlayStart + i);
+ args.Sprite.LayerSetVisible(layer, false);
+ }
+
+ if (TryComp<SharedPointLightComponent>(uid, out var light))
+ light.Color = comp.EmaggedScreenColor;
+ }
+ }
+}
+
+enum ApcVisualLayers : byte
+{
+ /// <summary>
+ /// The sprite layer used for the interface lock indicator light overlay.
+ /// </summary>
+ InterfaceLock = 0,
+ /// <summary>
+ /// The sprite layer used for the panel lock indicator light overlay.
+ /// </summary>
+ PanelLock = 1,
+ /// <summary>
+ /// The first of the lock indicator light layers.
+ /// </summary>
+ LockIndicatorOverlayStart = InterfaceLock,
+
+ /// <summary>
+ /// The sprite layer used for the equipment channel indicator light overlay.
+ /// </summary>
+ Equipment = 2,
+ /// <summary>
+ /// The sprite layer used for the lighting channel indicator light overlay.
+ /// </summary>
+ Lighting = 3,
+ /// <summary>
+ /// The sprite layer used for the environment channel indicator light overlay.
+ /// </summary>
+ Environment = 4,
+ /// <summary>
+ /// The first of the channel status indicator light layers.
+ /// </summary>
+ ChannelIndicatorOverlayStart = Equipment,
+
+ /// <summary>
+ /// The sprite layer used for the APC screen overlay.
+ /// </summary>
+ ChargeState = 5,
+}
namespace Content.Shared.APC
{
[Serializable, NetSerializable]
- public enum ApcVisuals
+ public enum ApcVisuals : byte
{
/// <summary>
- /// APC lights/HUD.
+ /// APC locks.
+ /// </summary>
+ LockState,
+ /// <summary>
+ /// APC channels.
+ /// </summary>
+ ChannelState,
+ /// <summary>
+ /// APC lights/HUD.
/// </summary>
ChargeState,
}
[Serializable, NetSerializable]
- public enum ApcChargeState
+ public enum ApcPanelState : sbyte
+ {
+ /// <summary>
+ /// APC is closed.
+ /// </summary>
+ Closed = 0,
+ /// <summary>
+ /// APC is opened.
+ /// </summary>
+ Open = 1,
+ /// <summary>
+ /// APC is oaisdoj.
+ /// </summary>
+ Error = -1,
+ }
+
+ /// <summary>
+ /// The state of the APC interface lock.
+ /// None of this is implemented.
+ /// </summary>
+ [Serializable, NetSerializable]
+ public enum ApcLockState : sbyte
+ {
+ /// <summary>
+ /// Empty bitmask.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Bitfield indicating status of APC lock indicator.
+ /// </summary>
+ Lock = (1<<0),
+ /// <summary>
+ /// Bit state indicating that the given APC lock is unlocked.
+ /// </summary>
+ Unlocked = None,
+ /// <summary>
+ /// Bit state indicating that the given APC lock is locked.
+ /// </summary>
+ Locked = (1<<0),
+
+ /// <summary>
+ /// Bitmask for the full state for a given APC lock indicator.
+ /// </summary>
+ All = (Lock),
+
+ /// <summary>
+ /// The log 2 width in bits of the bitfields indicating the status of an APC lock indicator.
+ /// Used for bit shifting operations (Mask for the state for indicator i is (All << (i << LogWidth))).
+ /// </summary>
+ LogWidth = 0,
+ }
+
+ /// <summary>
+ /// APC power channel states.
+ /// None of this is implemented.
+ /// </summary>
+ public enum ApcChannelState : sbyte
+ {
+ /// <summary>
+ /// Empty bitmask.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Bitfield indicating whether the APC is automatically regulating the given channel.
+ /// </summary>
+ Control = (1<<0),
+ /// <summary>
+ /// Bit state indicating that the APC has been set to automatically toggle the given channel depending on available power.
+ /// </summary>
+ Auto = None,
+ /// <summary>
+ /// Bit state indicating that the APC has been set to always provide/not provide power on the given channel if possible.
+ /// </summary>
+ Manual = Control,
+
+ /// <summary>
+ /// Bitfield indicating whether the APC is currently providing power on the given channel.
+ /// </summary>
+ Power = (1<<1),
+ /// <summary>
+ /// Bit state indicating that the APC is currently not providing power on the given channel.
+ /// </summary>
+ Off = None,
+ /// <summary>
+ /// Bit state indicating that the APC is currently providing power on the given channel.
+ /// </summary>
+ On = Power,
+
+ /// <summary>
+ /// Bitmask for the full state for a given APC power channel.
+ /// </summary>
+ All = Power | Control,
+
+ /// <summary>
+ /// State that indicates the given channel has been automatically disabled.
+ /// </summary>
+ AutoOff = (Off | Auto),
+ /// <summary>
+ /// State that indicates the given channel has been automatically enabled.
+ /// </summary>
+ AutoOn = (On | Auto),
+ /// <summary>
+ /// State that indicates the given channel has been manually disabled.
+ /// </summary>
+ ManualOff = (Off | Manual),
+ /// <summary>
+ /// State that indicates the given channel has been manually enabled.
+ /// </summary>
+ ManualOn = (On | Manual),
+
+ /// <summary>
+ /// The log 2 width in bits of the bitfields indicating the status of an APC power channel.
+ /// Used for bit shifting operations (Mask for the state for channel i is (All << (i << LogWidth))).
+ /// </summary>
+ LogWidth = 1,
+ }
+
+ [Serializable, NetSerializable]
+ public enum ApcChargeState : sbyte
{
/// <summary>
- /// APC does not have enough power to charge cell (if necessary) and keep powering the area.
+ /// APC does not have enough power to charge cell (if necessary) and keep powering the area.
+ /// </summary>
+ Lack = 0,
+
+ /// <summary>
+ /// APC is not full but has enough power.
+ /// </summary>
+ Charging = 1,
+
+ /// <summary>
+ /// APC battery is full and has enough power.
/// </summary>
- Lack,
+ Full = 2,
/// <summary>
- /// APC is not full but has enough power.
+ /// APC is being remotely accessed.
+ /// Currently unimplemented, though the corresponding sprite state exists in the RSI.
/// </summary>
- Charging,
+ Remote = 3,
/// <summary>
- /// APC battery is full and has enough power.
+ /// The number of valid states charge states the APC can be in.
/// </summary>
- Full,
+ NumStates = 4,
/// <summary>
/// APC is emagged (and not displaying other useful power colors at a glance)
/// </summary>
- Emag,
+ Emag = -1,
}
[Serializable, NetSerializable]
- state: panel
- state: on
shader: unshaded
-# - type: Appearance
-# visuals:
-# - type: ApcVisualizer
- type: NodeContainer
examinable: true
nodes:
sprite: Structures/Power/apc.rsi
layers:
- state: base
- - state: powered-1
+ - state: panel
+ map: ["enum.WiresVisualLayers.MaintenancePanel"]
+ visible: false
+ - state: display-charging
shader: unshaded
map: ["enum.ApcVisualLayers.ChargeState"]
- - state: apcox-0
+ - state: lock0-unlocked
+ shader: unshaded
+ map: ["enum.ApcVisualLayers.InterfaceLock"]
+ - state: lock1-unlocked
shader: unshaded
- map: ["enum.ApcVisualLayers.Lock"]
- - state: apco0-3
+ map: ["enum.ApcVisualLayers.PanelLock"]
+ - state: channel0-auto_on
shader: unshaded
map: ["enum.ApcVisualLayers.Equipment"]
- - state: apco1-3
+ - state: channel1-auto_on
shader: unshaded
map: ["enum.ApcVisualLayers.Lighting"]
- - state: apco2-3
+ - state: channel2-auto_on
shader: unshaded
map: ["enum.ApcVisualLayers.Environment"]
- - state: panel
- map: ["enum.WiresVisualLayers.MaintenancePanel"]
- type: Appearance
- visuals:
- - type: ApcVisualizer
+ - type: ApcVisuals
- type: Battery
maxCharge: 50000
startingCharge: 0
BoardName: "APC"
LayoutId: APC
- type: WiresVisuals
- - type: GenericVisualizer
- visuals:
- enum.ApcVisuals.ChargeState:
- enum.ApcVisualLayers.ChargeState:
- Lack: { state: powered-1 }
- Charging: { state: powered-2 }
- Full: { state: powered-3 }
- Emag: { state: emag-unlit }
- type: Damageable
damageContainer: Inorganic
damageModifierSet: StrongMetallic
drawdepth: WallMountedItems
netsync: false
sprite: Structures/Power/apc.rsi
- state: apcframe
+ state: frame
- type: Construction
graph: APC
node: apcFrame
"y": 32
},
"states": [
+ {
+ "name": "base"
+ },
{
"name": "broken"
},
{
- "name": "apcframe"
+ "name": "frame"
},
{
- "name": "sparks-unlit",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
+ "name": "panel"
},
{
- "name": "base"
+ "name": "lock0-unlocked"
},
{
- "name": "emag-unlit",
- "delays": [
- [
- 0.5,
- 0.5
- ]
- ]
+ "name": "lock0-locked"
},
{
- "name": "panel"
+ "name": "lock1-unlocked"
+ },
+ {
+ "name": "lock1-locked"
},
{
- "name": "apco0-0"
+ "name": "channel0-auto_off"
},
{
- "name": "apco0-1"
+ "name": "channel0-manual_off"
},
{
- "name": "apco0-2"
+ "name": "channel0-auto_on"
},
{
- "name": "apco0-3"
+ "name": "channel0-manual_on"
},
{
- "name": "apco1-0"
+ "name": "channel1-auto_off"
},
{
- "name": "apco1-1"
+ "name": "channel1-manual_off"
},
{
- "name": "apco1-2"
+ "name": "channel1-auto_on"
},
{
- "name": "apco1-3"
+ "name": "channel1-manual_on"
},
{
- "name": "apco2-0"
+ "name": "channel2-auto_off"
},
{
- "name": "apco2-1"
+ "name": "channel2-manual_off"
},
{
- "name": "apco2-2"
+ "name": "channel2-auto_on"
},
{
- "name": "apco2-3"
+ "name": "channel2-manual_on"
},
{
- "name": "powered-1",
+ "name": "display-lack",
"delays": [
[
1,
]
},
{
- "name": "powered-2",
+ "name": "display-charging",
"delays": [
[
0.1,
]
},
{
- "name": "powered-3",
+ "name": "display-full",
"delays": [
[
1,
]
},
{
- "name": "apco3-3",
+ "name": "display-remote",
"delays": [
[
0.1,
]
},
{
- "name": "apcox-0"
+ "name": "emag-unlit",
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
},
{
- "name": "apcox-1"
+ "name": "sparks-unlit",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
}
]
}