Basic Dynamic Power Consumption Systems (#41885)

* init commit

* Addr reviews
This commit is contained in:
ArtisticRoomba
2025-12-22 08:43:02 -08:00
committed by GitHub
parent 347a728ab7
commit dde01f746f
5 changed files with 324 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
using Content.Shared.Coordinates;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.IntegrationTests.Tests.Power;
[TestFixture]
public sealed class PowerStateTest
{
[TestPrototypes]
private const string Prototypes = @"
- type: entity
id: PowerStateApcReceiverDummy
components:
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: Transform
anchored: true
- type: PowerState
isWorking: false
idlePowerDraw: 10
workingPowerDraw: 50
";
/// <summary>
/// Asserts that switching from idle to working updates the power receiver load to the working draw.
/// </summary>
[Test]
public async Task SetWorkingState_IdleToWorking_UpdatesLoad()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var mapManager = server.ResolveDependency<IMapManager>();
var entManager = server.ResolveDependency<IEntityManager>();
var mapSys = entManager.System<SharedMapSystem>();
await server.WaitAssertion(() =>
{
mapSys.CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
mapSys.SetTile(grid, Vector2i.Zero, new Tile(1));
var ent = entManager.SpawnEntity("PowerStateApcReceiverDummy", grid.Owner.ToCoordinates());
var receiver = entManager.GetComponent<Server.Power.Components.ApcPowerReceiverComponent>(ent);
var powerState = entManager.GetComponent<PowerStateComponent>(ent);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.False);
Assert.That(receiver.Load, Is.EqualTo(powerState.IdlePowerDraw).Within(0.01f));
});
var system = entManager.System<PowerStateSystem>();
system.SetWorkingState((ent, powerState), true);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.True);
Assert.That(receiver.Load, Is.EqualTo(powerState.WorkingPowerDraw).Within(0.01f));
});
});
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that switching from working to idle updates the power receiver load to the idle draw.
/// </summary>
[Test]
public async Task SetWorkingState_WorkingToIdle_UpdatesLoad()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var mapManager = server.ResolveDependency<IMapManager>();
var entManager = server.ResolveDependency<IEntityManager>();
var mapSys = entManager.System<SharedMapSystem>();
await server.WaitAssertion(() =>
{
mapSys.CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
mapSys.SetTile(grid, Vector2i.Zero, new Tile(1));
var ent = entManager.SpawnEntity("PowerStateApcReceiverDummy", grid.Owner.ToCoordinates());
var receiver = entManager.GetComponent<Server.Power.Components.ApcPowerReceiverComponent>(ent);
var powerState = entManager.GetComponent<PowerStateComponent>(ent);
var system = entManager.System<PowerStateSystem>();
Entity<PowerStateComponent> newEnt = (ent, powerState);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.False);
Assert.That(receiver.Load, Is.EqualTo(powerState.IdlePowerDraw).Within(0.01f));
});
system.SetWorkingState(newEnt, true);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.True);
Assert.That(receiver.Load, Is.EqualTo(powerState.WorkingPowerDraw).Within(0.01f));
});
system.SetWorkingState(newEnt, false);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.False);
Assert.That(receiver.Load, Is.EqualTo(powerState.IdlePowerDraw).Within(0.01f));
});
});
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that setting the working state to the current state does not change the power receiver load.
/// </summary>
[Test]
public async Task SetWorkingState_AlreadyInState_NoChange()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var mapManager = server.ResolveDependency<IMapManager>();
var entManager = server.ResolveDependency<IEntityManager>();
var mapSys = entManager.System<SharedMapSystem>();
await server.WaitAssertion(() =>
{
mapSys.CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
mapSys.SetTile(grid, Vector2i.Zero, new Tile(1));
var ent = entManager.SpawnEntity("PowerStateApcReceiverDummy", grid.Owner.ToCoordinates());
var receiver = entManager.GetComponent<Server.Power.Components.ApcPowerReceiverComponent>(ent);
var powerState = entManager.GetComponent<PowerStateComponent>(ent);
var system = entManager.System<PowerStateSystem>();
Entity<PowerStateComponent> valueTuple = (ent, powerState);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.False);
Assert.That(receiver.Load, Is.EqualTo(powerState.IdlePowerDraw).Within(0.01f));
});
system.SetWorkingState(valueTuple, false);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.False);
Assert.That(receiver.Load, Is.EqualTo(powerState.IdlePowerDraw).Within(0.01f));
});
system.SetWorkingState(valueTuple, true);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.True);
Assert.That(receiver.Load, Is.EqualTo(powerState.WorkingPowerDraw).Within(0.01f));
});
system.SetWorkingState(valueTuple, true);
Assert.Multiple(() =>
{
Assert.That(powerState.IsWorking, Is.True);
Assert.That(receiver.Load, Is.EqualTo(powerState.WorkingPowerDraw).Within(0.01f));
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -0,0 +1,32 @@
namespace Content.Shared.Power.Components;
/// <summary>
/// Generic component for giving entities "idle" and "working" power states.
/// </summary>
/// <remarks><para>Entities that have more complex power draw
/// (ex. a thermomachine whose heating power is directly tied to its power consumption)
/// should just directly set their load on the <see cref="SharedApcPowerReceiverComponent"/>.</para>
///
/// <para>This is also applicable if you would like to add
/// more complex power behavior that is tied to a generic component.</para></remarks>
[RegisterComponent]
public sealed partial class PowerStateComponent : Component
{
/// <summary>
/// Whether the entity is currently in the working state.
/// </summary>
[DataField]
public bool IsWorking;
/// <summary>
/// The idle power draw of this entity when not working, in watts.
/// </summary>
[DataField]
public float IdlePowerDraw = 20f;
/// <summary>
/// The working power draw of this entity when working, in watts.
/// </summary>
[DataField]
public float WorkingPowerDraw = 350f;
}

View File

@@ -0,0 +1,16 @@
namespace Content.Shared.Power.Components;
/// <summary>
/// Component for entities that want to increase their power usage to a working state when
/// a UI on the machine is open. Requires <see cref="PowerStateComponent"/>.
/// </summary>
[RegisterComponent]
public sealed partial class UIPowerStateComponent : Component
{
/// <summary>
/// List of UI keys that will trigger the working state.
/// If null, any UI open will trigger the working state.
/// </summary>
[DataField]
public List<Enum>? Keys;
}

View File

@@ -0,0 +1,44 @@
using Content.Shared.Power.Components;
using JetBrains.Annotations;
namespace Content.Shared.Power.EntitySystems;
/// <summary>
/// Generic system that handles entities with <see cref="PowerStateComponent"/>.
/// Used for simple machines that only need to switch between "idle" and "working" power states.
/// </summary>
public sealed class PowerStateSystem : EntitySystem
{
[Dependency] private readonly SharedPowerReceiverSystem _powerReceiverSystem = default!;
private EntityQuery<PowerStateComponent> _powerStateQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PowerStateComponent, ComponentStartup>(OnComponentStartup);
_powerStateQuery = GetEntityQuery<PowerStateComponent>();
}
private void OnComponentStartup(Entity<PowerStateComponent> ent, ref ComponentStartup args)
{
SetWorkingState(ent.Owner, ent.Comp.IsWorking);
}
/// <summary>
/// Sets the working state of the entity, adjusting its power draw accordingly.
/// </summary>
/// <param name="ent">The entity to set the working state for.</param>
/// <param name="working">Whether the entity should be in the working state.</param>
[PublicAPI]
public void SetWorkingState(Entity<PowerStateComponent?> ent, bool working)
{
if (!_powerStateQuery.Resolve(ent, ref ent.Comp))
return;
_powerReceiverSystem.SetLoad(ent.Owner, working ? ent.Comp.WorkingPowerDraw : ent.Comp.IdlePowerDraw);
ent.Comp.IsWorking = working;
}
}

View File

@@ -0,0 +1,46 @@
using Content.Shared.Power.Components;
namespace Content.Shared.Power.EntitySystems;
/// <summary>
/// System for entities with <see cref="UIPowerStateComponent"/>.
/// Entities with this component will increase their power usage to a working state
/// when a UI on the entity is open.
/// </summary>
public sealed class UIPowerStateSystem : EntitySystem
{
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly PowerStateSystem _powerState = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UIPowerStateComponent, BoundUIOpenedEvent>(OnUiOpened);
SubscribeLocalEvent<UIPowerStateComponent, BoundUIClosedEvent>(OnUiClosed);
}
private void OnUiClosed(Entity<UIPowerStateComponent> ent, ref BoundUIClosedEvent args)
{
if (ent.Comp.Keys is null)
{
if (_ui.IsAnyUiOpen(ent.Owner))
return;
}
else
{
if (_ui.IsUiOpen(ent.Owner, ent.Comp.Keys))
return;
}
_powerState.SetWorkingState(ent.Owner, false);
}
private void OnUiOpened(Entity<UIPowerStateComponent> ent, ref BoundUIOpenedEvent args)
{
if (ent.Comp.Keys is not null && !ent.Comp.Keys.Contains(args.UiKey))
return;
_powerState.SetWorkingState(ent.Owner, true);
}
}