diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 3bfacb5bc6..8efe0b2367 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -334,7 +334,12 @@ namespace Content.Client.Actions private void OnEntityTargetAttempt(Entity ent, ref ActionTargetAttemptEvent args) { - if (args.Handled || args.Input.EntityUid is not { Valid: true } entity) + if (args.Handled) + return; + + args.Handled = true; + + if (args.Input.EntityUid is not { Valid: true } entity) return; // let world target component handle it @@ -345,8 +350,6 @@ namespace Content.Client.Actions return; } - args.Handled = true; - var action = args.Action; var user = args.User; diff --git a/Content.IntegrationTests/Tests/RoundEndTest.cs b/Content.IntegrationTests/Tests/RoundEndTest.cs index 6978085640..5de6de381d 100644 --- a/Content.IntegrationTests/Tests/RoundEndTest.cs +++ b/Content.IntegrationTests/Tests/RoundEndTest.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.GameTicking; using Content.Server.RoundEnd; using Content.Shared.CCVar; @@ -22,7 +21,7 @@ namespace Content.IntegrationTests.Tests private void OnRoundEnd(RoundEndSystemChangedEvent ev) { - Interlocked.Increment(ref RoundCount); + RoundCount += 1; } } @@ -127,13 +126,17 @@ namespace Content.IntegrationTests.Tests async Task WaitForEvent() { - var timeout = Task.Delay(TimeSpan.FromSeconds(10)); - var currentCount = Thread.VolatileRead(ref sys.RoundCount); - while (currentCount == Thread.VolatileRead(ref sys.RoundCount) && !timeout.IsCompleted) + const int maxTicks = 60; + var currentCount = sys.RoundCount; + for (var i = 0; i < maxTicks; i++) { - await pair.RunTicksSync(5); + if (currentCount != sys.RoundCount) + return; + + await pair.RunTicksSync(1); } - if (timeout.IsCompleted) throw new TimeoutException("Event took too long to trigger"); + + throw new TimeoutException("Event took too long to trigger"); } // Need to clean self up diff --git a/Content.Shared/Actions/Components/TargetActionComponent.cs b/Content.Shared/Actions/Components/TargetActionComponent.cs index 0cb9de4946..8d3e764032 100644 --- a/Content.Shared/Actions/Components/TargetActionComponent.cs +++ b/Content.Shared/Actions/Components/TargetActionComponent.cs @@ -1,6 +1,6 @@ -using Content.Shared.Actions; -using Content.Shared.Interaction; -using Robust.Shared.GameStates; +using Content.Shared.Interaction; +using Content.Shared.Physics; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; namespace Content.Shared.Actions.Components; @@ -37,6 +37,16 @@ public sealed partial class TargetActionComponent : Component [DataField] public bool CheckCanAccess = true; + /// + /// The collision group to use to check for accessibility if is true. + /// + [DataField] + public CollisionGroup AccessMask = SharedInteractionSystem.InRangeUnobstructedMask; + + /// + /// The allowed range for a target to be. If zero or negative, the range check is skipped, + /// unless is true. + /// [DataField] public float Range = SharedInteractionSystem.InteractionRange; diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 333d87157d..69b15235c4 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -417,13 +417,18 @@ public abstract class SharedActionsSystem : EntitySystem return comp.CanTargetSelf; var targetAction = Comp(uid); + // not using the ValidateBaseTarget logic since its raycast fails if the target is e.g. a wall if (targetAction.CheckCanAccess) - return _interaction.InRangeAndAccessible(user, target, range: targetAction.Range); + return _interaction.InRangeAndAccessible(user, target, targetAction.Range, targetAction.AccessMask); - // if not just checking pure range, let stored entities be targeted by actions - // if it's out of range it probably isn't stored anyway... - return _interaction.CanAccessViaStorage(user, target); + // Just check normal in range, allowing <= 0 range to mean infinite range. + if (targetAction.Range > 0 + && !_transform.InRange(user, target, targetAction.Range)) + return false; + + // If checkCanAccess isn't set, we allow targeting things in containers + return _interaction.IsAccessible(user, target); } public bool ValidateWorldTarget(EntityUid user, EntityCoordinates target, Entity ent) diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index b91f56a836..68e9d8a671 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -86,7 +86,11 @@ namespace Content.Shared.Interaction private EntityQuery _delayQuery; private EntityQuery _uiQuery; - private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; + /// + /// The collision mask used by default for + /// + /// + public const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; public const float InteractionRange = 1.5f; public const float InteractionRangeSquared = InteractionRange * InteractionRange; diff --git a/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs b/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs index 9547b6ce4e..6bfb3fdfd6 100644 --- a/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs +++ b/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs @@ -1,29 +1,49 @@ +using Content.Shared.CCVar; using Content.Shared.StatusIcon; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.SSDIndicator; /// -/// Shows status icon when player in SSD +/// Shows status icon when an entity is SSD, based on if a player is attached or not. /// [RegisterComponent, NetworkedComponent] [AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class SSDIndicatorComponent : Component { - [DataField, ViewVariables(VVAccess.ReadOnly)] + /// + /// Whether or not the entity is SSD. + /// [AutoNetworkedField] + [DataField, ViewVariables(VVAccess.ReadOnly)] public bool IsSSD = true; + /// + /// The icon displayed next to the associated entity when it is SSD. + /// [DataField] public ProtoId Icon = "SSDIcon"; /// - /// When the entity should fall asleep + /// The time at which the entity will fall asleep, if is true. /// - [DataField] [AutoNetworkedField, AutoPausedField] [Access(typeof(SSDIndicatorSystem))] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan FallAsleepTime = TimeSpan.Zero; + + /// + /// The next time this component will be updated. + /// + [AutoNetworkedField, AutoPausedField] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate = TimeSpan.Zero; + + /// + /// The time between updates checking if the entity should be force slept. + /// + [DataField] + public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1); } diff --git a/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs b/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs index b9c6659c9c..14d71d90d9 100644 --- a/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs +++ b/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs @@ -61,12 +61,12 @@ public sealed class SSDIndicatorSystem : EntitySystem // Prevents mapped mobs to go to sleep immediately private void OnMapInit(EntityUid uid, SSDIndicatorComponent component, MapInitEvent args) { - if (_icSsdSleep && - component.IsSSD && - component.FallAsleepTime == TimeSpan.Zero) - { - component.FallAsleepTime = _timing.CurTime + TimeSpan.FromSeconds(_icSsdSleepTime); - } + if (!_icSsdSleep || !component.IsSSD) + return; + + component.FallAsleepTime = _timing.CurTime + TimeSpan.FromSeconds(_icSsdSleepTime); + component.NextUpdate = _timing.CurTime + component.UpdateInterval; + Dirty(uid, component); } public override void Update(float frameTime) @@ -76,17 +76,21 @@ public sealed class SSDIndicatorSystem : EntitySystem if (!_icSsdSleep) return; + var curTime = _timing.CurTime; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var ssd)) { // Forces the entity to sleep when the time has come - if (ssd.IsSSD && - ssd.FallAsleepTime <= _timing.CurTime && - !TerminatingOrDeleted(uid)) - { - _statusEffects.TrySetStatusEffectDuration(uid, StatusEffectSSDSleeping, null); - } + if (!ssd.IsSSD + || ssd.NextUpdate > curTime + || ssd.FallAsleepTime > curTime + || TerminatingOrDeleted(uid)) + continue; + + _statusEffects.TryUpdateStatusEffectDuration(uid, StatusEffectSSDSleeping); + ssd.NextUpdate += ssd.UpdateInterval; + Dirty(uid, ssd); } } } diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e5d0e5c416..c11e5bbce0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,19 +1,4 @@ Entries: -- author: HyperB1 - changes: - - message: ERT engineering hardsuit now features proper fire protection. - type: Fix - id: 8285 - time: '2025-04-20T14:06:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/34949 -- author: K-Dynamic - changes: - - message: Nonlethal throwables crate can be ordered from cargo (2500 spesos) and - contains 4x bolas, 4x flashbang grenades, 4x tear gas grenades, 4x stinger grenades. - type: Add - id: 8286 - time: '2025-04-20T14:08:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/35808 - author: RedBookcase changes: - message: Updated Pirate weapons & hardsuits to be more inline with similar items. @@ -3905,3 +3890,20 @@ id: 8797 time: '2025-07-21T03:21:28.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/38648 +- author: ArtisticRoomba + changes: + - message: China-lake fragmentation grenades now spew fragmentation in all directions + upon impact. They now do reduced explosion damage. + type: Tweak + - message: China-lake blast grenades no longer cause spacing. + type: Tweak + id: 8798 + time: '2025-07-22T15:05:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39106 +- author: perryprog + changes: + - message: Void applause should now function as expected again. + type: Fix + id: 8799 + time: '2025-07-22T19:34:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38731 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index d46c5657c1..6ecac1b922 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2176,6 +2176,7 @@ - type: entity parent: [ SimpleMobBase, FlyingMobBase ] id: MobParrotBase + name: parrot abstract: true description: Infiltrates your domain, spies on you, and somehow still a cool pet. components: @@ -2251,7 +2252,6 @@ bloodMaxVolume: 50 - type: entity - name: parrot parent: MobParrotBase id: MobParrot components: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/grenade.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/grenade.yml index 873d970214..02aab56f7b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/grenade.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/grenade.yml @@ -54,3 +54,20 @@ count: 10 - type: TimedDespawn lifetime: 0.25 + +- type: entity + id: PelletClusterLessLethal + name: pellet (ball, less lethal) + categories: [ HideSpawnMenu ] + parent: BaseBullet + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles2.rsi + state: buckshot + - type: Projectile + deleteOnCollide: false + damage: + types: + Piercing: 5 + - type: TimedDespawn + lifetime: 0.25 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index 7a15d6520e..e89792694d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -863,6 +863,7 @@ totalIntensity: 150 # a ~2 tile radius intensitySlope: 5 maxIntensity: 10 + canCreateVacuum: false - type: entity id: BulletGrenadeFlash @@ -882,7 +883,6 @@ timeRemaining: 0.3 - type: DeleteOnTrigger -# This is supposed to spawn shrapnel and stuff so uhh... TODO? - type: entity id: BulletGrenadeFrag name: frag grenade @@ -896,9 +896,16 @@ - type: ExplodeOnTrigger - type: Explosive explosionType: Default - totalIntensity: 175 # about a ~6 tile radius + totalIntensity: 50 intensitySlope: 1 - maxIntensity: 10 + maxIntensity: 5 + canCreateVacuum: false + - type: ContainerContainer + containers: + cluster-payload: !type:Container + - type: ProjectileGrenade + fillPrototype: PelletClusterLessLethal + capacity: 30 - type: entity parent: BaseBulletTrigger diff --git a/Resources/Prototypes/Magic/teleport_spells.yml b/Resources/Prototypes/Magic/teleport_spells.yml index ab79a2f5d0..fc166e8c4b 100644 --- a/Resources/Prototypes/Magic/teleport_spells.yml +++ b/Resources/Prototypes/Magic/teleport_spells.yml @@ -35,7 +35,8 @@ sprite: Objects/Magic/Eldritch/eldritch_actions.rsi state: voidblink - type: TargetAction - checkCanAccess: false + accessMask: + - Opaque repeat: false range: 16 - type: EntityTargetAction