#nullable enable
using System.Numerics;
using Content.Client.Construction;
using Content.Client.Examine;
using Content.Client.Gameplay;
using Content.IntegrationTests.Pair;
using Content.Server.Hands.Systems;
using Content.Server.Stack;
using Content.Server.Tools;
using Content.Shared.CombatMode;
using Content.Shared.DoAfter;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Mind;
using Content.Shared.Players;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Client.Input;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.UnitTesting;
namespace Content.IntegrationTests.Tests.Interaction;
///
/// This is a base class designed to make it easier to test various interactions like construction & DoAfters.
///
/// For construction tests, the interactions are intentionally hard-coded and not pulled automatically from the
/// construction graph, even though this may be a pain to maintain. This is because otherwise these tests could not
/// detect errors in the graph pathfinding (e.g., infinite loops, missing steps, etc).
///
[TestFixture]
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
public abstract partial class InteractionTest
{
protected virtual string PlayerPrototype => "InteractionTestMob";
protected TestPair Pair = default!;
protected TestMapData MapData => Pair.TestMap!;
protected RobustIntegrationTest.ServerIntegrationInstance Server => Pair.Server;
protected RobustIntegrationTest.ClientIntegrationInstance Client => Pair.Client;
protected MapId MapId => MapData.MapId;
///
/// Target coordinates. Note that this does not necessarily correspond to the position of the
/// entity.
///
protected NetCoordinates TargetCoords;
///
/// Initial player coordinates. Note that this does not necessarily correspond to the position of the
/// entity.
///
protected NetCoordinates PlayerCoords;
///
/// The player entity that performs all these interactions. Defaults to an admin-observer with 1 hand.
///
protected NetEntity Player;
protected EntityUid SPlayer;
protected EntityUid CPlayer;
protected ICommonSession ClientSession = default!;
protected ICommonSession ServerSession = default!;
///
/// The current target entity. This is the default entity for various helper functions.
///
///
/// Note that this target may be automatically modified by various interactions, in particular construction
/// interactions often swap out entities, and there are helper methods that attempt to automatically upddate
/// the target entity. See
///
protected NetEntity? Target;
protected EntityUid? STarget => ToServer(Target);
protected EntityUid? CTarget => ToClient(Target);
///
/// When attempting to start construction, this is the client-side ID of the construction ghost.
///
protected int ConstructionGhostId;
// SERVER dependencies
protected IEntityManager SEntMan = default!;
protected ITileDefinitionManager TileMan = default!;
protected IMapManager MapMan = default!;
protected IPrototypeManager ProtoMan = default!;
protected IGameTiming STiming = default!;
protected IComponentFactory Factory = default!;
protected HandsSystem HandSys = default!;
protected StackSystem Stack = default!;
protected SharedInteractionSystem InteractSys = default!;
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
protected SharedDoAfterSystem DoAfterSys = default!;
protected ToolSystem ToolSys = default!;
protected ItemToggleSystem ItemToggleSys = default!;
protected InteractionTestSystem STestSystem = default!;
protected SharedTransformSystem Transform = default!;
protected SharedMapSystem MapSystem = default!;
protected ISawmill SLogger = default!;
protected SharedUserInterfaceSystem SUiSys = default!;
protected SharedCombatModeSystem SCombatMode = default!;
protected SharedGunSystem SGun = default!;
// CLIENT dependencies
protected IEntityManager CEntMan = default!;
protected IGameTiming CTiming = default!;
protected IUserInterfaceManager UiMan = default!;
protected IInputManager InputManager = default!;
protected Robust.Client.GameObjects.InputSystem InputSystem = default!;
protected ConstructionSystem CConSys = default!;
protected ExamineSystem ExamineSys = default!;
protected InteractionTestSystem CTestSystem = default!;
protected ISawmill CLogger = default!;
protected SharedUserInterfaceSystem CUiSys = default!;
// player components
protected HandsComponent Hands = default!;
protected DoAfterComponent DoAfters = default!;
public float TickPeriod => (float)STiming.TickPeriod.TotalSeconds;
// Simple mob that has one hand and can perform misc interactions.
[TestPrototypes]
private const string TestPrototypes = @"
- type: entity
id: InteractionTestMob
components:
- type: DoAfter
- type: Hands
hands:
hand_right: # only one hand, so that they do not accidentally pick up deconstruction products
location: Right
sortedHands:
- hand_right
- type: ComplexInteraction
- type: MindContainer
- type: Stripping
- type: Puller
- type: Physics
- type: GravityAffected
- type: Tag
tags:
- CanPilot
- type: UserInterface
- type: CombatMode
";
[SetUp]
public virtual async Task Setup()
{
Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true });
// server dependencies
SEntMan = Server.ResolveDependency();
TileMan = Server.ResolveDependency();
MapMan = Server.ResolveDependency();
ProtoMan = Server.ResolveDependency();
Factory = Server.ResolveDependency();
STiming = Server.ResolveDependency();
SLogger = Server.ResolveDependency().RootSawmill;
HandSys = SEntMan.System();
InteractSys = SEntMan.System();
ToolSys = SEntMan.System();
ItemToggleSys = SEntMan.System();
DoAfterSys = SEntMan.System();
Transform = SEntMan.System();
MapSystem = SEntMan.System();
SConstruction = SEntMan.System();
STestSystem = SEntMan.System();
Stack = SEntMan.System();
SUiSys = SEntMan.System();
SCombatMode = SEntMan.System();
SGun = SEntMan.System();
// client dependencies
CEntMan = Client.ResolveDependency();
UiMan = Client.ResolveDependency();
CTiming = Client.ResolveDependency();
InputManager = Client.ResolveDependency();
CLogger = Client.ResolveDependency().RootSawmill;
InputSystem = CEntMan.System();
CTestSystem = CEntMan.System();
CConSys = CEntMan.System();
ExamineSys = CEntMan.System();
CUiSys = CEntMan.System();
// Setup map.
await Pair.CreateTestMap();
PlayerCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)), MapData.MapUid));
TargetCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(1.5f, 0.5f)), MapData.MapUid));
await SetTile(Plating, grid: MapData.Grid);
// Get player data
var sPlayerMan = Server.ResolveDependency();
var cPlayerMan = Client.ResolveDependency();
if (Client.Session == null)
Assert.Fail("No player");
ClientSession = Client.Session!;
ServerSession = sPlayerMan.GetSessionById(ClientSession.UserId);
// Spawn player entity & attach
EntityUid? old = default;
await Server.WaitPost(() =>
{
// Fuck you mind system I want an hour of my life back
// Mind system is a time vampire
SEntMan.System().WipeMind(ServerSession.ContentData()?.Mind);
old = cPlayerMan.LocalEntity;
SPlayer = SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords));
Player = SEntMan.GetNetEntity(SPlayer);
Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer);
Hands = SEntMan.GetComponent(SPlayer);
DoAfters = SEntMan.GetComponent(SPlayer);
});
// Check player got attached.
await RunTicks(5);
CPlayer = ToClient(Player);
Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(CPlayer));
// Delete old player entity.
await Server.WaitPost(() =>
{
if (old != null)
SEntMan.DeleteEntity(old.Value);
});
// Change UI state to in-game.
var state = Client.ResolveDependency();
await Client.WaitPost(() => state.RequestStateChange());
// Final player asserts/checks.
await Pair.ReallyBeIdle(5);
Assert.Multiple(() =>
{
Assert.That(CEntMan.GetNetEntity(cPlayerMan.LocalEntity), Is.EqualTo(Player));
Assert.That(sPlayerMan.GetSessionById(ClientSession.UserId).AttachedEntity, Is.EqualTo(SEntMan.GetEntity(Player)));
});
}
[TearDown]
public async Task TearDownInternal()
{
await Server.WaitPost(() => MapSystem.DeleteMap(MapId));
await Pair.CleanReturnAsync();
await TearDown();
}
protected virtual Task TearDown()
{
return Task.CompletedTask;
}
}