private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args) { args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance); }
public async Task BuckleUnbuckleCooldownRangeTest() { var cOptions = new ClientIntegrationOptions { ExtraPrototypes = Prototypes }; var sOptions = new ServerIntegrationOptions { ExtraPrototypes = Prototypes }; var(_, server) = await StartConnectedServerClientPair(cOptions, sOptions); EntityUid human = default; EntityUid chair = default; BuckleComponent buckle = null; StrapComponent strap = null; await server.WaitAssertion(() => { var mapManager = IoCManager.Resolve <IMapManager>(); var entityManager = IoCManager.Resolve <IEntityManager>(); var actionBlocker = EntitySystem.Get <ActionBlockerSystem>(); var standingState = EntitySystem.Get <StandingStateSystem>(); var grid = GetMainGrid(mapManager); var coordinates = new EntityCoordinates(grid.GridEntityId, 0, 0); human = entityManager.SpawnEntity(BuckleDummyId, coordinates); chair = entityManager.SpawnEntity(StrapDummyId, coordinates); // Default state, unbuckled Assert.True(entityManager.TryGetComponent(human, out buckle)); Assert.NotNull(buckle); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(actionBlocker.CanMove(human)); Assert.True(actionBlocker.CanChangeDirection(human)); Assert.True(standingState.Down(human)); Assert.True(standingState.Stand(human)); // Default state, no buckled entities, strap Assert.True(entityManager.TryGetComponent(chair, out strap)); Assert.NotNull(strap); Assert.IsEmpty(strap.BuckledEntities); Assert.Zero(strap.OccupiedSize); // Side effects of buckling Assert.True(buckle.TryBuckle(human, chair)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); Assert.True(((BuckleComponentState)buckle.GetComponentState()).Buckled); Assert.False(actionBlocker.CanMove(human)); Assert.False(actionBlocker.CanChangeDirection(human)); Assert.False(standingState.Down(human)); Assert.That((entityManager.GetComponent <TransformComponent>(human).WorldPosition - entityManager.GetComponent <TransformComponent>(chair).WorldPosition).Length, Is.LessThanOrEqualTo(buckle.BuckleOffset.Length)); // Side effects of buckling for the strap Assert.That(strap.BuckledEntities, Does.Contain(human)); Assert.That(strap.OccupiedSize, Is.EqualTo(buckle.Size)); Assert.Positive(strap.OccupiedSize); // Trying to buckle while already buckled fails Assert.False(buckle.TryBuckle(human, chair)); // Trying to unbuckle too quickly fails Assert.False(buckle.TryUnbuckle(human)); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); }); // Wait enough ticks for the unbuckling cooldown to run out await server.WaitRunTicks(60); await server.WaitAssertion(() => { var actionBlocker = EntitySystem.Get <ActionBlockerSystem>(); var standingState = EntitySystem.Get <StandingStateSystem>(); // Still buckled Assert.True(buckle.Buckled); // Unbuckle Assert.True(buckle.TryUnbuckle(human)); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(actionBlocker.CanMove(human)); Assert.True(actionBlocker.CanChangeDirection(human)); Assert.True(standingState.Down(human)); // Unbuckle, strap Assert.IsEmpty(strap.BuckledEntities); Assert.Zero(strap.OccupiedSize); // Re-buckling has no cooldown Assert.True(buckle.TryBuckle(human, chair)); Assert.True(buckle.Buckled); // On cooldown Assert.False(buckle.TryUnbuckle(human)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); }); // Wait enough ticks for the unbuckling cooldown to run out await server.WaitRunTicks(60); await server.WaitAssertion(() => { var entityManager = IoCManager.Resolve <IEntityManager>(); var actionBlocker = EntitySystem.Get <ActionBlockerSystem>(); var standingState = EntitySystem.Get <StandingStateSystem>(); // Still buckled Assert.True(buckle.Buckled); // Unbuckle Assert.True(buckle.TryUnbuckle(human)); Assert.False(buckle.Buckled); // Move away from the chair entityManager.GetComponent <TransformComponent>(human).WorldPosition += (1000, 1000); // Out of range Assert.False(buckle.TryBuckle(human, chair)); Assert.False(buckle.TryUnbuckle(human)); Assert.False(buckle.ToggleBuckle(human, chair)); // Move near the chair entityManager.GetComponent <TransformComponent>(human).WorldPosition = entityManager.GetComponent <TransformComponent>(chair).WorldPosition + (0.5f, 0); // In range Assert.True(buckle.TryBuckle(human, chair)); Assert.True(buckle.Buckled); Assert.False(buckle.TryUnbuckle(human)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); // Force unbuckle Assert.True(buckle.TryUnbuckle(human, true)); Assert.False(buckle.Buckled); Assert.True(actionBlocker.CanMove(human)); Assert.True(actionBlocker.CanChangeDirection(human)); Assert.True(standingState.Down(human)); // Re-buckle Assert.True(buckle.TryBuckle(human, chair)); // Move away from the chair entityManager.GetComponent <TransformComponent>(human).WorldPosition += (1, 0); }); await server.WaitRunTicks(1); await server.WaitAssertion(() => { // No longer buckled Assert.False(buckle.Buckled); Assert.Null(buckle.BuckledTo); Assert.IsEmpty(strap.BuckledEntities); }); }
public async Task Test() { var server = StartServerDummyTicker(); IEntity human = null; IEntity chair = null; BuckleComponent buckle = null; StrapComponent strap = null; server.Assert(() => { var mapManager = IoCManager.Resolve <IMapManager>(); mapManager.CreateNewMapEntity(MapId.Nullspace); var entityManager = IoCManager.Resolve <IEntityManager>(); human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace); chair = entityManager.SpawnEntity("ChairWood", MapCoordinates.Nullspace); // Default state, unbuckled Assert.True(human.TryGetComponent(out buckle)); Assert.NotNull(buckle); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(ActionBlockerSystem.CanMove(human)); Assert.True(ActionBlockerSystem.CanChangeDirection(human)); Assert.True(EffectBlockerSystem.CanFall(human)); // Default state, no buckled entities, strap Assert.True(chair.TryGetComponent(out strap)); Assert.NotNull(strap); Assert.IsEmpty(strap.BuckledEntities); Assert.Zero(strap.OccupiedSize); // Side effects of buckling Assert.True(buckle.TryBuckle(human, chair)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); Assert.True(((BuckleComponentState)buckle.GetComponentState()).Buckled); Assert.False(ActionBlockerSystem.CanMove(human)); Assert.False(ActionBlockerSystem.CanChangeDirection(human)); Assert.False(EffectBlockerSystem.CanFall(human)); Assert.That(human.Transform.WorldPosition, Is.EqualTo(chair.Transform.WorldPosition)); // Side effects of buckling for the strap Assert.That(strap.BuckledEntities, Does.Contain(human)); Assert.That(strap.OccupiedSize, Is.EqualTo(buckle.Size)); Assert.Positive(strap.OccupiedSize); // Trying to buckle while already buckled fails Assert.False(buckle.TryBuckle(human, chair)); // Trying to unbuckle too quickly fails Assert.False(buckle.TryUnbuckle(human)); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); }); // Wait enough ticks for the unbuckling cooldown to run out server.RunTicks(60); server.Assert(() => { // Still buckled Assert.True(buckle.Buckled); // Unbuckle Assert.True(buckle.TryUnbuckle(human)); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(ActionBlockerSystem.CanMove(human)); Assert.True(ActionBlockerSystem.CanChangeDirection(human)); Assert.True(EffectBlockerSystem.CanFall(human)); // Unbuckle, strap Assert.IsEmpty(strap.BuckledEntities); Assert.Zero(strap.OccupiedSize); // Re-buckling has no cooldown Assert.True(buckle.TryBuckle(human, chair)); Assert.True(buckle.Buckled); // On cooldown Assert.False(buckle.TryUnbuckle(human)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); }); // Wait enough ticks for the unbuckling cooldown to run out server.RunTicks(60); server.Assert(() => { // Still buckled Assert.True(buckle.Buckled); // Unbuckle Assert.True(buckle.TryUnbuckle(human)); Assert.False(buckle.Buckled); // Move away from the chair human.Transform.WorldPosition += (1000, 1000); // Out of range Assert.False(buckle.TryBuckle(human, chair)); Assert.False(buckle.TryUnbuckle(human)); Assert.False(buckle.ToggleBuckle(human, chair)); // Move near the chair human.Transform.WorldPosition = chair.Transform.WorldPosition + (0.5f, 0); // In range Assert.True(buckle.TryBuckle(human, chair)); Assert.True(buckle.Buckled); Assert.False(buckle.TryUnbuckle(human)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); // Force unbuckle Assert.True(buckle.TryUnbuckle(human, true)); Assert.False(buckle.Buckled); Assert.True(ActionBlockerSystem.CanMove(human)); Assert.True(ActionBlockerSystem.CanChangeDirection(human)); Assert.True(EffectBlockerSystem.CanFall(human)); // Re-buckle Assert.True(buckle.TryBuckle(human, chair)); // Move away from the chair human.Transform.WorldPosition += (1, 0); }); server.RunTicks(1); server.Assert(() => { // No longer buckled Assert.False(buckle.Buckled); Assert.Null(buckle.BuckledTo); Assert.IsEmpty(strap.BuckledEntities); }); await server.WaitIdleAsync(); }
public async Task BuckledDyingDropItemsTest() { var server = StartServer(); IEntity human = null; IEntity chair = null; BuckleComponent buckle = null; StrapComponent strap = null; HandsComponent hands = null; IDamageableComponent humanDamageable = null; server.Assert(() => { var mapManager = IoCManager.Resolve <IMapManager>(); var mapId = new MapId(1); mapManager.CreateNewMapEntity(mapId); var entityManager = IoCManager.Resolve <IEntityManager>(); var gridId = new GridId(1); var grid = mapManager.CreateGrid(mapId, gridId); var coordinates = grid.GridEntityId.ToCoordinates(); var tileManager = IoCManager.Resolve <ITileDefinitionManager>(); var tileId = tileManager["underplating"].TileId; var tile = new Tile(tileId); grid.SetTile(coordinates, tile); human = entityManager.SpawnEntity("HumanMob_Content", coordinates); chair = entityManager.SpawnEntity("ChairWood", coordinates); // Component sanity check Assert.True(human.TryGetComponent(out buckle)); Assert.True(chair.TryGetComponent(out strap)); Assert.True(human.TryGetComponent(out hands)); Assert.True(human.TryGetComponent(out humanDamageable)); // Buckle Assert.True(buckle.TryBuckle(human, chair)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); // Put an item into every hand for (var i = 0; i < hands.Count; i++) { var akms = entityManager.SpawnEntity("RifleAk", coordinates); // Equip items Assert.True(akms.TryGetComponent(out ItemComponent item)); Assert.True(hands.PutInHand(item)); } }); server.RunTicks(10); server.Assert(() => { // Still buckled Assert.True(buckle.Buckled); // With items in all hands foreach (var slot in hands.Hands) { Assert.NotNull(hands.GetItem(slot)); } // Banish our guy into the shadow realm humanDamageable.ChangeDamage(DamageClass.Brute, 1000000, true); }); server.RunTicks(10); server.Assert(() => { // Still buckled Assert.True(buckle.Buckled); // Now with no item in any hand foreach (var slot in hands.Hands) { Assert.Null(hands.GetItem(slot)); } }); await server.WaitIdleAsync(); }
public async Task BuckleUnbuckleCooldownRangeTest() { var options = new ServerIntegrationOptions { ExtraPrototypes = Prototypes }; var server = StartServer(options); IEntity human = null; IEntity chair = null; BuckleComponent buckle = null; StrapComponent strap = null; await server.WaitAssertion(() => { var mapManager = IoCManager.Resolve <IMapManager>(); var entityManager = IoCManager.Resolve <IEntityManager>(); var gridId = new GridId(1); var grid = mapManager.GetGrid(gridId); var coordinates = grid.GridEntityId.ToCoordinates(); human = entityManager.SpawnEntity(BuckleDummyId, coordinates); chair = entityManager.SpawnEntity(StrapDummyId, coordinates); // Default state, unbuckled Assert.True(human.TryGetComponent(out buckle)); Assert.NotNull(buckle); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(ActionBlockerSystem.CanMove(human)); Assert.True(ActionBlockerSystem.CanChangeDirection(human)); Assert.True(EffectBlockerSystem.CanFall(human)); // Default state, no buckled entities, strap Assert.True(chair.TryGetComponent(out strap)); Assert.NotNull(strap); Assert.IsEmpty(strap.BuckledEntities); Assert.Zero(strap.OccupiedSize); // Side effects of buckling Assert.True(buckle.TryBuckle(human, chair)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); Assert.True(((BuckleComponentState)buckle.GetComponentState()).Buckled); Assert.False(ActionBlockerSystem.CanMove(human)); Assert.False(ActionBlockerSystem.CanChangeDirection(human)); Assert.False(EffectBlockerSystem.CanFall(human)); Assert.That(human.Transform.WorldPosition, Is.EqualTo(chair.Transform.WorldPosition)); // Side effects of buckling for the strap Assert.That(strap.BuckledEntities, Does.Contain(human)); Assert.That(strap.OccupiedSize, Is.EqualTo(buckle.Size)); Assert.Positive(strap.OccupiedSize); // Trying to buckle while already buckled fails Assert.False(buckle.TryBuckle(human, chair)); // Trying to unbuckle too quickly fails Assert.False(buckle.TryUnbuckle(human)); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); }); // Wait enough ticks for the unbuckling cooldown to run out await server.WaitRunTicks(60); await server.WaitAssertion(() => { // Still buckled Assert.True(buckle.Buckled); // Unbuckle Assert.True(buckle.TryUnbuckle(human)); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(ActionBlockerSystem.CanMove(human)); Assert.True(ActionBlockerSystem.CanChangeDirection(human)); Assert.True(EffectBlockerSystem.CanFall(human)); // Unbuckle, strap Assert.IsEmpty(strap.BuckledEntities); Assert.Zero(strap.OccupiedSize); // Re-buckling has no cooldown Assert.True(buckle.TryBuckle(human, chair)); Assert.True(buckle.Buckled); // On cooldown Assert.False(buckle.TryUnbuckle(human)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); }); // Wait enough ticks for the unbuckling cooldown to run out await server.WaitRunTicks(60); await server.WaitAssertion(() => { // Still buckled Assert.True(buckle.Buckled); // Unbuckle Assert.True(buckle.TryUnbuckle(human)); Assert.False(buckle.Buckled); // Move away from the chair human.Transform.WorldPosition += (1000, 1000); // Out of range Assert.False(buckle.TryBuckle(human, chair)); Assert.False(buckle.TryUnbuckle(human)); Assert.False(buckle.ToggleBuckle(human, chair)); // Move near the chair human.Transform.WorldPosition = chair.Transform.WorldPosition + (0.5f, 0); // In range Assert.True(buckle.TryBuckle(human, chair)); Assert.True(buckle.Buckled); Assert.False(buckle.TryUnbuckle(human)); Assert.True(buckle.Buckled); Assert.False(buckle.ToggleBuckle(human, chair)); Assert.True(buckle.Buckled); // Force unbuckle Assert.True(buckle.TryUnbuckle(human, true)); Assert.False(buckle.Buckled); Assert.True(ActionBlockerSystem.CanMove(human)); Assert.True(ActionBlockerSystem.CanChangeDirection(human)); Assert.True(EffectBlockerSystem.CanFall(human)); // Re-buckle Assert.True(buckle.TryBuckle(human, chair)); // Move away from the chair human.Transform.WorldPosition += (1, 0); }); await server.WaitRunTicks(1); await server.WaitAssertion(() => { // No longer buckled Assert.False(buckle.Buckled); Assert.Null(buckle.BuckledTo); Assert.IsEmpty(strap.BuckledEntities); }); }
// TODO ECS BUCKLE/STRAP These 'Strap' verbs are an incestuous mess of buckle component and strap component // functions. Whenever these are fully ECSed, maybe do it in a way that allows for these verbs to be handled in // a sensible manner in a single system? private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetInteractionVerbsEvent args) { if (args.Hands == null || !args.CanAccess || !args.CanInteract) { return; } // Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this // range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb. // Add unstrap verbs for every strapped entity. foreach (var entity in component.BuckledEntities) { var buckledComp = entity.GetComponent <BuckleComponent>(); if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range)) { continue; } Verb verb = new(); verb.Act = () => buckledComp.TryUnbuckle(args.User); verb.Category = VerbCategory.Unbuckle; if (entity == args.User) { verb.Text = Loc.GetString("verb-self-target-pronoun"); } else { verb.Text = entity.Name; } // In the event that you have more than once entity with the same name strapped to the same object, // these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to // the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by // appending an integer to verb.Text to distinguish the verbs. args.Verbs.Add(verb); } // Add a verb to buckle the user. if (args.User.TryGetComponent <BuckleComponent>(out var buckle) && buckle.BuckledTo != component && args.User != component.Owner && component.HasSpace(buckle) && _interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range)) { Verb verb = new(); verb.Act = () => buckle.TryBuckle(args.User, args.Target); verb.Category = VerbCategory.Buckle; verb.Text = Loc.GetString("verb-self-target-pronoun"); args.Verbs.Add(verb); } // If the user is currently holding/pulling an entity that can be buckled, add a verb for that. if (args.Using != null && args.Using.TryGetComponent <BuckleComponent>(out var usingBuckle) && component.HasSpace(usingBuckle) && _interactionSystem.InRangeUnobstructed(args.Using, args.Target, range: usingBuckle.Range)) { // Check that the entity is unobstructed from the target (ignoring the user). bool Ignored(IEntity entity) => entity == args.User || entity == args.Target || entity == args.Using; if (!_interactionSystem.InRangeUnobstructed(args.Using, args.Target, usingBuckle.Range, predicate: Ignored)) { return; } Verb verb = new(); verb.Act = () => usingBuckle.TryBuckle(args.User, args.Target); verb.Category = VerbCategory.Buckle; verb.Text = args.Using.Name; // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is // just a held object, the user is probably just trying to sit down. verb.Priority = args.Using.HasComponent <ActorComponent>() ? 1 : -1; args.Verbs.Add(verb); } }
private bool TryBuckle(IEntity user, IEntity to) { if (user == null || user == to) { return(false); } if (!ActionBlockerSystem.CanInteract(user)) { _notifyManager.PopupMessage(user, user, Loc.GetString("You can't do that!")); return(false); } var strapPosition = Owner.Transform.MapPosition; var range = SharedInteractionSystem.InteractionRange / 2; if (!InteractionChecks.InRangeUnobstructed(user, strapPosition, range)) { _notifyManager.PopupMessage(user, user, Loc.GetString("You can't reach there!")); return(false); } if (!user.TryGetComponent(out HandsComponent hands)) { _notifyManager.PopupMessage(user, user, Loc.GetString("You don't have hands!")); return(false); } if (hands.GetActiveHand != null) { _notifyManager.PopupMessage(user, user, Loc.GetString("Your hand isn't free!")); return(false); } if (BuckledTo != null) { _notifyManager.PopupMessage(Owner, user, Loc.GetString(Owner == user ? "You are already buckled in!" : "{0:They} are already buckled in!", Owner)); return(false); } if (!to.TryGetComponent(out StrapComponent strap)) { _notifyManager.PopupMessage(Owner, user, Loc.GetString(Owner == user ? "You can't buckle yourself there!" : "You can't buckle {0:them} there!", Owner)); return(false); } var parent = to.Transform.Parent; while (parent != null) { if (parent == user.Transform) { _notifyManager.PopupMessage(Owner, user, Loc.GetString(Owner == user ? "You can't buckle yourself there!" : "You can't buckle {0:them} there!", Owner)); return(false); } parent = parent.Parent; } if (!strap.HasSpace(this)) { _notifyManager.PopupMessage(Owner, user, Loc.GetString(Owner == user ? "You can't fit there!" : "{0:They} can't fit there!", Owner)); return(false); } _entitySystem.GetEntitySystem <AudioSystem>() .PlayFromEntity(strap.BuckleSound, Owner); if (!strap.TryAdd(this)) { _notifyManager.PopupMessage(Owner, user, Loc.GetString(Owner == user ? "You can't buckle yourself there!" : "You can't buckle {0:them} there!", Owner)); return(false); } BuckledTo = strap; if (Owner.TryGetComponent(out AppearanceComponent appearance)) { appearance.SetData(BuckleVisuals.Buckled, true); } var ownTransform = Owner.Transform; var strapTransform = strap.Owner.Transform; ownTransform.GridPosition = strapTransform.GridPosition; ownTransform.AttachParent(strapTransform); switch (strap.Position) { case StrapPosition.None: ownTransform.WorldRotation = strapTransform.WorldRotation; break; case StrapPosition.Stand: StandingStateHelper.Standing(Owner); ownTransform.WorldRotation = strapTransform.WorldRotation; break; case StrapPosition.Down: StandingStateHelper.Down(Owner); ownTransform.WorldRotation = Angle.South; break; } BuckleStatus(); return(true); }