+++ /dev/null
-using System.Collections.Generic;
-using Robust.Shared.GameObjects;
-
-namespace Content.IntegrationTests.Tests.Helpers;
-
-/// <summary>
-/// Component that is used by <see cref="TestListenerSystem{TEvent}"/> to store any information about received events.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TestListenerComponent : Component
-{
- public Dictionary<Type, List<object>> Events = new();
-}
+++ /dev/null
-#nullable enable
-using System.Collections.Generic;
-using System.Linq;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Utility;
-
-namespace Content.IntegrationTests.Tests.Helpers;
-
-/// <summary>
-/// Generic system that listens for and records any received events of a given type.
-/// </summary>
-public abstract class TestListenerSystem<TEvent> : EntitySystem where TEvent : notnull
-{
- public override void Initialize()
- {
- // TODO
- // supporting broadcast events requires cleanup on test finish, which will probably require changes to the
- // test pair/pool manager and would conflict with #36797
- SubscribeLocalEvent<TestListenerComponent, TEvent>(OnDirectedEvent);
- }
-
- protected virtual void OnDirectedEvent(Entity<TestListenerComponent> ent, ref TEvent args)
- {
- ent.Comp.Events.GetOrNew(args.GetType()).Add(args);
- }
-
- public int Count(EntityUid uid, Func<TEvent, bool>? predicate = null)
- {
- return GetEvents(uid, predicate).Count();
- }
-
- public void Clear(EntityUid uid)
- {
- CompOrNull<TestListenerComponent>(uid)?.Events.Remove(typeof(TEvent));
- }
-
- public IEnumerable<TEvent> GetEvents(EntityUid uid, Func<TEvent, bool>? predicate = null)
- {
- var events = CompOrNull<TestListenerComponent>(uid)?.Events.GetValueOrDefault(typeof(TEvent));
- if (events == null)
- return [];
-
- return events.Cast<TEvent>().Where(e => predicate?.Invoke(e) ?? true);
- }
-}
using System.Numerics;
using System.Reflection;
using Content.Client.Construction;
-using Content.IntegrationTests.Tests.Helpers;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction.Components;
using Content.Server.Gravity;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
-using Robust.Shared.Reflection;
-using Robust.UnitTesting;
using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent;
namespace Content.IntegrationTests.Tests.Interaction;
// This partial class defines various methods that are useful for performing & validating interactions
public abstract partial class InteractionTest
{
- private Dictionary<Type, EntitySystem> _listenerCache = new();
-
/// <summary>
/// Begin constructing an entity.
/// </summary>
#endregion
- #region EventListener
-
- /// <summary>
- /// Asserts that running the given action causes an event to be fired directed at the specified entity (defaults to <see cref="Target"/>).
- /// </summary>
- /// <remarks>
- /// This currently only checks server-side events.
- /// </remarks>
- /// <param name="uid">The entity at which the events are supposed to be directed</param>
- /// <param name="count">How many new events are expected</param>
- /// <param name="clear">Whether to clear all previously recorded events before invoking the delegate</param>
- protected async Task AssertFiresEvent<TEvent>(Func<Task> act, EntityUid? uid = null, int count = 1, bool clear = true)
- where TEvent : notnull
- {
- var sys = GetListenerSystem<TEvent>();
-
- uid ??= STarget;
- if (uid == null)
- {
- Assert.Fail("No target specified");
- return;
- }
-
- if (clear)
- sys.Clear(uid.Value);
- else
- count += sys.Count(uid.Value);
-
- await Server.WaitPost(() => SEntMan.EnsureComponent<TestListenerComponent>(uid.Value));
- await act();
- AssertEvent<TEvent>(uid, count: count);
- }
-
- /// <summary>
- /// This is a variant of <see cref="AssertFiresEvent{TEvent}"/> that passes the delegate to <see cref="RobustIntegrationTest.ServerIntegrationInstance.WaitPost"/>.
- /// </summary>
- /// <remarks>
- /// This currently only checks for server-side events.
- /// </remarks>
- /// <param name="uid">The entity at which the events are supposed to be directed</param>
- /// <param name="count">How many new events are expected</param>
- /// <param name="clear">Whether to clear all previously recorded events before invoking the delegate</param>
- protected async Task AssertPostFiresEvent<TEvent>(Action act, EntityUid? uid = null, int count = 1, bool clear = true)
- where TEvent : notnull
- {
- await AssertFiresEvent<TEvent>(async () => await Server.WaitPost(act), uid, count, clear);
- }
-
- /// <summary>
- /// This is a variant of <see cref="AssertFiresEvent{TEvent}"/> that passes the delegate to <see cref="RobustIntegrationTest.ServerIntegrationInstance.WaitAssertion"/>.
- /// </summary>
- /// <remarks>
- /// This currently only checks for server-side events.
- /// </remarks>
- /// <param name="uid">The entity at which the events are supposed to be directed</param>
- /// <param name="count">How many new events are expected</param>
- /// <param name="clear">Whether to clear all previously recorded events before invoking the delegate</param>
- protected async Task AssertAssertionFiresEvent<TEvent>(Action act,
- EntityUid? uid = null,
- int count = 1,
- bool clear = true)
- where TEvent : notnull
- {
- await AssertFiresEvent<TEvent>(async () => await Server.WaitAssertion(act), uid, count, clear);
- }
-
- /// <summary>
- /// Asserts that the specified event has been fired some number of times at the given entity (defaults to <see cref="Target"/>).
- /// For this to work, this requires that the entity has been given a <see cref="TestListenerComponent"/>
- /// </summary>
- /// <remarks>
- /// This currently only checks server-side events.
- /// </remarks>
- /// <param name="uid">The entity at which the events were directed</param>
- /// <param name="count">How many new events are expected</param>
- /// <param name="predicate">A predicate that can be used to filter the recorded events</param>
- protected void AssertEvent<TEvent>(EntityUid? uid = null, int count = 1, Func<TEvent,bool>? predicate = null)
- where TEvent : notnull
- {
- Assert.That(GetEvents(uid, predicate).Count, Is.EqualTo(count));
- }
-
- /// <summary>
- /// Gets all the events of the specified type that have been fired at the given entity (defaults to <see cref="Target"/>).
- /// For this to work, this requires that the entity has been given a <see cref="TestListenerComponent"/>
- /// </summary>
- /// <remarks>
- /// This currently only gets for server-side events.
- /// </remarks>
- /// <param name="uid">The entity at which the events were directed</param>
- /// <param name="predicate">A predicate that can be used to filter the returned events</param>
- protected IEnumerable<TEvent> GetEvents<TEvent>(EntityUid? uid = null, Func<TEvent, bool>? predicate = null)
- where TEvent : notnull
- {
- uid ??= STarget;
- if (uid == null)
- {
- Assert.Fail("No target specified");
- return [];
- }
-
- Assert.That(SEntMan.HasComponent<TestListenerComponent>(uid), $"Entity must have {nameof(TestListenerComponent)}");
- return GetListenerSystem<TEvent>().GetEvents(uid.Value, predicate);
- }
-
- protected TestListenerSystem<TEvent> GetListenerSystem<TEvent>()
- where TEvent : notnull
- {
- if (_listenerCache.TryGetValue(typeof(TEvent), out var listener))
- return (TestListenerSystem<TEvent>) listener;
-
- var type = Server.Resolve<IReflectionManager>().GetAllChildren<TestListenerSystem<TEvent>>().Single();
- if (!SEntMan.EntitySysManager.TryGetEntitySystem(type, out var systemObj))
- {
- // There has to be a listener system that is manually defined. Event subscriptions are locked once
- // finalized, so we can't really easily create new subscriptions on the fly.
- // TODO find a better solution
- throw new InvalidOperationException($"Event {typeof(TEvent).Name} has no associated listener system!");
- }
-
- var system = (TestListenerSystem<TEvent>)systemObj;
- _listenerCache[typeof(TEvent)] = system;
- return system;
- }
-
- /// <summary>
- /// Clears all recorded events of the given type.
- /// </summary>
- protected void ClearEvents<TEvent>(EntityUid uid) where TEvent : notnull
- => GetListenerSystem<TEvent>().Clear(uid);
-
- #endregion
-
#region Entity lookups
/// <summary>
#nullable enable
-using Content.IntegrationTests.Tests.Helpers;
+using System.Collections.Generic;
+using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Movement.Components;
using Content.Shared.Slippery;
using Content.Shared.Stunnable;
+using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Maths;
public sealed class SlippingTest : MovementTest
{
- public sealed class SlipTestSystem : TestListenerSystem<SlipEvent>;
+ public sealed class SlipTestSystem : EntitySystem
+ {
+ public HashSet<EntityUid> Slipped = new();
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<SlipperyComponent, SlipEvent>(OnSlip);
+ }
+
+ private void OnSlip(EntityUid uid, SlipperyComponent component, ref SlipEvent args)
+ {
+ Slipped.Add(args.Slipped);
+ }
+ }
[Test]
public async Task BananaSlipTest()
{
+ var sys = SEntMan.System<SlipTestSystem>();
await SpawnTarget("TrashBananaPeel");
var modifier = Comp<MovementSpeedModifierComponent>(Player).SprintSpeedModifier;
Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed.");
- // Player is to the left of the banana peel.
+ // Player is to the left of the banana peel and has not slipped.
Assert.That(Delta(), Is.GreaterThan(0.5f));
+ Assert.That(sys.Slipped, Does.Not.Contain(SEntMan.GetEntity(Player)));
// Walking over the banana slowly does not trigger a slip.
await SetKey(EngineKeyFunctions.Walk, BoundKeyState.Down);
- await AssertFiresEvent<SlipEvent>(async () => await Move(DirectionFlag.East, 1f), count: 0);
-
+ await Move(DirectionFlag.East, 1f);
Assert.That(Delta(), Is.LessThan(0.5f));
+ Assert.That(sys.Slipped, Does.Not.Contain(SEntMan.GetEntity(Player)));
AssertComp<KnockedDownComponent>(false, Player);
// Moving at normal speeds does trigger a slip.
await SetKey(EngineKeyFunctions.Walk, BoundKeyState.Up);
- await AssertFiresEvent<SlipEvent>(async () => await Move(DirectionFlag.West, 1f));
-
- // And the person that slipped was the player
- AssertEvent<SlipEvent>(predicate: @event => @event.Slipped == SPlayer);
+ await Move(DirectionFlag.West, 1f);
+ Assert.That(sys.Slipped, Does.Contain(SEntMan.GetEntity(Player)));
AssertComp<KnockedDownComponent>(true, Player);
}
}
-using Content.Shared.Clothing.Components;
-using Content.Shared.Damage;
+using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Inventory;
using Content.Shared.Silicons.Borgs;
/// <param name="args">The event, contains the running count of armor percentage as a coefficient</param>
private void OnCoefficientQuery(Entity<ArmorComponent> ent, ref InventoryRelayedEvent<CoefficientQueryEvent> args)
{
- if (TryComp<MaskComponent>(ent, out var mask) && mask.IsToggled)
- return;
-
foreach (var armorCoefficient in ent.Comp.Modifiers.Coefficients)
{
args.Args.DamageModifiers.Coefficients[armorCoefficient.Key] = args.Args.DamageModifiers.Coefficients.TryGetValue(armorCoefficient.Key, out var coefficient) ? coefficient * armorCoefficient.Value : armorCoefficient.Value;
private void OnDamageModify(EntityUid uid, ArmorComponent component, InventoryRelayedEvent<DamageModifyEvent> args)
{
- if (TryComp<MaskComponent>(uid, out var mask) && mask.IsToggled)
- return;
-
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
}
private void OnBorgDamageModify(EntityUid uid, ArmorComponent component,
ref BorgModuleRelayedEvent<DamageModifyEvent> args)
{
- if (TryComp<MaskComponent>(uid, out var mask) && mask.IsToggled)
- return;
-
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
}
SubscribeLocalEvent<SleepingComponent, EntityZombifiedEvent>(OnZombified);
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<SleepingComponent, ComponentInit>(OnCompInit);
- SubscribeLocalEvent<SleepingComponent, ComponentRemove>(OnComponentRemoved);
- SubscribeLocalEvent<SleepingComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SleepingComponent, StunEndAttemptEvent>(OnStunEndAttempt);
SubscribeLocalEvent<SleepingComponent, StandUpAttemptEvent>(OnStandUpAttempt);
+ SubscribeLocalEvent<SleepingComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
TrySleeping((ent, ent.Comp));
}
- private void OnRejuvenate(Entity<SleepingComponent> ent, ref RejuvenateEvent args)
- {
- // WAKE UP!!!
- RemComp<SleepingComponent>(ent);
- }
-
/// <summary>
/// when sleeping component is added or removed, we do some stuff with other components.
/// </summary>
_actionsSystem.AddAction(ent, ref ent.Comp.WakeAction, WakeActionId, ent);
}
- private void OnComponentRemoved(Entity<SleepingComponent> ent, ref ComponentRemove args)
- {
- _actionsSystem.RemoveAction(ent.Owner, ent.Comp.WakeAction);
-
- var ev = new SleepStateChangedEvent(false);
- RaiseLocalEvent(ent, ref ev);
-
- _blindableSystem.UpdateIsBlind(ent.Owner);
- }
-
private void OnSpeakAttempt(Entity<SleepingComponent> ent, ref SpeakAttemptEvent args)
{
// TODO reduce duplication of this behavior with MobStateSystem somehow
args.Cancelled = true;
}
+ private void OnRejuvenate(Entity<SleepingComponent> ent, ref RejuvenateEvent args)
+ {
+ TryWaking((ent.Owner, ent.Comp), true);
+ }
+
private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
{
if (args.IsInDetailsRange)
TrySleeping(args.Target);
}
+ private void Wake(Entity<SleepingComponent> ent)
+ {
+ RemComp<SleepingComponent>(ent);
+ _actionsSystem.RemoveAction(ent.Owner, ent.Comp.WakeAction);
+
+ var ev = new SleepStateChangedEvent(false);
+ RaiseLocalEvent(ent, ref ev);
+
+ _blindableSystem.UpdateIsBlind(ent.Owner);
+ }
+
/// <summary>
/// Try sleeping. Only mobs can sleep.
/// </summary>
_popupSystem.PopupClient(Loc.GetString("wake-other-success", ("target", Identity.Entity(ent, EntityManager))), ent, user);
}
- return RemComp<SleepingComponent>(ent);
+ Wake((ent, ent.Comp));
+ return true;
}
/// <summary>
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Tools.Components;
using Content.Shared.Item.ItemToggle.Components;
-using Content.Shared.Clothing.Components;
namespace Content.Shared.Eye.Blinding.Systems
{
private void OnGetProtection(EntityUid uid, EyeProtectionComponent component, GetEyeProtectionEvent args)
{
- if (TryComp<MaskComponent>(uid, out var mask) && mask.IsToggled)
- return;
-
args.Protection += component.ProtectionTime;
}
using System.Linq;
using Content.Shared.Movement.Systems;
using Content.Shared.Random.Helpers;
-using Content.Shared.Clothing.Components;
namespace Content.Shared.Flash;
private void OnFlashImmunityFlashAttempt(Entity<FlashImmunityComponent> ent, ref FlashAttemptEvent args)
{
- if (TryComp<MaskComponent>(ent, out var mask) && mask.IsToggled)
- return;
-
if (ent.Comp.Enabled)
args.Cancelled = true;
}
wrapper.Owner != target &&
// Wrapper should never be empty, but may as well make sure.
!_charges.IsEmpty(wrapper.Owner) &&
- _whitelist.CheckBoth(target, wrapper.Comp.Blacklist, wrapper.Comp.Whitelist);
+ _whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) &&
+ _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target);
}
}
- type: LimitedCharges
maxCharges: 30
-- type: entity
- parent: ParcelWrap
- id: ParcelWrapAdmeme
- name: bluespace wrap
- suffix: Admeme
- description: Paper used contain items for transport. This one seems to be able to store an unusual amount of space within it.
- components:
- - type: ParcelWrap
- whitelist: null
- blacklist: null
-
- type: entity
parent: BaseItem
id: WrappedParcel
- type: EmagLatheRecipes
emagStaticPacks:
- SecurityAmmoStatic
- - SyndicateAmmoStatic
- type: BlueprintReceiver
whitelist:
tags:
- SecurityAmmo
- SecurityWeapons
- SecurityDisablers
- - type: EmagLatheRecipes
- emagStaticPacks:
- - SyndicateAmmoStatic
- type: MaterialStorage
whitelist:
tags:
runningState: icon
staticPacks:
- SecurityAmmoStatic
- - type: EmagLatheRecipes
- emagStaticPacks:
- - SyndicateAmmoStatic
- type: MaterialStorage
whitelist:
tags:
meltingPoint: 128.0
metabolisms:
Narcotic:
- effects: # It would be nice to have speech slurred or mumbly, but accents are a bit iffy atm. Same for distortion effects.
- - !type:MovespeedModifier
- walkSpeedModifier: 0.65
- sprintSpeedModifier: 0.65
+ effects:
- !type:ModifyStatusEffect
conditions:
- !type:ReagentThreshold
reagent: Nocturine
min: 8
effectProto: StatusEffectForcedSleeping
- time: 6
- delay: 5
- type: Update
+ time: 3
+ delay: 6
+ type: Add
- type: reagent
id: MuteToxin
- MagazinePistolSubMachineGunTopMountedEmpty
- MagazineRifle
- MagazineRifleEmpty
+ - MagazineShotgun
+ - MagazineShotgunEmpty
+ - MagazineShotgunSlug
- SpeedLoaderMagnum
- SpeedLoaderMagnumEmpty
+++ /dev/null
-## Static recipes
-
-# Added to emagged autolathe
-- type: latheRecipePack
- id: SyndicateAmmoStatic
- recipes:
- - MagazineShotgun
- - MagazineShotgunEmpty
- - MagazineShotgunSlug
[color=#a4885c]Tracking Implants:[/color] Trackers can be applied to any suspect that has been convicted of a violent crime (the red linked crimes).
- [color=#a4885c]Mind Shields:[/color] Mind Shields can be administered to any suspect charged with a crime if there is reasonable and articulable suspicion to believe they committed the crime while under mind control. If there is undeniable proof of ongoing mind control on the station, such as a successful deconversion, all the following statements apply:
-
- - Crew can be required to be shielded and may be forcibly implanted if they refuse.
- - If a suspect refuses to cooperate or the implant fails to function they can be charged with Refusal of Mental Shielding.
- - Unlike standard implantation you may indefinitely extend the duration of a prisoner's sentence until you finish issuing Mind Shields, so long as active attempts are made to procure implants and complete the implantation procedure.
+ [color=#a4885c]Mind Shields:[/color] Shields can be administered to any inmate who has been clearly mind controlled, lost control of themselves, or a suspect charged with unlawful control. Unlike standard implantation you may hold a prisoner until you finish issuing Mind Shields, so long as it's done in a timely fashion. If a suspect refuses to cooperate or the implant fails to function they can be charged with Refusal of Mental Shielding.
Usage of contraband implanters is a detainable offense, but it's not reasonable to detain someone solely for having an unidentified implant inside them.