mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Predict holoprojectors and add an integration test for them (#41569)
* cleanup * fix fixtures * prediction * fix test * review * fix svalinn visuals * fix chargers * fix portable recharger and its unlit visuals * fix borgs * oomba review * fix examination prediction * predict holosign
This commit is contained in:
113
Content.IntegrationTests/Tests/Holosign/HolosignProjectorTest.cs
Normal file
113
Content.IntegrationTests/Tests/Holosign/HolosignProjectorTest.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
#nullable enable
|
||||
using Content.IntegrationTests.Tests.Movement;
|
||||
using Content.Shared.Holosign;
|
||||
using Content.Shared.PowerCell;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Holosign;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for different devices using <see cref="HolosignProjectorComponent"/>.
|
||||
/// </summary>
|
||||
[TestOf(typeof(HolosignProjectorComponent))]
|
||||
public sealed class HolosignProjectorTest : MovementTest
|
||||
{
|
||||
private static readonly EntProtoId HoloBarrierProjectorProtoId = "HoloprojectorSecurity";
|
||||
private static readonly EntProtoId HoloSignProjectorProtoId = "Holoprojector";
|
||||
|
||||
/// <summary>
|
||||
/// Tests the janitors holosign projector.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task HoloSignTest()
|
||||
{
|
||||
var projector = await PlaceInHands(HoloSignProjectorProtoId);
|
||||
var projectorComp = Comp<HolosignProjectorComponent>(projector);
|
||||
var signProtoId = projectorComp.SignProto;
|
||||
|
||||
// No holosigns before using the item.
|
||||
await AssertEntityLookup((WallPrototype, 2));
|
||||
|
||||
var powerCellSystem = SEntMan.System<PowerCellSystem>();
|
||||
var initialUses = powerCellSystem.GetRemainingUses(ToServer(projector), projectorComp.ChargeUse);
|
||||
Assert.That(initialUses, Is.GreaterThan(0), "Holoprojector spawned without usable charges.");
|
||||
|
||||
// Click on the tile next to the player.
|
||||
await Interact(null, TargetCoords);
|
||||
|
||||
// We should have one charge less.
|
||||
var remainingUses = powerCellSystem.GetRemainingUses(ToServer(projector), projectorComp.ChargeUse);
|
||||
Assert.That(remainingUses, Is.EqualTo(initialUses - 1), "Holoprojector did not use the right amount of charge when used.");
|
||||
|
||||
// We should have spawned exactly one holosign.
|
||||
await AssertEntityLookup(
|
||||
(signProtoId, 1),
|
||||
(WallPrototype, 2));
|
||||
|
||||
// Try spawn more holosigns than we have charge.
|
||||
for (var i = 0; i < initialUses; i++)
|
||||
{
|
||||
await Interact(null, TargetCoords);
|
||||
}
|
||||
|
||||
// The total should be the same as the initial charges.
|
||||
await AssertEntityLookup(
|
||||
(signProtoId, initialUses),
|
||||
(WallPrototype, 2));
|
||||
|
||||
// We should have no charges left.
|
||||
remainingUses = powerCellSystem.GetRemainingUses(ToServer(projector), projectorComp.ChargeUse);
|
||||
Assert.That(remainingUses, Is.Zero, "Holoprojector did not use up all charges.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the security holo barrier projector and the barrier.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task HoloBarrierTest()
|
||||
{
|
||||
var projector = await PlaceInHands(HoloBarrierProjectorProtoId);
|
||||
var holoBarrierProtoId = Comp<HolosignProjectorComponent>(projector).SignProto;
|
||||
// No holobarriers before using the item.
|
||||
await AssertEntityLookup((WallPrototype, 2));
|
||||
|
||||
// Click on the tile next to the player.
|
||||
await Interact(null, TargetCoords);
|
||||
|
||||
// We should have spawned exactly one holobarrier.
|
||||
await AssertEntityLookup(
|
||||
(holoBarrierProtoId, 1),
|
||||
(WallPrototype, 2));
|
||||
Target = FromServer(await FindEntity(holoBarrierProtoId));
|
||||
var timeRemaining = Comp<TimedDespawnComponent>(Target).Lifetime;
|
||||
|
||||
// Check that the barrier is at the location we clicked at.
|
||||
AssertLocation(Target, TargetCoords);
|
||||
|
||||
// Try moving past the barrier.
|
||||
Assert.That(Delta(), Is.GreaterThan(0.5), "Player was not located west of the holobarrier.");
|
||||
await Move(DirectionFlag.East, 0.5f);
|
||||
Assert.That(Delta(), Is.GreaterThan(0.5), "Player was able to walk through a holobarrier.");
|
||||
|
||||
// Try to climb the barrier.
|
||||
await Interact(Target, TargetCoords, altInteract: true);
|
||||
|
||||
// We should be able to move past the barrier now.
|
||||
await Move(DirectionFlag.East, 0.5f);
|
||||
Assert.That(Delta(), Is.LessThan(-0.5), "Player was not able to climb over a holobarrier.");
|
||||
|
||||
// We should not be able to walk back without climbing again.
|
||||
await Move(DirectionFlag.West, 0.5f);
|
||||
Assert.That(Delta(), Is.LessThan(-0.5), "Player was able to walk through a holobarrier.");
|
||||
|
||||
// Wait until the barrier despawns.
|
||||
await RunSeconds(timeRemaining);
|
||||
AssertDeleted(Target);
|
||||
|
||||
// We should be able to walk back now.
|
||||
await Move(DirectionFlag.West, 0.5f);
|
||||
Assert.That(DeltaCoordinates(), Is.GreaterThan(0.5), "Player was able to walk past a deleted holobarrier.");
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Numerics;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Movement;
|
||||
|
||||
@@ -13,6 +15,7 @@ namespace Content.IntegrationTests.Tests.Movement;
|
||||
public abstract class MovementTest : InteractionTest
|
||||
{
|
||||
protected override string PlayerPrototype => "MobHuman";
|
||||
protected static readonly EntProtoId WallPrototype = "WallSolid";
|
||||
|
||||
/// <summary>
|
||||
/// Number of tiles to add either side of the player.
|
||||
@@ -47,8 +50,8 @@ public abstract class MovementTest : InteractionTest
|
||||
|
||||
if (AddWalls)
|
||||
{
|
||||
var sWallLeft = await SpawnEntity("WallSolid", pCoords.Offset(new Vector2(-Tiles, 0)));
|
||||
var sWallRight = await SpawnEntity("WallSolid", pCoords.Offset(new Vector2(Tiles, 0)));
|
||||
var sWallLeft = await SpawnEntity(WallPrototype, pCoords.Offset(new Vector2(-Tiles, 0)));
|
||||
var sWallRight = await SpawnEntity(WallPrototype, pCoords.Offset(new Vector2(Tiles, 0)));
|
||||
|
||||
WallLeft = SEntMan.GetNetEntity(sWallLeft);
|
||||
WallRight = SEntMan.GetNetEntity(sWallRight);
|
||||
@@ -59,7 +62,7 @@ public abstract class MovementTest : InteractionTest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the relative horizontal between two entities. Defaults to using the target & player entity.
|
||||
/// Get the relative horizontal between two entities. Defaults to using the target & player entity.
|
||||
/// </summary>
|
||||
protected float Delta(NetEntity? target = null, NetEntity? other = null)
|
||||
{
|
||||
@@ -73,5 +76,17 @@ public abstract class MovementTest : InteractionTest
|
||||
var delta = Transform.GetWorldPosition(SEntMan.GetEntity(target.Value)) - Transform.GetWorldPosition(SEntMan.GetEntity(other ?? Player));
|
||||
return delta.X;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the relative horizontal between a set of coordinates and an entity. Defaults to using the target coordinates and the player entity.
|
||||
/// </summary>
|
||||
protected float DeltaCoordinates(NetCoordinates? coords = null, NetEntity? other = null)
|
||||
{
|
||||
other ??= Player;
|
||||
coords ??= TargetCoords;
|
||||
|
||||
var delta = Transform.ToWorldPosition(ToServer(coords.Value)) - Transform.GetWorldPosition(ToServer(other.Value));
|
||||
return delta.X;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Holosign
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class HolosignProjectorComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("signProto", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string SignProto = "HolosignWetFloor";
|
||||
|
||||
/// <summary>
|
||||
/// How much charge a single use expends.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("chargeUse")]
|
||||
public float ChargeUse = 50f;
|
||||
}
|
||||
}
|
||||
31
Content.Shared/Holosign/HolosignProjectorComponent.cs
Normal file
31
Content.Shared/Holosign/HolosignProjectorComponent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Holosign;
|
||||
|
||||
/// <summary>
|
||||
/// Added to an item and allows it to spawn a specified prototype at the location you click on, using charge from a power cell.
|
||||
/// Used for holosigns, holofans and holobarriers.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class HolosignProjectorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The prototype to spawn on use.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntProtoId SignProto = "HolosignWetFloor";
|
||||
|
||||
/// <summary>
|
||||
/// How much charge a single use expends, in watts.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ChargeUse = 50f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to use predictive spawning.
|
||||
/// At the moment this does not support entities with animated sprites, so set this to false in that case.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool PredictedSpawn;
|
||||
}
|
||||
@@ -1,29 +1,30 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Holosign;
|
||||
namespace Content.Shared.Holosign;
|
||||
|
||||
public sealed class HolosignSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HolosignProjectorComponent, BeforeRangedInteractEvent>(OnBeforeInteract);
|
||||
SubscribeLocalEvent<HolosignProjectorComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, HolosignProjectorComponent component, ExaminedEvent args)
|
||||
private void OnExamine(Entity<HolosignProjectorComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
// TODO: This should probably be using an itemstatus
|
||||
// TODO: I'm too lazy to do this rn but it's literally copy-paste from emag.
|
||||
var charges = _powerCell.GetRemainingUses(uid, component.ChargeUse);
|
||||
var maxCharges = _powerCell.GetMaxUses(uid, component.ChargeUse);
|
||||
var charges = _powerCell.GetRemainingUses(ent.Owner, ent.Comp.ChargeUse);
|
||||
var maxCharges = _powerCell.GetMaxUses(ent.Owner, ent.Comp.ChargeUse);
|
||||
|
||||
using (args.PushGroup(nameof(HolosignProjectorComponent)))
|
||||
{
|
||||
@@ -36,23 +37,18 @@ public sealed class HolosignSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeforeInteract(EntityUid uid, HolosignProjectorComponent component, BeforeRangedInteractEvent args)
|
||||
private void OnBeforeInteract(Entity<HolosignProjectorComponent> ent, ref BeforeRangedInteractEvent args)
|
||||
{
|
||||
|
||||
if (args.Handled
|
||||
|| !args.CanReach // prevent placing out of range
|
||||
|| HasComp<StorageComponent>(args.Target) // if it's a storage component like a bag, we ignore usage so it can be stored
|
||||
|| !_powerCell.TryUseCharge(uid, component.ChargeUse, user: args.User) // if no battery or no charge, doesn't work
|
||||
|| !_powerCell.TryUseCharge(ent.Owner, ent.Comp.ChargeUse, user: args.User, predicted: true) // if no battery or no charge, doesn't work
|
||||
)
|
||||
return;
|
||||
|
||||
// places the holographic sign at the click location, snapped to grid.
|
||||
// overlapping of the same holo on one tile remains allowed to allow holofan refreshes
|
||||
var holoUid = Spawn(component.SignProto, args.ClickLocation.SnapToGrid(EntityManager));
|
||||
var xform = Transform(holoUid);
|
||||
// TODO: Just make the prototype anchored
|
||||
if (!xform.Anchored)
|
||||
_transform.AnchorEntity(holoUid, xform); // anchor to prevent any tempering with (don't know what could even interact with it)
|
||||
if (ent.Comp.PredictedSpawn || _net.IsServer)
|
||||
PredictedSpawnAtPosition(ent.Comp.SignProto, args.ClickLocation);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
state: icon
|
||||
- type: Tag
|
||||
tags:
|
||||
- HolosignProjector
|
||||
- HolosignProjector
|
||||
|
||||
- type: entity
|
||||
parent: Holoprojector
|
||||
@@ -87,7 +87,7 @@
|
||||
state: icon
|
||||
- type: Tag
|
||||
tags:
|
||||
- HolofanProjector
|
||||
- HolofanProjector
|
||||
- type: StaticPrice
|
||||
price: 50
|
||||
- type: GuideHelp
|
||||
@@ -126,17 +126,17 @@
|
||||
name: force field projector
|
||||
description: Creates an impassable forcefield that won't let anything through. Close proximity may or may not cause cancer.
|
||||
components:
|
||||
- type: HolosignProjector
|
||||
signProto: HolosignForcefield
|
||||
chargeUse: 120
|
||||
- type: Sprite
|
||||
sprite: Objects/Devices/Holoprojectors/field.rsi
|
||||
state: icon
|
||||
- type: Tag
|
||||
tags:
|
||||
- HolofanProjector
|
||||
- type: StaticPrice
|
||||
price: 130
|
||||
- type: HolosignProjector
|
||||
signProto: HolosignForcefield
|
||||
chargeUse: 120
|
||||
- type: Sprite
|
||||
sprite: Objects/Devices/Holoprojectors/field.rsi
|
||||
state: icon
|
||||
- type: Tag
|
||||
tags:
|
||||
- HolofanProjector
|
||||
- type: StaticPrice
|
||||
price: 130
|
||||
|
||||
- type: entity
|
||||
parent: HoloprojectorField
|
||||
@@ -154,18 +154,18 @@
|
||||
name: holobarrier projector
|
||||
description: Creates a solid but fragile holographic barrier.
|
||||
components:
|
||||
- type: HolosignProjector
|
||||
signProto: HolosignSecurity
|
||||
chargeUse: 90
|
||||
- type: Sprite
|
||||
sprite: Objects/Devices/Holoprojectors/security.rsi
|
||||
state: icon
|
||||
- type: Tag
|
||||
tags:
|
||||
- HolofanProjector
|
||||
- SecBeltEquip
|
||||
- type: StaticPrice
|
||||
price: 50
|
||||
- type: HolosignProjector
|
||||
signProto: HolosignSecurity
|
||||
chargeUse: 90
|
||||
- type: Sprite
|
||||
sprite: Objects/Devices/Holoprojectors/security.rsi
|
||||
state: icon
|
||||
- type: Tag
|
||||
tags:
|
||||
- HolofanProjector
|
||||
- SecBeltEquip
|
||||
- type: StaticPrice
|
||||
price: 50
|
||||
|
||||
- type: entity
|
||||
parent: HoloprojectorSecurity
|
||||
|
||||
@@ -15,16 +15,17 @@
|
||||
state: icon
|
||||
- type: TimedDespawn
|
||||
lifetime: 90
|
||||
- type: Clickable
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 30
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 30
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
|
||||
- type: entity
|
||||
id: HoloFan
|
||||
@@ -74,15 +75,14 @@
|
||||
- SlipLayer
|
||||
- type: TimedDespawn
|
||||
lifetime: 30
|
||||
- type: Clickable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 10
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 10
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
|
||||
- type: entity
|
||||
id: HolosignSecurity
|
||||
@@ -113,7 +113,6 @@
|
||||
radius: 3
|
||||
color: red
|
||||
- type: Climbable
|
||||
- type: Clickable
|
||||
|
||||
- type: entity
|
||||
id: HolosignForcefield
|
||||
@@ -143,14 +142,13 @@
|
||||
enabled: true
|
||||
radius: 3
|
||||
color: blue
|
||||
- type: Clickable
|
||||
- type: ContainmentField
|
||||
throwForce: 0
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 60
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 60
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
|
||||
Reference in New Issue
Block a user