From 5004cf83d86683dace5d1da068b67fdfd95b9f44 Mon Sep 17 00:00:00 2001 From: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:38:45 +0300 Subject: [PATCH 01/24] Physical Pulling (#883) # Description Fixes pulling being obnoxiously unphysical. Entities now exert a specific amount of force defined as the product of their mass and the SpecificForce set in their PullerComponent. Doing a ctrl-right click now sets a target position where your entity begins to continuously push the pulled entity towards, instead of throwing it or pushing it once (however, it should still be possible to drag people and disposals and the like). Additionally, your entity will properly experience recoil from dragging someone around. # TODO - [X] Do stuff - [X] Fix stuff - [X] Fix aghosts not being able to push (?) - [X] Fix pulling giving you free kinetic energy while in spess (how?) - [ ] Test if throwing felinids into disposals via pulling still works :trollface:

Media

https://github.com/user-attachments/assets/2498ef8c-f743-4702-b73c-b2da0c16e9aa

# Changelog :cl: - add: Pulling has been reworked. Your character will now try to continuously push the pulled entity when you use the push keybind. - remove: You can no longer push the pulled entity while walking, and pushing now follows the momentum conservation laws. --- .../Pulling/Components/PullableComponent.cs | 7 + .../Pulling/Components/PullerComponent.cs | 38 ++++- .../Movement/Pulling/Systems/PullingSystem.cs | 137 +++++++++++++++--- .../Entities/Mobs/Player/admin_ghost.yml | 2 + 4 files changed, 154 insertions(+), 30 deletions(-) diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs index db889e7e3bd..01ce0efaae6 100644 --- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs @@ -36,4 +36,11 @@ public sealed partial class PullableComponent : Component [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] [AutoNetworkedField, DataField] public bool PrevFixedRotation; + + /// + /// Whether the entity is currently being actively pushed by the puller. + /// If true, the entity will be able to enter disposals upon colliding with them, and the like. + /// + [DataField, AutoNetworkedField] + public bool BeingActivelyPushed = false; } diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs index 1fc9b731bd5..c13baa28eff 100644 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Movement.Pulling.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Map; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Movement.Pulling.Components; @@ -11,16 +12,17 @@ namespace Content.Shared.Movement.Pulling.Components; [Access(typeof(PullingSystem))] public sealed partial class PullerComponent : Component { - // My raiding guild /// - /// Next time the puller can throw what is being pulled. - /// Used to avoid spamming it for infinite spin + velocity. + /// Next time the puller change where they are pulling the target towards. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] - public TimeSpan NextThrow; + public TimeSpan NextPushTargetChange; + + [DataField, AutoNetworkedField] + public TimeSpan NextPushStop; [DataField] - public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(1); + public TimeSpan PushChangeCooldown = TimeSpan.FromSeconds(0.1f), PushDuration = TimeSpan.FromSeconds(2f); // Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed public float WalkSpeedModifier => Pulling == default ? 1.0f : 0.95f; @@ -28,14 +30,38 @@ public sealed partial class PullerComponent : Component public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f; /// - /// Entity currently being pulled if applicable. + /// Entity currently being pulled/pushed if applicable. /// [AutoNetworkedField, DataField] public EntityUid? Pulling; + /// + /// The position the entity is currently being pulled towards. + /// + [AutoNetworkedField, DataField] + public EntityCoordinates? PushingTowards; + /// /// Does this entity need hands to be able to pull something? /// [DataField] public bool NeedsHands = true; + + /// + /// The maximum acceleration of pushing, in meters per second squared. + /// + [DataField] + public float PushAcceleration = 0.3f; + + /// + /// The maximum speed to which the pulled entity may be accelerated relative to the push direction, in meters per second. + /// + [DataField] + public float MaxPushSpeed = 1.2f; + + /// + /// The maximum distance between the puller and the point towards which the puller may attempt to pull it, in meters. + /// + [DataField] + public float MaxPushRange = 2f; } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 3c265d5a027..e6acabc33c5 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -8,16 +8,19 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Systems; +using Content.Shared.Projectiles; using Content.Shared.Pulling.Events; using Content.Shared.Throwing; using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -34,6 +37,7 @@ public sealed class PullingSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly MovementSpeedModifierSystem _modifierSystem = default!; @@ -43,7 +47,7 @@ public sealed class PullingSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _xformSys = default!; - [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly ThrownItemSystem _thrownItem = default!; public override void Initialize() { @@ -57,7 +61,9 @@ public override void Initialize() SubscribeLocalEvent(OnJointRemoved); SubscribeLocalEvent>(AddPullVerbs); SubscribeLocalEvent(OnPullableContainerInsert); + SubscribeLocalEvent(OnPullableCollide); + SubscribeLocalEvent(OnPullerMoveInput); SubscribeLocalEvent(OnPullerContainerInsert); SubscribeLocalEvent(OnPullerUnpaused); SubscribeLocalEvent(OnVirtualItemDeleted); @@ -69,6 +75,86 @@ public override void Initialize() .Register(); } + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); + } + + public override void Update(float frameTime) + { + if (_net.IsClient) // Client cannot predict this + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var puller, out var pullerComp, out var pullerPhysics, out var pullerXForm)) + { + // If not pulling, reset the pushing cooldowns and exit + if (pullerComp.Pulling is not { } pulled || !TryComp(pulled, out var pulledComp)) + { + pullerComp.PushingTowards = null; + pullerComp.NextPushTargetChange = TimeSpan.Zero; + continue; + } + + pulledComp.BeingActivelyPushed = false; // Temporarily set to false; if the checks below pass, it will be set to true again + + // If pulling but the pullee is invalid or is on a different map, stop pulling + var pulledXForm = Transform(pulled); + if (!TryComp(pulled, out var pulledPhysics) + || pulledPhysics.BodyType == BodyType.Static + || pulledXForm.MapUid != pullerXForm.MapUid) + { + StopPulling(pulled, pulledComp); + continue; + } + + if (pullerComp.PushingTowards is null) + continue; + + // If pushing but the target position is invalid, or the push action has expired or finished, stop pushing + if (pullerComp.NextPushStop < _timing.CurTime + || !(pullerComp.PushingTowards.Value.ToMap(EntityManager, _xformSys) is var pushCoordinates) + || pushCoordinates.MapId != pulledXForm.MapID) + { + pullerComp.PushingTowards = null; + pullerComp.NextPushTargetChange = TimeSpan.Zero; + continue; + } + + // Actual force calculation. All the Vector2's below are in map coordinates. + var desiredDeltaPos = pushCoordinates.Position - Transform(pulled).Coordinates.ToMapPos(EntityManager, _xformSys); + if (desiredDeltaPos.LengthSquared() < 0.1f) + { + pullerComp.PushingTowards = null; + continue; + } + + var velocityAndDirectionAngle = new Angle(pulledPhysics.LinearVelocity) - new Angle(desiredDeltaPos); + var currentRelativeSpeed = pulledPhysics.LinearVelocity.Length() * (float) Math.Cos(velocityAndDirectionAngle.Theta); + var desiredAcceleration = MathF.Max(0f, pullerComp.MaxPushSpeed - currentRelativeSpeed); + + var desiredImpulse = pulledPhysics.Mass * desiredDeltaPos; + var maxSourceImpulse = MathF.Min(pullerComp.PushAcceleration, desiredAcceleration) * pullerPhysics.Mass; + var actualImpulse = desiredImpulse.LengthSquared() > maxSourceImpulse * maxSourceImpulse ? desiredDeltaPos.Normalized() * maxSourceImpulse : desiredImpulse; + + // Ideally we'd want to apply forces instead of impulses, however... + // We cannot use ApplyForce here because it will be cleared on the next physics substep which will render it ultimately useless + // The alternative is to run this function on every physics substep, but that is way too expensive for such a minor system + _physics.ApplyLinearImpulse(pulled, actualImpulse); + _physics.ApplyLinearImpulse(puller, -actualImpulse); + pulledComp.BeingActivelyPushed = true; + } + query.Dispose(); + } + + private void OnPullerMoveInput(EntityUid uid, PullerComponent component, ref MoveInputEvent args) + { + // Stop pushing + component.PushingTowards = null; + component.NextPushStop = TimeSpan.Zero; + } + private void OnPullerContainerInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args) { if (ent.Comp.Pulling == null) return; @@ -84,15 +170,26 @@ private void OnPullableContainerInsert(Entity ent, ref EntGot TryStopPull(ent.Owner, ent.Comp); } - public override void Shutdown() + private void OnPullableCollide(Entity ent, ref StartCollideEvent args) { - base.Shutdown(); - CommandBinds.Unregister(); + if (!ent.Comp.BeingActivelyPushed || args.OtherEntity == ent.Comp.Puller) + return; + + // This component isn't actually needed anywhere besides the thrownitemsyste`m itself, so we just fake it + var fakeThrown = new ThrownItemComponent() + { + Owner = ent.Owner, + Animate = false, + Landed = false, + PlayLandSound = false, + Thrower = ent.Comp.Puller + }; + _thrownItem.ThrowCollideInteraction(fakeThrown, ent, args.OtherEntity); } private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref EntityUnpausedEvent args) { - component.NextThrow += args.PausedTime; + component.NextPushTargetChange += args.PausedTime; } private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args) @@ -234,31 +331,22 @@ public bool IsPulled(EntityUid uid, PullableComponent? component = null) private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) { - if (session?.AttachedEntity is not { } player || - !player.IsValid()) - { - return false; - } - - if (!TryComp(player, out var pullerComp)) + if (session?.AttachedEntity is not { } player + || !player.IsValid() + || !TryComp(player, out var pullerComp)) return false; var pulled = pullerComp.Pulling; - - if (!HasComp(pulled)) - return false; - - if (_containerSystem.IsEntityInContainer(player)) + if (!HasComp(pulled) + || _containerSystem.IsEntityInContainer(player) + || _timing.CurTime < pullerComp.NextPushTargetChange) return false; - // Cooldown buddy - if (_timing.CurTime < pullerComp.NextThrow) - return false; - - pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown; + pullerComp.NextPushTargetChange = _timing.CurTime + pullerComp.PushChangeCooldown; + pullerComp.NextPushStop = _timing.CurTime + pullerComp.PushDuration; // Cap the distance - const float range = 2f; + var range = pullerComp.MaxPushRange; var fromUserCoords = coords.WithEntityId(player, EntityManager); var userCoords = new EntityCoordinates(player, Vector2.Zero); @@ -268,8 +356,9 @@ private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinate fromUserCoords = userCoords.Offset(userDirection.Normalized() * range); } + pullerComp.PushingTowards = fromUserCoords; Dirty(player, pullerComp); - _throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false, doSpin: false); + return false; } diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 6c9ec2049f4..e0300fa5033 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -25,6 +25,8 @@ - type: GhostHearing - type: Hands - type: Puller + pushAcceleration: 1000000 # Will still be capped in max speed + maxPushRange: 20 - type: CombatMode - type: Physics ignorePaused: true From b65a5d853baeccaa15734a5e09dec03996333cac Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 19:39:07 -0400 Subject: [PATCH 02/24] Core Fullsize (Underfueled) AME (#927) # Description By request. A typical engineering team takes as much as 40 minutes to setup a Supermatter engine with no experience. Core is designed to **FORCE** players to setup a Singulo, which takes 15 minutes, therefore Core loses power in 20 minutes with its comically undersized and underpowered AME. I increased the size of Core's AME so that engineers have more time to setup Supermatter. A lot more time actually. # Changelog :cl: - tweak: Core now has a 6-Core AME, which is supplied with two jars of fuel. This should give Engineers significantly more than 20 minutes of time to setup the Supermatter engine. --- Resources/Maps/core.yml | 292 +++++++++++++++++++++------------------- 1 file changed, 155 insertions(+), 137 deletions(-) diff --git a/Resources/Maps/core.yml b/Resources/Maps/core.yml index 0f31cea8379..d8ad22c30ac 100644 --- a/Resources/Maps/core.yml +++ b/Resources/Maps/core.yml @@ -97,11 +97,11 @@ entities: version: 6 0,-2: ind: 0,-2 - tiles: aAAAAAAAaAAAAAAAXQAAAAADXQAAAAADaAAAAAACXQAAAAABXQAAAAADXQAAAAABaAAAAAABXQAAAAADaAAAAAADXQAAAAAAXQAAAAABXQAAAAAAXQAAAAADXQAAAAADTgAAAAABTgAAAAABaAAAAAACaAAAAAACaAAAAAABaAAAAAAAaAAAAAABaAAAAAACaAAAAAABXQAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAACXQAAAAABXQAAAAAAXQAAAAAAaAAAAAADXQAAAAABXQAAAAABXQAAAAACXQAAAAADXQAAAAABfgAAAAAAHwAAAAAAJAAAAAAAJAAAAAABJAAAAAACJAAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAAAXQAAAAABXQAAAAAAXQAAAAADXQAAAAABfgAAAAAAHwAAAAACJAAAAAABfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAAAfgAAAAAAJAAAAAADegAAAAAAfgAAAAAAHwAAAAACHwAAAAAAHwAAAAACHwAAAAAAJAAAAAADfgAAAAAAHwAAAAADJAAAAAABfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAADHwAAAAADegAAAAAAegAAAAAAfgAAAAAAHwAAAAAAHwAAAAACHwAAAAADHwAAAAABHwAAAAABfgAAAAAAHwAAAAACJAAAAAADfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAACfgAAAAAAegAAAAAAegAAAAADfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAADJAAAAAABJAAAAAAAJAAAAAADJAAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAADXQAAAAACaAAAAAABXQAAAAABHwAAAAABfgAAAAAAHwAAAAACJAAAAAABJAAAAAACJAAAAAAAJAAAAAABXQAAAAAAXQAAAAAAJAAAAAAAfgAAAAAAJAAAAAACaAAAAAADaAAAAAAAaAAAAAACXQAAAAAAHwAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABfgAAAAAATgAAAAACTgAAAAADJAAAAAABfgAAAAAAaAAAAAADaAAAAAAAaAAAAAABaAAAAAACXQAAAAACHwAAAAABHwAAAAACHwAAAAADHwAAAAAAHwAAAAACHwAAAAAAHwAAAAADXQAAAAAAXQAAAAABXQAAAAABaAAAAAADaAAAAAADaAAAAAACaAAAAAADaAAAAAACXQAAAAABHwAAAAACHwAAAAAAHwAAAAABHwAAAAABXQAAAAADXQAAAAADaAAAAAAAXQAAAAAAXQAAAAABXQAAAAADfgAAAAAAaAAAAAACaAAAAAADaAAAAAACaAAAAAADXQAAAAAAHwAAAAAAHwAAAAAAfgAAAAAAHwAAAAACXQAAAAADXQAAAAABaAAAAAAAHwAAAAACHwAAAAAAHwAAAAABfgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAADXQAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAaAAAAAACaAAAAAABaAAAAAABaAAAAAABXQAAAAABfgAAAAAAJAAAAAACJAAAAAAAJAAAAAABXQAAAAABXQAAAAADaAAAAAAAXQAAAAAAXQAAAAADXQAAAAACfgAAAAAAXQAAAAABXQAAAAABXQAAAAABXQAAAAADXQAAAAAAfgAAAAAAJAAAAAACHwAAAAACHwAAAAACXQAAAAAAXQAAAAADaAAAAAADXQAAAAABXQAAAAACXQAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAbAAAAAAAfgAAAAAAJAAAAAAAHwAAAAADHwAAAAABXQAAAAADXQAAAAAAaAAAAAAD + tiles: aAAAAAAAaAAAAAAAXQAAAAADXQAAAAADaAAAAAACXQAAAAABXQAAAAADXQAAAAABaAAAAAABXQAAAAADaAAAAAADXQAAAAAAXQAAAAABXQAAAAAAXQAAAAADXQAAAAADTgAAAAABTgAAAAABaAAAAAACaAAAAAACaAAAAAABaAAAAAAAaAAAAAABaAAAAAACaAAAAAABXQAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAACXQAAAAABXQAAAAAAXQAAAAAAaAAAAAADXQAAAAABXQAAAAABXQAAAAACXQAAAAADXQAAAAABfgAAAAAAHwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAAAXQAAAAABXQAAAAAAXQAAAAADXQAAAAABfgAAAAAAHwAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAAAfgAAAAAAJAAAAAADegAAAAAAfgAAAAAAHwAAAAACHwAAAAAAHwAAAAACHwAAAAAAJAAAAAADfgAAAAAAHwAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAADHwAAAAADegAAAAAAegAAAAAAfgAAAAAAHwAAAAAAHwAAAAACHwAAAAADHwAAAAABHwAAAAABfgAAAAAAHwAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAACfgAAAAAAegAAAAAAegAAAAADfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAADJAAAAAABJAAAAAAAJAAAAAADJAAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAADXQAAAAACaAAAAAABXQAAAAABHwAAAAABfgAAAAAAHwAAAAACJAAAAAABJAAAAAACJAAAAAAAJAAAAAABXQAAAAAAXQAAAAAAJAAAAAAAfgAAAAAAJAAAAAACaAAAAAADaAAAAAAAaAAAAAACXQAAAAAAHwAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABfgAAAAAATgAAAAACTgAAAAADJAAAAAABfgAAAAAAaAAAAAADaAAAAAAAaAAAAAABaAAAAAACXQAAAAACHwAAAAABHwAAAAACHwAAAAADHwAAAAAAHwAAAAACHwAAAAAAHwAAAAADXQAAAAAAXQAAAAABXQAAAAABaAAAAAADaAAAAAADaAAAAAACaAAAAAADaAAAAAACXQAAAAABHwAAAAACHwAAAAAAHwAAAAABHwAAAAABXQAAAAADXQAAAAADaAAAAAAAXQAAAAAAXQAAAAABXQAAAAADfgAAAAAAaAAAAAACaAAAAAADaAAAAAACaAAAAAADXQAAAAAAHwAAAAAAHwAAAAAAfgAAAAAAHwAAAAACXQAAAAADXQAAAAABaAAAAAAAHwAAAAACHwAAAAAAHwAAAAABfgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAADXQAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAADfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAaAAAAAACaAAAAAABaAAAAAABaAAAAAABXQAAAAABfgAAAAAAJAAAAAACJAAAAAAAJAAAAAABXQAAAAABXQAAAAADaAAAAAAAXQAAAAAAXQAAAAADXQAAAAACfgAAAAAAXQAAAAABXQAAAAABXQAAAAABXQAAAAADXQAAAAAAfgAAAAAAJAAAAAACHwAAAAACHwAAAAACXQAAAAAAXQAAAAADaAAAAAADXQAAAAABXQAAAAACXQAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAbAAAAAAAfgAAAAAAJAAAAAAAHwAAAAADHwAAAAABXQAAAAADXQAAAAAAaAAAAAAD version: 6 1,-2: ind: 1,-2 - tiles: XQAAAAAAXQAAAAABaAAAAAAAXQAAAAAAaAAAAAAAXQAAAAACfgAAAAAAHwAAAAACaAAAAAADaAAAAAABaAAAAAABaAAAAAADaAAAAAABHwAAAAABfgAAAAAAHwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAADaAAAAAABXQAAAAABfgAAAAAAHwAAAAAAaAAAAAADaAAAAAAAaAAAAAADaAAAAAABaAAAAAABHwAAAAAAfgAAAAAAHwAAAAAAJAAAAAABHwAAAAACfgAAAAAAXQAAAAABaAAAAAACXQAAAAACfgAAAAAAHwAAAAAAaAAAAAAAaAAAAAABaAAAAAABXQAAAAADaAAAAAACHwAAAAADHwAAAAACHwAAAAACJAAAAAAAHwAAAAADfgAAAAAAXQAAAAADaAAAAAAAXQAAAAABfgAAAAAAHwAAAAADaAAAAAABaAAAAAABaAAAAAABaAAAAAADaAAAAAADHwAAAAABfgAAAAAAHwAAAAAAJAAAAAACHwAAAAABfgAAAAAAaAAAAAAAaAAAAAABaAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABJAAAAAADHwAAAAAAfgAAAAAAXQAAAAADaAAAAAACXQAAAAAAJAAAAAABXQAAAAAAXQAAAAADXQAAAAABXQAAAAABXQAAAAACXQAAAAAAXQAAAAACfgAAAAAAfgAAAAAAJAAAAAADHwAAAAABfgAAAAAAXQAAAAACaAAAAAACaAAAAAAAaAAAAAABaAAAAAACaAAAAAACaAAAAAABaAAAAAADaAAAAAABaAAAAAADXQAAAAACfgAAAAAAHwAAAAABJAAAAAAAHwAAAAADfgAAAAAAXQAAAAACXQAAAAADXQAAAAABXQAAAAACXQAAAAABXQAAAAAAXQAAAAADXQAAAAABXQAAAAABaAAAAAAAXQAAAAACfgAAAAAAXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAACXQAAAAABXQAAAAABfgAAAAAAXQAAAAACfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAACaAAAAAADXQAAAAACfgAAAAAAXQAAAAABfgAAAAAAHwAAAAAAHwAAAAACXQAAAAABXQAAAAAAXQAAAAACfgAAAAAAXQAAAAABfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAABaAAAAAACXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAAAHwAAAAAAXQAAAAACXQAAAAACXQAAAAAAXQAAAAACXQAAAAACfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAABaAAAAAAAXQAAAAACfgAAAAAAXQAAAAAAfgAAAAAAHwAAAAAAJAAAAAAAXQAAAAACXQAAAAACXQAAAAADfgAAAAAAHwAAAAABfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAAAaAAAAAABXQAAAAAAaAAAAAABXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAABaAAAAAABaAAAAAABaAAAAAAAaAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABHwAAAAABHwAAAAABHwAAAAADfgAAAAAAXQAAAAABXQAAAAABXQAAAAACaAAAAAAAXQAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABHwAAAAAAHwAAAAABHwAAAAABfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAADHwAAAAACHwAAAAADHwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfQAAAAAA + tiles: XQAAAAAAXQAAAAABaAAAAAAAXQAAAAAAaAAAAAAAXQAAAAACfgAAAAAAHwAAAAACaAAAAAADaAAAAAABaAAAAAABaAAAAAADaAAAAAABHwAAAAABfgAAAAAAHwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAADaAAAAAABXQAAAAABfgAAAAAAHwAAAAAAaAAAAAADaAAAAAAAaAAAAAADaAAAAAABaAAAAAABHwAAAAAAfgAAAAAAHwAAAAAAfgAAAAAAHwAAAAACfgAAAAAAXQAAAAABaAAAAAACXQAAAAACfgAAAAAAHwAAAAAAaAAAAAAAaAAAAAABaAAAAAABXQAAAAADaAAAAAACHwAAAAADHwAAAAACHwAAAAACfgAAAAAAHwAAAAADfgAAAAAAXQAAAAADaAAAAAAAXQAAAAABfgAAAAAAHwAAAAADaAAAAAABaAAAAAABaAAAAAABaAAAAAADaAAAAAADHwAAAAABfgAAAAAAHwAAAAAAfgAAAAAAHwAAAAABfgAAAAAAaAAAAAAAaAAAAAABaAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABfgAAAAAAHwAAAAAAfgAAAAAAXQAAAAADaAAAAAACXQAAAAAAJAAAAAABXQAAAAAAXQAAAAADXQAAAAABXQAAAAABXQAAAAACXQAAAAAAXQAAAAACfgAAAAAAfgAAAAAAJAAAAAADHwAAAAABfgAAAAAAXQAAAAACaAAAAAACaAAAAAAAaAAAAAABaAAAAAACaAAAAAACaAAAAAABaAAAAAADaAAAAAABaAAAAAADXQAAAAACfgAAAAAAHwAAAAABJAAAAAAAHwAAAAADfgAAAAAAXQAAAAACXQAAAAADXQAAAAABXQAAAAACXQAAAAABXQAAAAAAXQAAAAADXQAAAAABXQAAAAABaAAAAAAAXQAAAAACfgAAAAAAXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAACXQAAAAABXQAAAAABfgAAAAAAXQAAAAACfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAACaAAAAAADXQAAAAACfgAAAAAAXQAAAAABfgAAAAAAHwAAAAAAHwAAAAACXQAAAAABXQAAAAAAXQAAAAACfgAAAAAAXQAAAAABfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAABaAAAAAACXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAAAHwAAAAAAXQAAAAACXQAAAAACXQAAAAAAXQAAAAACXQAAAAACfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAABaAAAAAAAXQAAAAACfgAAAAAAXQAAAAAAfgAAAAAAHwAAAAAAJAAAAAAAXQAAAAACXQAAAAACXQAAAAADfgAAAAAAHwAAAAABfgAAAAAAAwAAAAAAAwAAAAAAXQAAAAAAaAAAAAABXQAAAAAAaAAAAAABXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAABfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAXQAAAAABaAAAAAABaAAAAAABaAAAAAAAaAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABHwAAAAABHwAAAAABHwAAAAADfgAAAAAAXQAAAAABXQAAAAABXQAAAAACaAAAAAAAXQAAAAACfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAABHwAAAAAAHwAAAAABHwAAAAABfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAHwAAAAADHwAAAAACHwAAAAADHwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfQAAAAAA version: 6 1,-1: ind: 1,-1 @@ -3793,7 +3793,6 @@ entities: 5754: 12,-22 5755: 15,-26 5756: 12,-26 - 5757: 12,-29 5758: 7,-30 5759: 8,-32 5760: 7,-32 @@ -3860,15 +3859,6 @@ entities: 6417: 39,-3 6418: 37,-3 6419: 37,-3 - - node: - cleanable: True - color: '#FFFFFF47' - id: Dirt - decals: - 6314: 15,-30 - 6315: 15,-30 - 6316: 16,-27 - 6317: 16,-27 - node: cleanable: True color: '#FFFFFFFF' @@ -5194,7 +5184,6 @@ entities: 4911: 13,-17 4912: 11,-30 4913: 11,-28 - 4914: 11,-27 4915: 11,-26 4916: 17,-26 4917: 17,-28 @@ -5955,15 +5944,12 @@ entities: 3363: 10,-16 3364: 10,-18 3365: 10,-18 - 3366: 12,-29 - 3367: 12,-28 3368: 12,-26 3369: 13,-25 3370: 14,-25 3371: 14,-26 3372: 15,-25 3373: 16,-26 - 3374: 16,-29 3375: -4,-36 3376: -4,-37 3377: -6,-37 @@ -6482,10 +6468,6 @@ entities: id: DirtHeavyMonotile decals: 2847: -44,17 - 6310: 13,-30 - 6311: 16,-30 - 6312: 12,-30 - 6313: 12,-30 - node: cleanable: True angle: -6.283185307179586 rad @@ -8333,12 +8315,6 @@ entities: decals: 6401: 49,-11 6402: 52,-11 - - node: - color: '#EFB34118' - id: WarnCornerSmallNE - decals: - 6325: 12,-30 - 6326: 12,-30 - node: color: '#EFB34131' id: WarnCornerSmallNE @@ -8347,11 +8323,6 @@ entities: 6471: 14,39 6472: 14,39 6473: 14,39 - - node: - color: '#EFB3416C' - id: WarnCornerSmallNE - decals: - 6324: 12,-30 - node: color: '#EFB34196' id: WarnCornerSmallNE @@ -8368,12 +8339,6 @@ entities: 6071: 51,-11 6748: 46,15 6846: 57,-5 - - node: - color: '#EFB34118' - id: WarnCornerSmallNW - decals: - 6327: 16,-30 - 6328: 16,-30 - node: color: '#EFB34131' id: WarnCornerSmallNW @@ -8382,11 +8347,6 @@ entities: 6467: 16,39 6468: 16,39 6469: 16,39 - - node: - color: '#EFB3416C' - id: WarnCornerSmallNW - decals: - 6323: 16,-30 - node: color: '#EFB34196' id: WarnCornerSmallNW @@ -8469,9 +8429,6 @@ entities: 85: -4,-19 116: -4,-16 119: -2,-15 - 189: 12,-27 - 190: 12,-28 - 191: 12,-29 265: 25,-16 266: 25,-15 276: 29,-13 @@ -8648,9 +8605,6 @@ entities: 88: -10,-17 89: -10,-16 128: 0,-21 - 186: 16,-27 - 187: 16,-28 - 188: 16,-29 194: 13,-21 195: 13,-20 196: 13,-19 @@ -8677,16 +8631,6 @@ entities: 6370: -33,-11 6743: 46,16 6824: 61,-4 - - node: - color: '#EFB34118' - id: WarnLineW - decals: - 6329: 13,-30 - 6330: 13,-30 - 6331: 14,-30 - 6332: 14,-30 - 6333: 15,-30 - 6334: 15,-30 - node: color: '#EFB34131' id: WarnLineW @@ -8695,13 +8639,6 @@ entities: 6451: 15,39 6452: 15,39 6453: 15,39 - - node: - color: '#EFB3416C' - id: WarnLineW - decals: - 6320: 13,-30 - 6321: 14,-30 - 6322: 15,-30 - node: color: '#EFB34196' id: WarnLineW @@ -17945,10 +17882,15 @@ entities: parent: 2 - proto: AmeJar entities: - - uid: 94 + - uid: 695 components: - type: Transform - pos: 17.51151,-27.35627 + pos: 15.659197,-24.407036 + parent: 2 + - uid: 1534 + components: + - type: Transform + pos: 15.346697,-24.39141 parent: 2 - proto: AmePartFlatpack entities: @@ -17997,6 +17939,61 @@ entities: - type: Transform pos: 16.69675,-24.555443 parent: 2 + - uid: 693 + components: + - type: Transform + pos: 16.299822,-24.36016 + parent: 2 + - uid: 694 + components: + - type: Transform + pos: 17.768572,-25.313286 + parent: 2 + - uid: 6126 + components: + - type: Transform + pos: 16.299822,-24.20391 + parent: 2 + - uid: 10573 + components: + - type: Transform + pos: 16.299822,-24.563286 + parent: 2 + - uid: 22532 + components: + - type: Transform + pos: 17.768572,-25.563286 + parent: 2 + - uid: 22533 + components: + - type: Transform + pos: 17.768572,-25.73516 + parent: 2 + - uid: 22534 + components: + - type: Transform + pos: 17.284197,-25.70391 + parent: 2 + - uid: 22535 + components: + - type: Transform + pos: 17.284197,-25.48516 + parent: 2 + - uid: 22536 + components: + - type: Transform + pos: 17.284197,-25.26641 + parent: 2 + - uid: 22538 + components: + - type: Transform + pos: 17.737322,-26.20391 + parent: 2 + - uid: 22539 + components: + - type: Transform + pos: 17.518572,-26.344536 + parent: 2 - proto: AmmoniaCanister entities: - uid: 22510 @@ -51029,50 +51026,37 @@ entities: - uid: 641 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,-26.5 - parent: 2 - - uid: 642 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,-27.5 + pos: 12.5,-28.5 parent: 2 - uid: 643 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,-28.5 + pos: 12.5,-27.5 parent: 2 - uid: 647 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,-26.5 + pos: 13.5,-29.5 parent: 2 - uid: 648 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,-27.5 + pos: 12.5,-29.5 parent: 2 - uid: 650 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,-27.5 + pos: 13.5,-27.5 parent: 2 - uid: 651 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,-28.5 + pos: 14.5,-26.5 parent: 2 - uid: 652 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,-26.5 + pos: 13.5,-28.5 parent: 2 - uid: 653 components: @@ -51212,6 +51196,11 @@ entities: rot: 1.5707963267948966 rad pos: 16.5,-13.5 parent: 2 + - uid: 692 + components: + - type: Transform + pos: 13.5,-26.5 + parent: 2 - uid: 753 components: - type: Transform @@ -51400,6 +51389,11 @@ entities: rot: -1.5707963267948966 rad pos: -22.5,25.5 parent: 2 + - uid: 2189 + components: + - type: Transform + pos: 12.5,-26.5 + parent: 2 - uid: 2199 components: - type: Transform @@ -51660,11 +51654,6 @@ entities: - type: Transform pos: 40.5,21.5 parent: 2 - - uid: 6126 - components: - - type: Transform - pos: 14.5,-28.5 - parent: 2 - uid: 6203 components: - type: Transform @@ -56376,6 +56365,61 @@ entities: - type: Transform pos: -6.5,-12.5 parent: 2 + - uid: 22521 + components: + - type: Transform + pos: 14.5,-27.5 + parent: 2 + - uid: 22522 + components: + - type: Transform + pos: 14.5,-28.5 + parent: 2 + - uid: 22523 + components: + - type: Transform + pos: 14.5,-29.5 + parent: 2 + - uid: 22524 + components: + - type: Transform + pos: 15.5,-29.5 + parent: 2 + - uid: 22525 + components: + - type: Transform + pos: 15.5,-28.5 + parent: 2 + - uid: 22526 + components: + - type: Transform + pos: 15.5,-27.5 + parent: 2 + - uid: 22527 + components: + - type: Transform + pos: 15.5,-26.5 + parent: 2 + - uid: 22528 + components: + - type: Transform + pos: 16.5,-26.5 + parent: 2 + - uid: 22529 + components: + - type: Transform + pos: 16.5,-27.5 + parent: 2 + - uid: 22530 + components: + - type: Transform + pos: 16.5,-28.5 + parent: 2 + - uid: 22531 + components: + - type: Transform + pos: 16.5,-29.5 + parent: 2 - proto: Chair entities: - uid: 678 @@ -56452,28 +56496,6 @@ entities: rot: 3.141592653589793 rad pos: 6.5,-24.5 parent: 2 - - uid: 692 - components: - - type: Transform - pos: 11.5,-26.5 - parent: 2 - - uid: 693 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 11.5,-28.5 - parent: 2 - - uid: 694 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 17.5,-28.5 - parent: 2 - - uid: 695 - components: - - type: Transform - pos: 17.5,-26.5 - parent: 2 - uid: 696 components: - type: Transform @@ -62611,10 +62633,10 @@ entities: parent: 2 - proto: DefaultStationBeaconAME entities: - - uid: 10573 + - uid: 642 components: - type: Transform - pos: 14.5,-27.5 + pos: 13.5,-25.5 parent: 2 - proto: DefaultStationBeaconAnomalyGenerator entities: @@ -105392,7 +105414,7 @@ entities: - type: Transform pos: 14.5,32.5 parent: 2 -- proto: Oracle +- proto: OracleSpawner entities: - uid: 22459 components: @@ -105879,13 +105901,6 @@ entities: - type: Transform pos: -5.47442,23.570398 parent: 2 -- proto: PaperWrittenAMEScribbles - entities: - - uid: 1534 - components: - - type: Transform - pos: 11.480261,-27.434395 - parent: 2 - proto: ParticleAcceleratorComputerCircuitboard entities: - uid: 5471 @@ -110183,6 +110198,11 @@ entities: parent: 2 - proto: Rack entities: + - uid: 94 + components: + - type: Transform + pos: 15.5,-24.5 + parent: 2 - uid: 1605 components: - type: Transform @@ -110242,6 +110262,11 @@ entities: - type: Transform pos: -40.5,-33.5 parent: 2 + - uid: 2190 + components: + - type: Transform + pos: 17.5,-25.5 + parent: 2 - uid: 3526 components: - type: Transform @@ -110615,6 +110640,11 @@ entities: - type: Transform pos: 3.5,-4.5 parent: 2 + - uid: 22537 + components: + - type: Transform + pos: 17.5,-26.5 + parent: 2 - proto: RadiationCollectorNoTank entities: - uid: 607 @@ -121356,7 +121386,7 @@ entities: - type: Transform pos: -35.5,-42.5 parent: 2 -- proto: SophicScribe +- proto: SophicScribeSpawner entities: - uid: 16214 components: @@ -125063,18 +125093,6 @@ entities: - type: Transform pos: 5.5,-23.5 parent: 2 - - uid: 2189 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 11.5,-27.5 - parent: 2 - - uid: 2190 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 17.5,-27.5 - parent: 2 - uid: 2191 components: - type: Transform From 3dc28cfc2c8f8077c55c6f54b1f5d1064f1d2d7b Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Tue, 17 Sep 2024 23:39:12 +0000 Subject: [PATCH 03/24] Automatic Changelog Update (#883) --- Resources/Changelog/Changelog.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6a8111cec5e..43088dac5c4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6381,3 +6381,16 @@ Entries: id: 6363 time: '2024-09-16T04:41:44.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/919 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Pulling has been reworked. Your character will now try to continuously + push the pulled entity when you use the push keybind. + - type: Remove + message: >- + You can no longer push the pulled entity while walking, and pushing now + follows the momentum conservation laws. + id: 6364 + time: '2024-09-17T23:38:45.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/883 From 0079259a5772b56ed0fc07d2717b54f0a4ebaf92 Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Tue, 17 Sep 2024 23:39:39 +0000 Subject: [PATCH 04/24] Automatic Changelog Update (#927) --- Resources/Changelog/Changelog.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 43088dac5c4..27f4befdd04 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6394,3 +6394,13 @@ Entries: id: 6364 time: '2024-09-17T23:38:45.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/883 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Core now has a 6-Core AME, which is supplied with two jars of fuel. This + should give Engineers significantly more than 20 minutes of time to + setup the Supermatter engine. + id: 6365 + time: '2024-09-17T23:39:07.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/927 From 2f8d9e8e422ff81a4b23d69e46b67207a7cac110 Mon Sep 17 00:00:00 2001 From: Jay Jacobs <84914277+Flybik@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:39:57 +0300 Subject: [PATCH 05/24] Fixes to Bench and Barber Chair Rotation (#925) # Description Changed the textures of wooden and metal benches. Now wooden benches are turned correctly, and metal ones have the correct texture of the left and right halves --- # TODO - [x] Task - [x] Completed Task --- ![Screenshot_80](https://github.com/user-attachments/assets/ff51a698-5387-4b4e-8d2e-4408c2893577) ![Screenshot_81](https://github.com/user-attachments/assets/235f4dbd-c379-45cd-86ea-821a145f0862) ![Screenshot_82](https://github.com/user-attachments/assets/04757bd0-c317-4f3f-91eb-91c1c2d52dec) ![Screenshot_83](https://github.com/user-attachments/assets/dcf006e9-50d6-443f-80b1-c2cce22b1b9a) --- # Changelog :cl: JayJacobs - tweak: Changed the sprite of the barber chair. - fix: Fixed bench textures. Co-authored-by: Flybik --- .../Structures/Specific/bay12barbershop.yml | 2 +- .../Furniture/Benches/pews.rsi/left.png | Bin 1976 -> 1924 bytes .../Furniture/Benches/pews.rsi/right.png | Bin 1999 -> 1897 bytes .../Benches/steel_bench.rsi/left.png | Bin 6061 -> 6022 bytes .../Benches/steel_bench.rsi/right.png | Bin 6022 -> 6061 bytes .../Benches/steel_bench_white.rsi/left.png | Bin 7349 -> 7243 bytes .../Benches/steel_bench_white.rsi/right.png | Bin 7243 -> 7349 bytes .../Specific/barberchair.rsi/barberchair.png | Bin 0 -> 1781 bytes .../Specific/barberchair.rsi/meta.json | 15 +++++++++++++++ .../Specific/barbershop.rsi/barberchair.png | Bin 586 -> 0 bytes .../Specific/barbershop.rsi/meta.json | 3 --- 11 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 Resources/Textures/Structures/Specific/barberchair.rsi/barberchair.png create mode 100644 Resources/Textures/Structures/Specific/barberchair.rsi/meta.json delete mode 100644 Resources/Textures/Structures/Specific/barbershop.rsi/barberchair.png diff --git a/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml b/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml index e66ae347d97..2b0f2a788a6 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml @@ -10,7 +10,7 @@ - type: Anchorable - type: Rotatable - type: Sprite - sprite: Structures/Specific/barbershop.rsi + sprite: Structures/Specific/barberchair.rsi state: barberchair - type: entity diff --git a/Resources/Textures/SimpleStation14/Structures/Furniture/Benches/pews.rsi/left.png b/Resources/Textures/SimpleStation14/Structures/Furniture/Benches/pews.rsi/left.png index 9631b4b17147124ef7fa9e4e4edffcf0f163f337..beb8cd4a0b298f142f8dd562dcf8456b21aa563a 100644 GIT binary patch delta 1888 zcmV-m2cP)34}=epIe#`uL_t(|ob8)!ZyU!E$A7bXca#)Mc4AqUl31zh!cg7XM$w`` z-lTwCqv&@l`f(ZMHf#Wx4j>_<}1+Lo&(+&^PHQHxc3?)HxdcF&fBty6BZhRj~(Yu zdhl@V-`RnMtAB!Mai+2F!%m^fwxHkp_n!q^WSp56Zm%mL7-sEJP5>{M7ruJ|XOGrq z0*KDe_@Pg-`Z93+c0jx!0%k^s{FuQ;Y9tnz2+FNThALcKoU_OecYV>#7iCI(xHvu? zv;Hz=Ha4~mUZ%#k57yg~sj-n8&cHC#00)2kC`R0Hy?r`KO z+Yh2wS%0~sbu`@(-48rf?>DFFxH~>%L?2Am6AG+uD39M1XMizs0z|FoYuIpJ`NLw& zo#$}>Sv^PHDyYpEIoGN7;}aFtY>kWQvaF6Ew;F0DppZLBYI^P}U?dt~B`1SX8OEqC zWenPu(EI&?P`;JJkw)!IL|a)s40P^!nT+BD6MqmZ$jS}sFqYAi*EP8$*A9y`>}Q~G z5(3SX*sD}23MB{Ah?c5(f!ta%6xRUJx8FbY&6Tx+w@#E@?`16Mc%SaM*8up<;{1f2tSJF(q* z^MBFtDOT1tx$`8D2HO{Zn;M&`qGp5=!}QzJ3-)e9n_>5U8Mof#eEp^%qt{{u+tC83 zF(sB~Df2Ti&vRw@?po=*Ya2zHFz;fSpKl+A)7nkm2#TZS8oKcCmVKan_s&tzt~Bg@46v6*23=`45iebJG)kGIsciS|8MSlPa6J zG3kXUa^O7|j|sP51@1oG4$G@g_ieblapT5Lg+|2jP9)4Ej@b#%iAKw0{O=%5)4nPR z?$V`8J6{mzIFbmB#FI>Tel;I6isVI-Bm)-&<2df?yAI&`_3H^V3lYKaK{+!GE`Kt- zuisAgyNjI=>2Jd#fXkOJrvOpZ;9>S7q_DcBMZ_i}w^Mx~P17(r*{G}qyG+bK@7@>_(O=TYuZtNOaB(tO2SDB~2CGezPY5bJGb|S5p3TIv#XA zyKwbRHQVM7&=oR}5$uJzDbLk=?S(tv-vkJC$gBXA0T{i1q8qhB@Nwaz|$=s2{?A39nI)}R4! zWT_%1rNoR)iRv)nq)4m3-d4W&A?NXy;>7sH-0bGQv7=aEH(a}R?NE{rYLMo}Y+}qd zgm))|=~#GYGMd^ucB}}va^=e5)y2efZayZN@H7(7kwi*u*I3$sqJNv*P_xQu(_n3% z}}BxB~_-eBp?q)77RlxC*%ys@{W{qBz%5PckZfMsn;Gm0xmAjnp(um44f8$ zmNP4D#@ctSMOXUE3{E)ZsTSv#wQb|({k2jq$BKXpr{-)T5#s1=CCY9PvRs)q={bY-hQFiIW}smUrwz1mc^fPm5PuN)vK0RYO&APWH;G a0RIK1*Zs(-9p(H00000wg1Z6c@|B}xJXQZNK)6u{WN+uP3U*=HNuaJKQ< zg-&$g{@J_x+nL|a%#JMp;CT3OMuh-S&;jz!&;`X9&IC@yH(v5v%I0!DfTTuazABkGAE5C{OsX3|}KjsVAI;BW@M^kFS+&MGEw z_6hRUeFZCfE`njh)YzbUxU|u75HtV`2i@Z^V736;VsI5_VsqJp$+3%ydLSSmiE5BE z`+Rk~#gLGABh`;L+jNAr7}Qko1W zN0Q1Ct?h#b;P#I{iEqF8N}hLoKuv@#Z2V%c1M{y8Eq@Tcf;|s4kk}9Z`P3{f&Yz9i z#L6vgS_3dWJIY5i&!784T)p6blUJ*?KnSnI05?j@7`e(>74gpZA z6XxE$SbuQF<$nMQCk!1@ID=AC13d3+FkuM*NRRh}PiH887-G7IyCovS0h84j(z=*sjW*mu-0^rL60zOEb{>@#pW)Lhq^k0w?d!6$k*eGdEBMNFQ z@Vf}qpoQYSA^?7{^vx}~peP$7Y3_nD=$kU1P7J9oblfJ+Gx+}3)w*!otMwn_*@QZOFaqw zP{#@MeVldLm6BBm=!3Cg)gSZoNLG&VAnn1!bO6Rt4WglipQaZ>0G@8_SX1K()sG&a z=jNat*}<24L2A;g!NC7yPr^GbAD4C1Lw}P(nc<_J|Gu=|lFk(X&|Bn(1ES+(Zf=fG zPfs^R)pzdPiO;#0UEiea3-!K!x5CmJ&W`~>?Xt=26mJA$Syo?4Re@XIv#f;Bv_C2d~$^b z-@MB^i$Pto*E#mRY5)*yZ6Ujb)lEyvghyPfi$p|ypcg}WfMj>HAsEqssDB(lul~9A zGB)*QBAY-{;Nmz%1c< zR%ek178wA}dE*RN2MK&JkyI&UZ`X^Wapf97&W@+*(^MnNAg&;hJwc|i^56i8Fspb) zfL?k{!)Gc;aL^j%;`{Z|XMbc32nHcqZUPen0I9UVrObyO5#;Tn2${ex9vQzr2D0TU}7k_ z&zXXa16)qlf^t>5?y*kqW@#A!u^2zsYx!km^Qi*_LYx=?rD^$&kbg1&c$KpG)L%s4 zC)VeV8azoE0Mf54nG;5H>s6s|aM#wN-x>a~i1zq_jBm@Ls`i%aP=SwcME;HcVq8-{>NdfntN9&YH b6ZjYC()tGM^A6S)00000NkvXXu0mjf?02AJ diff --git a/Resources/Textures/SimpleStation14/Structures/Furniture/Benches/pews.rsi/right.png b/Resources/Textures/SimpleStation14/Structures/Furniture/Benches/pews.rsi/right.png index ea0922930feeae3e2d339219736dbea2bb32d6b4..c1f2949a71932dfb13691245550c05ef1949ecc0 100644 GIT binary patch delta 1861 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 XuM(MODQ_T(00000NkvXXu0mjfLV}!8 diff --git a/Resources/Textures/SimpleStation14/Structures/Furniture/Benches/steel_bench.rsi/right.png b/Resources/Textures/SimpleStation14/Structures/Furniture/Benches/steel_bench.rsi/right.png index b6c1ef00fcb980741e21dddf4fdccbfce1c18b58..260c7298f776908f184289f4075028adae1d8e7c 100644 GIT binary patch delta 6045 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 XuM(MODQ_T(00000NkvXXu0mjfLV}!8 delta 5991 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 RixU6<002ovPDHLkV1gY^#RC8U delta 7290 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 RixU6<002ovPDHLkV1gY^#RC8U diff --git a/Resources/Textures/Structures/Specific/barberchair.rsi/barberchair.png b/Resources/Textures/Structures/Specific/barberchair.rsi/barberchair.png new file mode 100644 index 0000000000000000000000000000000000000000..244a875dca497cf2e0f2bbe1483ab527bd7b4c74 GIT binary patch literal 1781 zcmVPx#1ZP1_K>z@;j|==^1pojBg-Jv~RCr$Pn@wmPRTO}4{E4P58ntN_NwZNG(jeG^ zZ)HgZYeia=px~xO5L(og6|!@YUBFEiY9nsiEW}EQw3ZYEjU}76sM)0G)>0QH{v7Y5atxpbiSlrdWhx zG#bis0LDKTG<;y|vyW7b-vhCoPT^F@6x3hB>$7CIrCFNxF$*c!B_J@=Z6 z2)5C7`13sZ)&pJ+5X4~HXhhO%%5s3+-Try(19RqP?ybgsJ6{s14%lK0;a9asU_S=m z10q)fWg-$SlCLO9)edau+s~%u6VfBV7{o8`D69}94Q--W6= zy}x3f3W-K!9H|ZfjG|FNjs>GZk;7XXGQ6cB;)F5~LwKh|8!+M%fm-k!BDza)07J?P z!H3V~;A4Y2#b>Y#gyKekobx>SqT}+cymLO#AAD@>x<7QR%mIKs7XTawf~X^n=gCL@ z@NPV*5nvk;HP(gn21Sh*FyiT;dlTLAevbUwQXaXF@Vt-lkob^%sL-Dh=Az7n+@oML zmQsK+K8F7I?P7vvH;-=L>!shT4FKE2cs?r8U!-gve`0s6^z}Vim&dmEnjC(3IpB?x zqp@9}Ez%Y8d@ER~{Z*H1*M6zX%B>r9$%AF=gIS|0sg5il|`qP;&jIH<Up z$HvC=yUU1xsQ$&pMRoP+RecYxU%#%8dGx0`0Od!I9#zZB%j)})cXc{_a*q`|j0u=1 zg@I$o^>KD~Ru`hE18n^;H#et`F1owB&6&S`uPQJxF=5Uc#xv9v;hJ;-ut+9+&WgGO7TGejl>vcRIkgQ0s{}M|k%F8@e;5(;wr;}!f z(*b6vP^x>l>zI>^Y;B(FQ9{2VKl ziqj5f;@c0YH8w$Qlt%DOO{tG>WM~AL+S*7Y*^}4{Y=lNIS1$mqn%rzRX?DaqK%gG2 zw-KS#e;*PfU?X&cX%XOT8JQ1YF+^s6XhrD>E)RzrC2Z`y1G~<&zPF$Z=JdA4jj~V% zB7j~@rs^07<(?0aC}6h-YksmSD?(abtirVd-f!!3l|^=?_(pGP1hCOzdZjTIa1D;y zJzz>J0};R`ZogJ(nr3Dp9j1Xp}hKA#v=-xzZwyMM?ZYNqP-`*053lILi0uh zR~nQ#dUW+dX=On_QT+b5RYhdPML?nh$SC0iJI+dc(n=!(gq3WNK!A-CjJiZxf&G5;fn5#>cPT67 zru@<_#1R_-0*jKn_Z-yczb>q*-W_I(hQXBvKU_Mek44rBWkFQ*+oY8R{kHDRRlp|i z1_kmJb%4CbBzY6nn|mApyv!C^okjK^c&}+?R>Wj2h3LD{hh8Z|5&d2caIw0&s)hBs zBxuBPN$+Rd;o9tx{1MAn~Xw;JZ~zR7FdSs9z?pXo7-bOzgn%c1JE;g zp7+c|AOOC&TCLy`g`E*vg2`k8!*m)N*50oV+1i?PzYyhff9|&sOLx>885uFhM ziqwP%0r)ru5tZQ%#t@ttK$hhMPQpUKcrCd0i(~rny>`3pd3!PeVCVLFJ#xUqbP9X? z`VbB0bFy-yk?pJ1YQD1|Gz0YI{ow!~UZdC{>;o>9BDS&Hk=5_##|%I<%;n(mxcWhm z`ELb9ag_?wIA#XGn@zFoZnsOyG9Hh~9viS&ECSw#olfWU#c|#RZZ(@t5~Sbnn}flC z`0T(Db=`MCkgyE1*^Jm6$1(o_xflS`j)AfMrBccEQwX%9fJ6qM!BNg8JXetcXamNj Y9|80Cs(CLv=l}o!07*qoM6N<$g6bFx`Tzg` diff --git a/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json b/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json index 1226428bcfb..6d57cc380d5 100644 --- a/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json +++ b/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json @@ -18,9 +18,6 @@ ] ] }, - { - "name": "barberchair" - }, { "name": "dyedispenser" }, From 34d9e0d60cf33823ae425fbb3c5a4c2bedeb7c5e Mon Sep 17 00:00:00 2001 From: zelezniciar1 <39102800+zelezniciar1@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:40:13 +0000 Subject: [PATCH 06/24] Re-Adds Fireaxe Prying (#921) # Description - Makes the fireaxe once again able to perform its primary function. - There is now a way to emergency space a room without using an RCD. - Viva Atmosia. --- # TODO [x] Return Fireaxe Prying ---

Media

--- # Changelog :cl: zelezniciar - tweak: The fireaxe once again can pry subfloors Co-authored-by: SsalamethVersaach --- Resources/Locale/en-US/tools/tool-qualities.ftl | 5 ++++- .../Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml | 1 + Resources/Prototypes/Tiles/plating.yml | 6 ++++++ Resources/Prototypes/tool_qualities.yml | 7 +++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/tools/tool-qualities.ftl b/Resources/Locale/en-US/tools/tool-qualities.ftl index 14e42390a76..2482dca5174 100644 --- a/Resources/Locale/en-US/tools/tool-qualities.ftl +++ b/Resources/Locale/en-US/tools/tool-qualities.ftl @@ -32,4 +32,7 @@ tool-quality-rolling-name = Rolling tool-quality-rolling-tool-name = Rolling Pin tool-quality-digging-name = Digging -tool-quality-digging-tool-name = Shovel \ No newline at end of file +tool-quality-digging-tool-name = Shovel + +tool-quality-axing-name = Axing +tool-quality-axing-tool-name = Fireaxe \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml index b30a2855796..6630a22ea7d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml @@ -43,6 +43,7 @@ - type: Tool qualities: - Prying + - Axing - type: ToolTileCompatible - type: Prying - type: UseDelay diff --git a/Resources/Prototypes/Tiles/plating.yml b/Resources/Prototypes/Tiles/plating.yml index 7edb1ae784c..0a74ef9fd43 100644 --- a/Resources/Prototypes/Tiles/plating.yml +++ b/Resources/Prototypes/Tiles/plating.yml @@ -4,6 +4,7 @@ sprite: /Textures/Tiles/plating.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -20,6 +21,7 @@ - 1.0 baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -31,6 +33,7 @@ sprite: /Textures/Tiles/plating_burnt.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -42,6 +45,7 @@ sprite: /Textures/Tiles/Asteroid/asteroid_plating.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -53,6 +57,7 @@ sprite: /Textures/Tiles/Misc/clockwork/clockwork_floor.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -64,6 +69,7 @@ sprite: /Textures/Tiles/snow_plating.png #Not in the snow planet RSI because it doesn't have any metadata. Should probably be moved to its own folder later. baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.15 #a little less then actual snow diff --git a/Resources/Prototypes/tool_qualities.yml b/Resources/Prototypes/tool_qualities.yml index ff55d9fcf14..4508ea94b71 100644 --- a/Resources/Prototypes/tool_qualities.yml +++ b/Resources/Prototypes/tool_qualities.yml @@ -67,3 +67,10 @@ toolName: tool-quality-rolling-tool-name spawn: RollingPin icon: { sprite: Objects/Tools/rolling_pin.rsi, state: icon } + +- type: tool + id: Axing + name: tool-quality-axing-name + toolName: tool-quality-axing-tool-name + spawn: FireAxe + icon: { sprite: Objects/Weapons/Melee/fireaxe.rsi, state: icon } From ff618e9a6444d1224381ae2d854c359f3d53b6e1 Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Tue, 17 Sep 2024 23:40:23 +0000 Subject: [PATCH 07/24] Automatic Changelog Update (#925) --- Resources/Changelog/Changelog.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 27f4befdd04..66a257b98b2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6404,3 +6404,12 @@ Entries: id: 6365 time: '2024-09-17T23:39:07.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/927 +- author: JayJacobs + changes: + - type: Tweak + message: Changed the sprite of the barber chair. + - type: Fix + message: Fixed bench textures. + id: 6366 + time: '2024-09-17T23:39:57.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/925 From f5275e295deb97ee4e5e07f547b869ae19361bd5 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 19:40:34 -0400 Subject: [PATCH 08/24] 9 More Traits (#918) # Description This "ZERO C#" Pr adds 10 additional traits to the game, which only make use of pre-existing components, along with some of the new TraitSystem functionality. These are the following new traits: - **Photophobia**: Functions as per Vulpkanin Light Sensitivity - **Clumsy**: Functions as per Clown clumsiness - **Small**: Functions as per Felinid PseudoItem, requires a character be Felinid sized - **Temperature Tolerance**: Functions as per Vulpkanin low temperature resistance - **Spinarette**: Functions as per the same ability from Arachnids. - **Talons**: Replaces your melee natural attacks with Piercing talons - **Claws**: Replaces your melee natural attacks with Slashing claws - **Striking Calluses**: [Human only, requires Martial Artist or Boxer job], increases your base unarmed attack to 6 damage, from 5. - **Natural Weapon Removal**: For species with melee attacks other than blunt, replaces Talons or Claws with Fist. ## TODO: - [ ] Adjust the points costs/additions for these traits to ensure balance. Although I can't imagine most of them being changed. I do still wish for a decent number of traits to be within the 1 to 3 point range.

Media

![image](https://github.com/user-attachments/assets/c2e7a42b-2b1f-43ea-8f0d-5275c06c51ae) ![image](https://github.com/user-attachments/assets/bb60ffbe-0755-483b-b3a4-028764b465a7)

# Changelog :cl: - add: 10 new Traits have been added to the game. Photophobia, Clumsy, Small, Temperature Tolerance, Claws, Talons, Natural Weapon Removal, Striking Calluses, and Spinarette. --------- Signed-off-by: VMSolidus Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- Resources/Locale/en-US/traits/traits.ftl | 64 ++++++- Resources/Prototypes/Traits/disabilities.yml | 38 +++++ Resources/Prototypes/Traits/physical.yml | 170 +++++++++++++++++++ 3 files changed, 271 insertions(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index ff329a12a71..b034f69b9e8 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -228,6 +228,68 @@ trait-description-AnomalousPositronics = your positronic brain lacks its standard psionic insulation. As a being that can be argued to have a soul, this by extension means that it is possible for you to be influenced by the Noosphere. +trait-name-Photophobia = Photophobia +trait-description-Photophobia = + Your eyes are extremely sensitive to bright lights. + As a result, you may be blinded for a greater duration than others when exposed to sudden flashes of light. + Your eyes are also more likely to be injured by flashes. + +trait-name-Clumsy = Clumsy +trait-description-Clumsy = + You have a severe deficiency in hand-eye-coordination, resulting in an inability to do some things that others would take for granted. + Any weapons you may try to use are more likely to injure yourself than others. You are unable to climb any objects without injuring yourself. + +trait-name-Small = Small +trait-description-Small = + You are much smaller than a typical person, and can climb into spaces others would not normally be able to fit into, such as duffel bags. + This trait does not in any way modify your character's size, it merely requires that your character be at most the size of a standard Felinid. + +trait-name-TemperatureTolerance = Temperature Tolerance +trait-description-TemperatureTolerance = + You have a notable tolerance for lower temperatures. You can stand for extended periods of time + in conditions just slightly below freezing, such as the inside of a kitchen fridge, + or the sunlit mountainside of the famous Glacier station. + +trait-name-Talons = Talons +trait-description-Talons = + Your fingertips have been replaced with piercing talons. + These could come from gene modifications, vatgrown implants, + or even hard plastic retractable talons incorpoated into a prosthetic limb. + Your unarmed melee attacks deal Piercing damage instead of the standard damage type for your species. + This has no effect on damage dealt with any form of armed melee. + +trait-name-Claws = Claws +trait-description-Claws = + Your fingertips have been replaced with sharp claws. + These could come from gene modifications, vatgrown implants, + or even hard plastic retractable claws incorpoated into a prosthetic limb. + Your unarmed melee attacks deal Slashing damage instead of the standard damage type for your species. + This has no effect on damage dealt with any form of armed melee. + +trait-name-NaturalWeaponRemoval = Natural Weapons Removal +trait-description-NaturalWeaponRemoval = + Whatever "Natural Weapons" your species are normally born with have been surgically removed. + This could have been done to better fit in with terran space stations, or as a cosmetic choice. + As a result, your unarmed attacks deal Blunt damage instead of the standard damage type for your species. + This has no effect on damage dealt with any form of armed melee. + +trait-name-StrikingCalluses = Striking Calluses +trait-description-StrikingCalluses = + An iconic enhancement commonly found in the world of cyberenhanced martial arts. + Striking Calluses consist of bony dermal deposits grafted into a user's hands, either inside the palm + for "Tiger Style" fighting, or just below the knuckles for those who favor traditional boxing. + Owners of prosthetic or bionic limbs would instead have a hard plastic shell over their knuckles. + These enhancements increase your unarmed strike damage by 1 point base, but do not confer + any benefits to any form of armed melee. + +trait-name-Spinarette = Bionic Spinarette +trait-description-Spinarette = + This vatgrown organ-- trademarked and patented by the Cybersun Corporation, is marketed as a highly + utilitarian enhancement, and sold in clinics all across known space. It consists of a nodule that is traditionally + implanted right below the wrist, which absorbs bodily lipids to convert into all natural silk. A small opening + in the palm allows the user to 'spin' this thread. Users of this enhancement typically require twice as much food + as a standard Sol Human, owing to the high metabolic cost of artificial Sericulture. + trait-name-AddictionNicotine = Nicotine Addiction trait-description-AddictionNicotine = - You have an addiction to Nicotine, and will require frequent smoke breaks to keep your mood in check. \ No newline at end of file + You have an addiction to Nicotine, and will require frequent smoke breaks to keep your mood in check. diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml index c24e8927693..3afc3bfba43 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -191,3 +191,41 @@ damageModifiers: coefficients: Blunt: 1.1 + +- type: trait + id: Photophobia + category: Visual + points: 1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Vulpkanin # This trait functions exactly as-is for the Vulpkanin trait. + components: + - type: Flashable + eyeDamageChance: 0.3 + eyeDamage: 1 + durationMultiplier: 1.5 + +- type: trait + id: Clumsy + category: Physical + points: 1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Clown # This trait functions exactly as is for the Clown's trait. + - !type:CharacterDepartmentRequirement + inverted: true + departments: + - Command # Because I know for a fact people will play Captain and grief with their inability to fight back. + - Security # Because I know for a fact people will play Security and grief with their inability to use guns. + components: + - type: Clumsy + clumsyDamage: + types: + Blunt: 5 + Piercing: 4 + groups: + Burn: 3 diff --git a/Resources/Prototypes/Traits/physical.yml b/Resources/Prototypes/Traits/physical.yml index cfc15d4ee25..8debf7ffbfb 100644 --- a/Resources/Prototypes/Traits/physical.yml +++ b/Resources/Prototypes/Traits/physical.yml @@ -231,3 +231,173 @@ Blunt: 1.5 Slash: 1.5 Piercing: 1.5 + +- type: trait + id: Small + category: Physical + points: -2 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Felinid # Felinids already have this feature by default. + - !type:CharacterHeightRequirement + max: 150 + - !type:CharacterWidthRequirement + max: 32 + components: + - type: PseudoItem + storedOffset: 0,17 + shape: + - 0,0,1,4 + - 0,2,3,4 + - 4,0,5,4 + +- type: trait + id: TemperatureTolerance + category: Physical + points: -1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Vulpkanin # This trait functions exactly as-is for the Vulpkanin trait. + components: + - type: TemperatureProtection + coefficient: 0.1 # Enough resistance to walk into the chef's freezer, or tolerate daytime temperatures on Glacier without a jacket. + +# These traits largely exist to demonstrate more of the "Component Removals" functionality. This way contributors +# can get used to seeing that they can "Remove and Replace" a pre-existing component. +# When declared, componentRemovals work like a "RemComp" that activates upon joining a round. +- type: trait + id: Talons + category: Physical + points: -1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Harpy # Harpies already have talons + - Arachnid # Apparently they have a "piercing" bite + - !type:CharacterTraitRequirement + inverted: true + traits: + - Claws + componentRemovals: + - MeleeWeapon # Remove the original innate melee attack, so that it can be replaced with a new one. + components: + - type: MeleeWeapon + soundHit: + collection: AlienClaw + animation: WeaponArcClaw + damage: + types: + Piercing: 5 # No, this isn't "OP", this is literally the worst brute damage type in the game. + # Same deal as Slash, except that a majority of all armor provides Piercing resistance. + +- type: trait + id: Claws + category: Physical + points: -1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Felinid # Felinids already have cat claws. + - Reptilian # Reptilians also have cat claws. + # - Vulpkanin # Vulpkanin have "Blunt" claws. One could argue this trait "Sharpens" their claws. + - !type:CharacterTraitRequirement + inverted: true + traits: + - Talons + componentRemovals: + - MeleeWeapon # Remove the original innate melee attack, so that it can be replaced with a new one. + components: + - type: MeleeWeapon + soundHit: + collection: AlienClaw + angle: 30 + animation: WeaponArcClaw + damage: + types: + Slash: 5 # Trade stamina damage on hit for a very minor amount of extra bleed. + # Blunt also deals bleed damage, so this is more of a sidegrade. + +- type: trait + id: NaturalWeaponRemoval + category: Physical + points: 0 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Human + - Oni + - SlimePerson + - !type:CharacterTraitRequirement + inverted: true + traits: + - Talons + - Claws + componentRemovals: + - MeleeWeapon # Remove the original innate melee attack, so that it can be replaced with a new one. + components: + - type: MeleeWeapon + soundHit: + collection: Punch + angle: 30 + animation: WeaponArcFist + damage: + types: + Blunt: 5 + +- type: trait + id: StrikingCalluses + category: Physical + points: -4 + requirements: + - !type:CharacterSpeciesRequirement + species: + - Human # Entirely arbitrary, I've decided I want a trait unique to humans. Since they don't normally get anything exciting. + # When we get the Character Records system in, I also want to make this require certain Backgrounds. + - !type:CharacterTraitRequirement + inverted: true + traits: + - Claws + - Talons + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - MartialArtist + - !type:CharacterJobRequirement + jobs: + - Boxer + componentRemovals: + - MeleeWeapon + components: + - type: MeleeWeapon + soundHit: + collection: Punch + angle: 30 + animation: WeaponArcFist + damage: + types: + Blunt: 6 + +- type: trait + id: Spinarette + category: Physical + points: -4 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Arachnid + - Arachne + components: + - type: Sericulture + action: ActionSericulture + productionLength: 2 + entityProduced: MaterialWebSilk1 + hungerCost: 4 From 3dd57ca04aa3b8efeca47819ad2f03be488e153d Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 19:41:14 -0400 Subject: [PATCH 09/24] Open Market Tacsuits (#845) # Description I've spoken with this subject with quite a few people so far, that there's some inherent issues with the game's setting as regards to corporations, and people readily identifying certain companies as being "Syndicate Companies". It makes absolutely no sense for things like that to be completely public knowledge that X-Company is explicitly antagonistic, when publicly they would just be companies that sell products to an open market-- an open market that NanoTrasen buys from. And while we've been doing this lately with adding non-NT company names to the names and descriptions of hardsuits, one thing has been bugging me and other people for awhile. "If the Cybersun tacsuits are meant to be something that the Nukies literally bought on the open market and painted red. Why can't the station also purchase these suits from the same market? If a manufacturer of these suits notably declined to sell its products to another company, it would be an extremely obvious tell that they were overtly hostile to that corporation. It makes more sense if they openly sell said products at an inflated cost, so that they can put up public appearances of neutrality". This also brings to mind that the Reverse Engineering Machine, in it's current rendition, is extremely problematic for establishing the game as a serious setting, given that it currently functions as what is essentially "Press button to commit industrial espionage". Having talked with its original creator, Rane also agreed that- especially in the context of the "Blukie Suits", this presents an issue for the game's setting presentation. It's a plot hole that makes very little sense. So, what this PR does, is make it so that station Logistics can purchase crates containing CSA branded Tacsuits, painted in the blue, at a massively overinflated cost. These crates come with Security locks, meaning that they would be bought for use solely by the station's security. Additionally, this removes the ability for the Reverse Engineering machine to violate the intellectual property of other corporations, which in-universe would be something highly illegal under a truly neutral space law. I actually also believe that other hardsuits and tacsuits, including ones purchased from Five-Points-Armory, Hephaeustus, etc, should all be purchasable from cargo. Technically, any weapon, hardsuit, or armor, should also be purchasable, but making it so that cargo can order a Sec-Crate with a C-20r in it would be extremely controversial, and is best left to a separate PR. # Changelog :cl: - add: CSA-51a "Shanlin" and CSA-80UA "Guan-Yu" Tacsuits can now be purchased from Logistics for use by station security personnel, at an extremely overinflated price. These suits come prepainted in blue. - remove: Reverse Engineering machines can no longer violate the intellectual property rights of publicly traded corporations. This means that Cybersun Tacsuits; regardless of where they were obtained from or what color they were painted, can no longer be reverse engineered. --------- Signed-off-by: VMSolidus Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- .../clothing/head/hardsuit-helmets.ftl | 4 ++++ .../clothing/outerClothing/hardsuits.ftl | 6 ++++++ Resources/Maps/glacier.yml | 2 -- .../Catalog/Cargo/cargo_security.yml | 20 ++++++++++++++++++ .../Catalog/Fills/Crates/security.yml | 20 ++++++++++++++++++ .../Clothing/OuterClothing/hardsuits.yml | 10 --------- .../Entities/Structures/Machines/lathe.yml | 2 -- .../Clothing/OuterClothing/hardsuits.yml | 4 ---- .../Nyanotrasen/Recipes/Lathes/hardsuits.yml | 21 ------------------- 9 files changed, 50 insertions(+), 39 deletions(-) delete mode 100644 Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml diff --git a/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl b/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl index 2bbacf2abca..4e003d2704d 100644 --- a/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl +++ b/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl @@ -55,6 +55,8 @@ ent-ClothingHeadHelmetHardsuitLuxury = HpI-20c helmet .desc = A modified helmet for the Minos hardsuit, fashioned after the Logistics Officer's colors. It's been modified for greater mobility at the expense of physical trauma protection. ent-ClothingHeadHelmetHardsuitSyndie = CSA-51a helmet .desc = An armored helmet deployed over a Shanlin tacsuit. This one has been painted blood red. +ent-ClothingHeadHelmetHardsuitSyndieReverseEngineered = CSA-51a helmet + .desc = An armored helmet deployed over a Shanlin tacsuit. This one has been painted blue. ent-ClothingHeadHelmetHardsuitSyndieMedic = CSA-51m helmet .desc = An armored helmet deployed over a Zhongyao tacsuit. features optic integrations for nearly every medical hud on the market. Designed to enable the survival of combat medics in the most dangerous of environments. @@ -64,6 +66,8 @@ ent-ClothingHeadHelmetHardsuitSyndieCommander = CSA-54c helmet .desc = A bulked up version of the Shanlin tacsuit's helmet, purpose-built for commanders of special operation squads. This one has been painted blood-red. ent-ClothingHeadHelmetHardsuitCybersun = CSA-80UA helmet .desc = An incredibly sturdy looking helmet designed for the Guan Yu tacsuit. +ent-ClothingHeadHelmetHardsuitJuggernautReverseEngineered = CSA-80UA helmet + .desc = An incredibly sturdy looking helmet designed for the Guan Yu tacsuit. This one has been painted blue. ent-ClothingHeadHelmetHardsuitWizard = WZD-84 helmet .desc = A bizarre, gem-encrusted helmet from unknown origins. It provides some protection to its wearer without restricting their movements. ent-ClothingHeadHelmetHardsuitLing = organic space helmet diff --git a/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl b/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl index 84f03af5c92..188f08ae260 100644 --- a/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl +++ b/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl @@ -74,6 +74,9 @@ ent-ClothingOuterHardsuitLuxury = HpI-20c - "Minos" hardsuit ent-ClothingOuterHardsuitSyndie = CSA-51a - "Shanlin" tacsuit .desc = A tactical combat hardsuit produced by the Cybersun-Armaments Corporation, the suit's tags indicate it provides moderate protection against most forms of damage. This one has been painted blood red. It feels incredibly light. +ent-ClothingOuterHardsuitSyndieReverseEngineered = CSA-51a - "Shanlin" tacsuit + .desc = A tactical combat hardsuit produced by the Cybersun-Armaments Corporation, the suit's tags indicate it provides moderate protection against most forms of damage. + This one has been painted blue. It feels incredibly light. ent-ClothingOuterHardsuitSyndieMedic = CSA-51m - "Zhongyao" tacsuit .desc = A tactical combat hardsuit produced by the Cybersun-Armaments Corporation, the suit's tags indicate it provides moderate protection against most forms of damage. Half of the suit is painted blood red, the rest bears galactic-standard medical markings. It feels incredibly light. @@ -87,6 +90,9 @@ ent-ClothingOuterHardsuitSyndieCommander = CSA-54c - "Tianming" tacsuit ent-ClothingOuterHardsuitJuggernaut = CSA-80UA - "Guan Yu" tacsuit .desc = The pride and joy of the Cybersun-Armaments Corporation, named after an ancient Sol' War God. Commonly known throughout the galaxy as a "Juggernaut". Matching its bulky appearance, it protects against all forms of damage. It feels VERY heavy. +end-ClothingOuterHardsuitJuggernautReverseEngineered = CSA-80UA - "Guan Yu" tacsuit + .desc = The pride and joy of the Cybersun-Armaments Corporation, named after an ancient Sol' War God. Commonly known throughout the galaxy as a "Juggernaut". + Matching its bulky appearance, it protects against all forms of damage. It feels VERY heavy. ent-ClothingOuterHardsuitWizard = WZD-84 - "Mana" tacsuit .desc = A bizarre gem-encrusted hardsuit. Famously used by members of the Wizard Federation in their operations. Contrary to it's appearance, it can protect its wearer from space and considerable amounts of physical trauma, it feels somewhat light. diff --git a/Resources/Maps/glacier.yml b/Resources/Maps/glacier.yml index 2cba2930f0e..5891d0ca9b6 100644 --- a/Resources/Maps/glacier.yml +++ b/Resources/Maps/glacier.yml @@ -52876,8 +52876,6 @@ entities: - HamtrLLeg - HamtrRLeg - VimHarness - - ClothingOuterHardsuitJuggernautReverseEngineered - - ClothingOuterHardsuitSyndieReverseEngineered - JetpackBlue - JetpackMini - proto: ExtinguisherCabinetFilled diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_security.yml b/Resources/Prototypes/Catalog/Cargo/cargo_security.yml index 2ad7628ddb3..daa5e95777b 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_security.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_security.yml @@ -77,3 +77,23 @@ cost: 1000 category: cargoproduct-category-name-security group: market + +- type: cargoProduct + id: SecurityShanlinTacsuit + icon: + sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/syndicate.rsi + state: icon + product: CrateSecurityShanlinTacsuit + cost: 17500 + category: cargoproduct-category-name-security + group: market + +- type: cargoProduct + id: SecurityGuanYuTacsuit + icon: + sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/juggernaut.rsi + state: icon + product: CrateSecurityGuanYuTacsuit + cost: 30000 + category: cargoproduct-category-name-security + group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/security.yml b/Resources/Prototypes/Catalog/Fills/Crates/security.yml index 592c1535083..b1ef5eb0d1c 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/security.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/security.yml @@ -113,4 +113,24 @@ - id: TrackingImplanter amount: 4 +- type: entity + id: CrateSecurityShanlinTacsuit + parent: CrateSecgear + name: shanlin tacsuit crate + description: Contains a single CSA-51a Shanlin tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitSyndieReverseEngineered + +- type: entity + id: CrateSecurityGuanYuTacsuit + parent: CrateSecgear + name: shanlin tacsuit crate + description: Contains a single CSA-80UA Guan-Yu tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitJuggernautReverseEngineered + # Cosmetic Crates diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index ba889285a16..6a0f445fc94 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -61,11 +61,6 @@ - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitAtmos - - type: ReverseEngineering # Nyano - difficulty: 5 - newItem: ClothingOuterHardsuitJuggernautReverseEngineered - recipes: - - ClothingOuterHardsuitJuggernautReverseEngineered #Engineering Hardsuit - type: entity @@ -557,11 +552,6 @@ - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitSyndie - - type: ReverseEngineering # Nyano - difficulty: 5 - newItem: ClothingOuterHardsuitSyndieReverseEngineered - recipes: - - ClothingOuterHardsuitSyndieReverseEngineered - type: Tag tags: - MonkeyWearable diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 2d4bc9e0d8f..e5a9cb35417 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -628,8 +628,6 @@ - HamtrRLeg - VimHarness # Begin Nyano additions - - ClothingOuterHardsuitJuggernautReverseEngineered - - ClothingOuterHardsuitSyndieReverseEngineered - JetpackBlue - JetpackMini # End Nyano additions diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml index 6498eda075e..4e988c27406 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml @@ -19,8 +19,6 @@ sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/syndicate.rsi - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitSyndieReverseEngineered - - type: ReverseEngineering - newItem: null - type: entity parent: ClothingOuterHardsuitJuggernaut @@ -35,8 +33,6 @@ sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/juggernaut.rsi - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitJuggernautReverseEngineered - - type: ReverseEngineering - newItem: null - type: entity parent: ClothingOuterHardsuitERTLeader diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml deleted file mode 100644 index 560c03db49c..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml +++ /dev/null @@ -1,21 +0,0 @@ -- type: latheRecipe - id: ClothingOuterHardsuitSyndieReverseEngineered - result: ClothingOuterHardsuitSyndieReverseEngineered - completetime: 25 - materials: - Steel: 6000 - Glass: 1500 - Uranium: 100 - Plastic: 100 - Gold: 200 - -- type: latheRecipe - id: ClothingOuterHardsuitJuggernautReverseEngineered - result: ClothingOuterHardsuitJuggernautReverseEngineered - completetime: 25 - materials: - Steel: 12000 - Glass: 3000 - Uranium: 500 - Plastic: 500 - Gold: 250 From 9e293ea531cac81779457e90a6841784c2c96cc6 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 19:41:27 -0400 Subject: [PATCH 10/24] Psionic Power Rolling Rework (#830) # Description This PR implements a long overdue rework of the underlying system for actually giving Psions their powers, by making it a semi-deterministic system that favors people having a small handful of powers, unless said person has inherent modifiers that let them generate them more consistently. A common complaint I received from the new Psionic System is that stations tended to become "Space Hogwarts" given enough time, since people could very easily game the system to consistently get new powers every 20 minutes, until a majority of the station was throwing fireballs at each other. Now, obtaining new powers gets more difficult the more powers you have, exponentially so. Powers can also declare in their prototype how many "Slots" they occupy, meaning that certain powers can make this even more difficult, or not at all, as desired. # Changelog :cl: VMSolidus & Rane - add: Psionic Power "Rerolls" have been reworked. Psions now have a stat called Potentia, which is increased by sources of Psionic Rerolls such as Space Drugs, Loto Oil, and Random Events. Instead of being an all-or-nothing "D100 with modifiers" roll, whatever you roll is added to your Potentia stat, and when Potentia reaches a certain threshold, it is automatically "Spent" to buy a random new power. Your first power costs 100 Potentia, your 2nd power costs 200, 3rd costs 400, and so on. The more powers you have, the more difficult it is to obtain new ones. - tweak: Some powers, such as Telepathy and Xenoglossy, do not count as a power for the purpose of Potentia Cost --------- Signed-off-by: VMSolidus --- .../Psionics/PsionicAbilitiesSystem.cs | 24 ++ Content.Server/Psionics/PsionicsSystem.cs | 325 ++++++++++++------ .../StationEvents/Events/NoosphericZapRule.cs | 4 +- Content.Shared/Psionics/PsionicComponent.cs | 28 +- .../Psionics/PsionicPowerPrototype.cs | 6 + .../Locale/en-US/psionics/psionic-powers.ftl | 4 +- .../Roles/Jobs/Epistemics/forensicmantis.yml | 1 + Resources/Prototypes/Psionics/psionics.yml | 3 + .../Roles/Jobs/Science/research_director.yml | 1 + 9 files changed, 278 insertions(+), 118 deletions(-) diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index 32e51d3c101..536235b6d63 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -12,6 +12,7 @@ using System.Linq; using Robust.Server.Player; using Content.Server.Chat.Managers; +using Content.Server.Psionics.Glimmer; namespace Content.Server.Abilities.Psionics { @@ -122,6 +123,8 @@ public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, P AddPsionicStatSources(proto, psionic); RefreshPsionicModifiers(uid, psionic); SendFeedbackMessage(uid, proto, playFeedback); + UpdatePowerSlots(psionic); + //UpdatePsionicDanger(uid, psionic); // TODO: After Glimmer Refactor //SendFeedbackAudio(uid, proto, playPopup); // TODO: This one is coming next! } @@ -297,6 +300,27 @@ private void SendFeedbackMessage(EntityUid uid, PsionicPowerPrototype proto, boo session.Channel); } + private void UpdatePowerSlots(PsionicComponent psionic) + { + var slotsUsed = 0; + foreach (var power in psionic.ActivePowers) + slotsUsed += power.PowerSlotCost; + + psionic.PowerSlotsTaken = slotsUsed; + } + + /// + /// Psions over a certain power threshold become a glimmer source. This cannot be fully implemented until after I rework Glimmer + /// + //private void UpdatePsionicDanger(EntityUid uid, PsionicComponent psionic) + //{ + // if (psionic.PowerSlotsTaken <= psionic.PowerSlots) + // return; + // + // EnsureComp(uid, out var glimmerSource); + // glimmerSource.SecondsPerGlimmer = 10 / (psionic.PowerSlotsTaken - psionic.PowerSlots); + //} + /// /// Remove all Psychic Actions listed in an entity's Psionic Component. Unfortunately, removing actions associated with a specific Power Prototype is not supported. /// diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index 23cf6aeb80b..0d05000a3cd 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -11,139 +11,242 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Random; +using Content.Shared.Popups; +using Content.Shared.Chat; +using Robust.Server.Player; +using Content.Server.Chat.Managers; +using Robust.Shared.Prototypes; +using Content.Shared.Psionics; -namespace Content.Server.Psionics +namespace Content.Server.Psionics; + +public sealed class PsionicsSystem : EntitySystem { - public sealed class PsionicsSystem : EntitySystem + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + private const string BaselineAmplification = "Baseline Amplification"; + private const string BaselineDampening = "Baseline Dampening"; + + // Yes these are a mirror of what's normally default datafields on the PsionicPowerPrototype. + // We haven't generated a prototype yet, and I'm not going to duplicate them on the PsionicComponent. + private const string PsionicRollFailedMessage = "psionic-roll-failed"; + private const string PsionicRollFailedColor = "#8A00C2"; + private const int PsionicRollFailedFontSize = 12; + private const ChatChannel PsionicRollFailedChatChannel = ChatChannel.Emotes; + + /// + /// Unfortunately, since spawning as a normal role and anything else is so different, + /// this is the only way to unify them, for now at least. + /// + Queue<(PsionicComponent component, EntityUid uid)> _rollers = new(); + public override void Update(float frameTime) { - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - - private const string BaselineAmplification = "Baseline Amplification"; - private const string BaselineDampening = "Baseline Dampening"; - - /// - /// Unfortunately, since spawning as a normal role and anything else is so different, - /// this is the only way to unify them, for now at least. - /// - Queue<(PsionicComponent component, EntityUid uid)> _rollers = new(); - public override void Update(float frameTime) - { - base.Update(frameTime); - foreach (var roller in _rollers) - RollPsionics(roller.uid, roller.component, false); - _rollers.Clear(); - } - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnMeleeHit); - SubscribeLocalEvent(OnStamHit); + base.Update(frameTime); + foreach (var roller in _rollers) + RollPsionics(roller.uid, roller.component, false); + _rollers.Clear(); + } + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnMeleeHit); + SubscribeLocalEvent(OnStamHit); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnRemove); - } + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } - private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) - { - _rollers.Enqueue((component, uid)); - } + private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) + { + if (!component.Removable + || !component.CanReroll) + return; - private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) - { - foreach (var entity in args.HitEntities) - { - if (HasComp(entity)) - { - _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); - args.ModifiersList.Add(component.Modifiers); - if (_random.Prob(component.DisableChance)) - _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); - } - - if (TryComp(entity, out var swapped)) - { - _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); - return; - } - - if (component.Punish && !HasComp(entity) && _random.Prob(component.PunishChances)) - _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); - } - } + CheckPowerCost(uid, component); + _rollers.Enqueue((component, uid)); + } - private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) - { - component.AmplificationSources.Add(BaselineAmplification, _random.NextFloat(component.BaselineAmplification.Item1, component.BaselineAmplification.Item2)); - component.DampeningSources.Add(BaselineDampening, _random.NextFloat(component.BaselineDampening.Item1, component.BaselineDampening.Item2)); + /// + /// On MapInit, PsionicComponent isn't going to contain any powers. + /// So before we send a Latent Psychic into the roundstart roll queue, we need to calculate their power cost in advance. + /// + private void CheckPowerCost(EntityUid uid, PsionicComponent component) + { + if (!TryComp(uid, out var innate)) + return; - if (!component.Removable - || !TryComp(uid, out var factions) - || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) - return; + var powerCount = 0; + foreach (var powerId in innate.PowersToAdd) + if (_protoMan.TryIndex(powerId, out var power)) + powerCount += power.PowerSlotCost; - _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); - } + component.NextPowerCost = 100 * MathF.Pow(2, powerCount); + } - private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + foreach (var entity in args.HitEntities) + CheckAntiPsionic(entity, component, args); + } + + private void CheckAntiPsionic(EntityUid entity, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + if (HasComp(entity)) { - if (!HasComp(uid)) + _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); + args.ModifiersList.Add(component.Modifiers); + + if (!_random.Prob(component.DisableChance)) return; - _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); } - private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) - { - if (HasComp(args.Target)) - args.FlatModifier += component.PsychicStaminaDamage; - } + if (TryComp(entity, out var swapped)) + _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); - public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) - { - if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) - || !component.Removable) - return; + if (!component.Punish + || HasComp(entity) + || !_random.Prob(component.PunishChances)) + return; + + _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); + } - // Calculate the initial odds based on the innate potential - var baselineChance = component.Chance - * component.PowerRollMultiplier - + component.PowerRollFlatBonus; + private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) + { + component.AmplificationSources.Add(BaselineAmplification, _random.NextFloat(component.BaselineAmplification.Item1, component.BaselineAmplification.Item2)); + component.DampeningSources.Add(BaselineDampening, _random.NextFloat(component.BaselineDampening.Item1, component.BaselineDampening.Item2)); - // Increase the initial odds based on Glimmer. - // TODO: Change this equation when I do my Glimmer Refactor - baselineChance += applyGlimmer - ? (float) _glimmerSystem.Glimmer / 1000 //Convert from Glimmer to %chance - : 0; + if (!component.Removable + || !TryComp(uid, out var factions) + || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) + return; - // Certain sources of power rolls provide their own multiplier. - baselineChance *= rollEventMultiplier; + _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); + } - // Ask if the Roller has any other effects to contribute, such as Traits. - var ev = new OnRollPsionicsEvent(uid, baselineChance); - RaiseLocalEvent(uid, ref ev); + private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + { + if (!HasComp(uid)) + return; - if (_random.Prob(Math.Clamp(ev.BaselineChance, 0, 1))) - _psionicAbilitiesSystem.AddPsionics(uid); - } + _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + } - public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) - { - if (!Resolve(uid, ref psionic, false) - || !psionic.Removable - || psionic.CanReroll) - return; + private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) + { + if (!HasComp(args.Target)) + return; - RollPsionics(uid, psionic, true, bonusMuliplier); - psionic.CanReroll = true; - } + args.FlatModifier += component.PsychicStaminaDamage; + } + + /// + /// Now we handle Potentia calculations, the more powers you have, the harder it is to obtain psionics, but the content of your roll carries over to the next roll. + /// Your first power costs 100(2^0 is always 1), your second power costs 200, your 3rd power costs 400, and so on. This also considers people with roundstart powers. + /// Such that a Mystagogue(who has 3 powers at roundstart) needs 800 Potentia to gain his 4th power. + /// + /// + /// This exponential cost is mainly done to prevent stations from becoming "Space Hogwarts", + /// which was a common complaint with Psionic Refactor opening up the opportunity for people to have multiple powers. + /// + private bool HandlePotentiaCalculations(EntityUid uid, PsionicComponent component, float psionicChance) + { + component.Potentia += _random.NextFloat(0 + psionicChance, 100 + psionicChance); + + if (component.Potentia < component.NextPowerCost) + return false; + + component.Potentia -= component.NextPowerCost; + _psionicAbilitiesSystem.AddPsionics(uid); + component.NextPowerCost = 100 * MathF.Pow(2, component.PowerSlotsTaken); + return true; + } + + /// + /// Provide the player with feedback about their roll failure, so they don't just think nothing happened. + /// TODO: Add an audio cue to this and other areas of psionic player feedback. + /// + private void HandleRollFeedback(EntityUid uid) + { + if (!_playerManager.TryGetSessionByEntity(uid, out var session) + || !Loc.TryGetString(PsionicRollFailedMessage, out var rollFailedMessage)) + return; + + _popups.PopupEntity(rollFailedMessage, uid, uid, PopupType.MediumCaution); + + // Popups only last a few seconds, and are easily ignored. + // So we also put a message in chat to make it harder to miss. + var feedbackMessage = $"[font size={PsionicRollFailedFontSize}][color={PsionicRollFailedColor}]{rollFailedMessage}[/color][/font]"; + _chatManager.ChatMessageToOne( + PsionicRollFailedChatChannel, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + session.Channel); + } + + /// + /// This function attempts to generate a psionic power by incrementing a Psion's Potentia stat by a random amount, then checking if it beats a certain threshold. + /// Please consider going through RerollPsionics or PsionicAbilitiesSystem.InitializePsionicPower instead of this function, particularly if you don't have a good reason to call this directly. + /// + public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) + { + if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) + || !component.Removable) + return; + + // Calculate the initial odds based on the innate potential + var baselineChance = component.Chance + * component.PowerRollMultiplier + + component.PowerRollFlatBonus; + + // Increase the initial odds based on Glimmer. + // TODO: Change this equation when I do my Glimmer Refactor + baselineChance += applyGlimmer + ? (float) _glimmerSystem.Glimmer / 1000 //Convert from Glimmer to %chance + : 0; + + // Certain sources of power rolls provide their own multiplier. + baselineChance *= rollEventMultiplier; + + // Ask if the Roller has any other effects to contribute, such as Traits. + var ev = new OnRollPsionicsEvent(uid, baselineChance); + RaiseLocalEvent(uid, ref ev); + + if (HandlePotentiaCalculations(uid, component, ev.BaselineChance)) + return; + + HandleRollFeedback(uid); + } + + /// + /// Each person has a single free reroll for their Psionics, which certain conditions can restore. + /// This function attempts to "Spend" a reroll, if one is available. + /// + public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) + { + if (!Resolve(uid, ref psionic, false) + || !psionic.Removable + || !psionic.CanReroll) + return; + + RollPsionics(uid, psionic, true, bonusMuliplier); + psionic.CanReroll = false; } } diff --git a/Content.Server/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/StationEvents/Events/NoosphericZapRule.cs index 4be7b6e63fc..96c33612036 100644 --- a/Content.Server/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericZapRule.cs @@ -36,9 +36,9 @@ protected override void Started(EntityUid uid, NoosphericZapRuleComponent compon _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(component.StunDuration), false); _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(component.StutterDuration), false, "StutteringAccent"); - if (psionicComponent.CanReroll) + if (!psionicComponent.CanReroll) { - psionicComponent.CanReroll = false; + psionicComponent.CanReroll = true; _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); } else diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 63529576152..85b7e380fea 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -8,11 +8,19 @@ namespace Content.Shared.Abilities.Psionics public sealed partial class PsionicComponent : Component { /// - /// How close a Psion is to awakening a new power. - /// TODO: Implement this in a separate PR. + /// How close a Psion is to generating a new power. When Potentia reaches the NextPowerCost, it is "Spent" in order to "Buy" a random new power. + /// TODO: Psi-Potentiometry should be able to read how much Potentia a person has. + /// + [DataField] + public float Potentia; + + /// + /// Each time a Psion rolls for a new power, they roll a number between 0 and 100, adding any relevant modifiers. This number is then added to Potentia, + /// meaning that it carries over between rolls. When a character has an amount of potentia equal to at least 100 * 2^(total powers), the potentia is then spent, and a power is generated. + /// This variable stores the cost of the next power. /// [DataField] - public float Potentia = 0; + public float NextPowerCost = 100; /// /// The baseline chance of obtaining a psionic power when rolling for one. @@ -24,7 +32,7 @@ public sealed partial class PsionicComponent : Component /// Whether or not a Psion has an available "Reroll" to spend on attempting to gain powers. /// [DataField] - public bool CanReroll; + public bool CanReroll = true; /// /// The Base amount of time (in minutes) this Psion is given the stutter effect if they become mindbroken. @@ -142,6 +150,18 @@ private set public float CurrentDampening; /// + /// How many "Slots" an entity has for psionic powers. This is not a hard limit, and is instead used for calculating the cost to generate new powers. + /// Exceeding this limit causes an entity to become a Glimmer Source. + /// + [DataField] + public int PowerSlots = 1; + + /// + /// How many "Slots" are currently occupied by psionic powers. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int PowerSlotsTaken; + /// List of descriptors this entity will bring up for psychognomy. Used to remove /// unneccesary subs for unique psionic entities like e.g. Oracle. /// diff --git a/Content.Shared/Psionics/PsionicPowerPrototype.cs b/Content.Shared/Psionics/PsionicPowerPrototype.cs index 3d389f6cdbe..c4a3326733a 100644 --- a/Content.Shared/Psionics/PsionicPowerPrototype.cs +++ b/Content.Shared/Psionics/PsionicPowerPrototype.cs @@ -86,4 +86,10 @@ public sealed partial class PsionicPowerPrototype : IPrototype /// [DataField] public float DampeningModifier = 0; + + /// + /// How many "Power Slots" this power occupies. + /// + [DataField] + public int PowerSlotCost = 1; } \ No newline at end of file diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index ad6bbef02bd..c68bb2a4968 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -86,7 +86,9 @@ telepathy-power-initialization-feedback = The voices I've heard all my life begin to clear, yet they do not leave me. Before, they were as incoherent whispers, now my senses broaden, I come to a realization that they are part of a communal shared hallucination. Behind every voice is a glimmering sentience. +# Psionic System Messages mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience examine-mindbroken-message = Eyes unblinking, staring deep into the horizon. {CAPITALIZE($entity)} is a sack of meat pretending it has a soul. - There is nothing behind its gaze, no evidence there can be found of the divine light of creation. \ No newline at end of file + There is nothing behind its gaze, no evidence there can be found of the divine light of creation. +psionic-roll-failed = For a moment, my consciousness expands, yet I feel that it is not enough. \ No newline at end of file diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index 1f78015cd63..f7a31205706 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -32,6 +32,7 @@ - !type:AddComponentSpecial components: - type: Psionic + powerSlots: 2 - !type:AddComponentSpecial components: - type: InnatePsionicPowers diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml index b8f798d4b63..461098eab21 100644 --- a/Resources/Prototypes/Psionics/psionics.yml +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -119,6 +119,7 @@ - type: UniversalLanguageSpeaker initializationFeedback: xenoglossy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 - type: psionicPower id: PsychognomyPower #i.e. reverse physiognomy @@ -128,6 +129,7 @@ - type: Psychognomist initializationFeedback: psychognomy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback + powerSlotCost: 0 - type: psionicPower id: TelepathyPower @@ -137,3 +139,4 @@ - type: Telepathy initializationFeedback: telepathy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index e2d06f3af96..46d91ee00ee 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -38,6 +38,7 @@ components: - type: BibleUser # Nyano - Lets them heal with bibles - type: Psionic # Nyano - They start with telepathic chat + powerSlots: 3 - !type:AddImplantSpecial implants: [ MindShieldImplant ] - !type:AddComponentSpecial From a55390985846a3262023b5c512f1f828d9e007d2 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 19:42:47 -0400 Subject: [PATCH 11/24] IPC Refactor (#771) # Description IPCs were ported from a codebase that didn't necessarily follow all of our repo's coding standards. And while I had done my part to cleanup as much of the system as was practical within the bounds of a Maintainer Review, there were a lot of things that I felt were inappropriate to leave to review, and wished to go over with a fine lense. Thus, here is my Refactor of IPC code. Do not merge this without first testing that nothing was broken. Because I haven't tested it myself yet. --- .../Commands/SetOutfitCommand.cs | 8 +- .../SiliconEmitSoundOnDrainedComponent.cs | 2 - .../Power/Systems/BatteryDrinkerSystem.cs | 56 ++---- .../Systems/BatteryElectrocuteChargeSystem.cs | 9 +- .../SiliconEmitSoundOnDrainedSystem.cs | 7 +- .../BatterySlotRequiresLockSystem.cs | 15 +- .../BlindHealing/BlindHealingComponent.cs | 39 ++-- .../BlindHealing/BlindHealingSystem.cs | 161 +++++++--------- .../Components/SiliconDownOnDeadComponent.cs | 4 +- .../Systems/SiliconChargeDeathSystem.cs | 9 +- .../Charge/Systems/SiliconChargeSystem.cs | 52 ++--- .../DeadStartupButtonSystem.cs | 26 +-- .../EmitBuzzWhileDamagedSystem.cs | 33 ++-- .../EncryptionHolderRequiresLockSystem.cs | 6 +- .../IPC/InternalEncryptionKeySpawner.cs | 42 ++-- .../Silicon/Systems/SiliconMiscSystem.cs | 20 -- .../WeldingHealableComponent.cs | 12 +- .../WeldingHealable/WeldingHealableSystem.cs | 179 ++++++++---------- .../WeldingHealing/WeldingHealingComponent.cs | 83 ++++---- .../Station/Systems/StationSpawningSystem.cs | 4 +- Content.Shared/Lock/LockComponent.cs | 54 +++--- Content.Shared/Lock/LockSystem.cs | 1 - .../EncryptionKeyHolderComponent.cs | 18 +- .../EntitySystems/EncryptionKeySystem.cs | 12 +- Content.Shared/Silicon/BatteryDrinkerEvent.cs | 4 +- .../BlindHealing/SharedBlindHealingSystem.cs | 4 +- .../Silicon/Components/SiliconComponent.cs | 30 +-- .../DeadStartupButtonComponent.cs | 14 +- .../SharedDeadStartupButtonSystem.cs | 23 +-- .../EmitBuzzWhileDamagedComponent.cs | 14 +- .../Silicon/Systems/SharedSiliconSystem.cs | 68 ++++--- .../Station/SharedStationSpawningSystem.cs | 1 - 32 files changed, 424 insertions(+), 586 deletions(-) delete mode 100644 Content.Server/Silicon/Systems/SiliconMiscSystem.cs diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs index 2f979f4340b..12312286518 100644 --- a/Content.Server/Administration/Commands/SetOutfitCommand.cs +++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs @@ -13,6 +13,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Content.Server.Silicon.IPC; +using Content.Shared.Radio.Components; namespace Content.Server.Administration.Commands { @@ -127,7 +128,12 @@ public static bool SetOutfit(EntityUid target, string gear, IEntityManager entit handsSystem.TryPickup(target, inhandEntity, checkActionBlocker: false, handsComp: handsComponent); } } - InternalEncryptionKeySpawner.TryInsertEncryptionKey(target, startingGear, entityManager, profile); + + if (entityManager.HasComponent(target)) + { + var encryption = new InternalEncryptionKeySpawner(); + encryption.TryInsertEncryptionKey(target, startingGear, entityManager); + } return true; } } diff --git a/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs b/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs index 4e5121d6076..5740d7822d0 100644 --- a/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs +++ b/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations; using Robust.Shared.Audio; -using Content.Server.Sound.Components; namespace Content.Server.Silicon; diff --git a/Content.Server/Power/Systems/BatteryDrinkerSystem.cs b/Content.Server/Power/Systems/BatteryDrinkerSystem.cs index 9a06d4181c9..e42783c4d8d 100644 --- a/Content.Server/Power/Systems/BatteryDrinkerSystem.cs +++ b/Content.Server/Power/Systems/BatteryDrinkerSystem.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Power.Components; using Content.Shared.Containers.ItemSlots; @@ -12,8 +11,6 @@ using Content.Server.Popups; using Content.Server.PowerCell; using Content.Shared.Popups; -using Content.Shared.Silicon.Components; -using FastAccessors; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -21,13 +18,11 @@ namespace Content.Server.Power; public sealed class BatteryDrinkerSystem : EntitySystem { - [Dependency] private readonly ItemSlotsSystem _slots = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly SiliconChargeSystem _silicon = default!; [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly SharedContainerSystem _container = default!; public override void Initialize() @@ -41,12 +36,10 @@ public override void Initialize() private void AddAltVerb(EntityUid uid, BatteryComponent batteryComponent, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract) - return; - - if (!TryComp(args.User, out var drinkerComp) || - !TestDrinkableBattery(uid, drinkerComp) || - !_silicon.TryGetSiliconBattery(args.User, out var drinkerBattery)) + if (!args.CanAccess || !args.CanInteract + || !TryComp(args.User, out var drinkerComp) + || !TestDrinkableBattery(uid, drinkerComp) + || !_silicon.TryGetSiliconBattery(args.User, out var _)) return; AlternativeVerb verb = new() @@ -80,6 +73,7 @@ private void DrinkBattery(EntityUid target, EntityUid user, BatteryDrinkerCompon { BreakOnDamage = true, BreakOnTargetMove = true, + BreakOnUserMove = true, Broadcast = false, DistanceThreshold = 1.35f, RequireCanInteract = true, @@ -91,39 +85,28 @@ private void DrinkBattery(EntityUid target, EntityUid user, BatteryDrinkerCompon private void OnDoAfter(EntityUid uid, BatteryDrinkerComponent drinkerComp, DoAfterEvent args) { - if (args.Cancelled || args.Target == null) + if (args.Cancelled || args.Target == null + || !TryComp(args.Target.Value, out var sourceBattery) + || !_silicon.TryGetSiliconBattery(uid, out var drinkerBatteryComponent) + || !TryComp(uid, out PowerCellSlotComponent? batterySlot) + || !TryComp(args.Target.Value, out var sourceComp) + || !_container.TryGetContainer(uid, batterySlot.CellSlotId, out var container) + || container.ContainedEntities is null) return; var source = args.Target.Value; - var drinker = uid; - var sourceBattery = Comp(source); - - _silicon.TryGetSiliconBattery(drinker, out var drinkerBatteryComponent); - - if (!TryComp(uid, out PowerCellSlotComponent? batterySlot)) - return; - - var container = _container.GetContainer(uid, batterySlot.CellSlotId); var drinkerBattery = container.ContainedEntities.First(); - - TryComp(source, out var sourceComp); - - DebugTools.AssertNotNull(drinkerBattery); - - if (drinkerBattery == null) - return; - var amountToDrink = drinkerComp.DrinkMultiplier * 1000; amountToDrink = MathF.Min(amountToDrink, sourceBattery.CurrentCharge); amountToDrink = MathF.Min(amountToDrink, drinkerBatteryComponent!.MaxCharge - drinkerBatteryComponent.CurrentCharge); - if (sourceComp != null && sourceComp.MaxAmount > 0) + if (sourceComp.MaxAmount > 0) amountToDrink = MathF.Min(amountToDrink, (float) sourceComp.MaxAmount); if (amountToDrink <= 0) { - _popup.PopupEntity(Loc.GetString("battery-drinker-empty", ("target", source)), drinker, drinker); + _popup.PopupEntity(Loc.GetString("battery-drinker-empty", ("target", source)), uid, uid); return; } @@ -135,10 +118,11 @@ private void OnDoAfter(EntityUid uid, BatteryDrinkerComponent drinkerComp, DoAft _battery.SetCharge(source, 0); } - if (sourceComp != null && sourceComp.DrinkSound != null){ - _popup.PopupEntity(Loc.GetString("ipc-recharge-tip"), drinker, drinker, PopupType.SmallCaution); - _audio.PlayPvs(sourceComp.DrinkSound, source); - Spawn("EffectSparks", Transform(source).Coordinates); - } + if (sourceComp.DrinkSound is null) + return; + + _popup.PopupEntity(Loc.GetString("ipc-recharge-tip"), uid, uid, PopupType.SmallCaution); + _audio.PlayPvs(sourceComp.DrinkSound, source); + Spawn("EffectSparks", Transform(source).Coordinates); } } diff --git a/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs b/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs index 9993c151b1e..707d7c679e8 100644 --- a/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs +++ b/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Electrocution; using Robust.Shared.Random; -using Robust.Shared.Timing; namespace Content.Server.Power.Systems; @@ -26,10 +25,10 @@ private void OnElectrocuted(EntityUid uid, BatteryComponent battery, Electrocute if (args.ShockDamage == null || args.ShockDamage <= 0) return; - var damagePerWatt = ElectrocutionSystem.ElectrifiedDamagePerWatt * 2; - - var damage = args.ShockDamage.Value * args.SiemensCoefficient; - var charge = Math.Min(damage / damagePerWatt, battery.MaxCharge * 0.25f) * _random.NextFloat(0.75f, 1.25f); + var charge = Math.Min(args.ShockDamage.Value * args.SiemensCoefficient + / ElectrocutionSystem.ElectrifiedDamagePerWatt * 2, + battery.MaxCharge * 0.25f) + * _random.NextFloat(0.75f, 1.25f); _battery.SetCharge(uid, battery.CurrentCharge + charge); diff --git a/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs b/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs index 28a46cc7f70..f95a940aaeb 100644 --- a/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs +++ b/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.Sound.Components; using Content.Server.Sound; using Content.Shared.Mobs; -using Content.Shared.Silicon.Systems; namespace Content.Server.Silicon; @@ -35,7 +34,9 @@ private void OnAlive(EntityUid uid, SiliconEmitSoundOnDrainedComponent component public void OnStateChange(EntityUid uid, SiliconEmitSoundOnDrainedComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Dead) - RemComp(uid); + if (args.NewMobState != MobState.Dead) + return; + + RemComp(uid); } } diff --git a/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs b/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs index 98e1cb5584c..12e86691281 100644 --- a/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs +++ b/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs @@ -2,7 +2,7 @@ using Content.Shared.Lock; using Content.Shared.Popups; using Content.Shared.Silicon.Components; -using Content.Shared.IdentityManagement; +using Content.Shared.IdentityManagement; namespace Content.Server.Silicon.BatteryLocking; @@ -10,19 +10,18 @@ public sealed class BatterySlotRequiresLockSystem : EntitySystem { [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(LockToggled); - SubscribeLocalEvent(LockToggleAttempted); - + SubscribeLocalEvent(LockToggleAttempted); } + private void LockToggled(EntityUid uid, BatterySlotRequiresLockComponent component, LockToggledEvent args) { - if (!TryComp(uid, out var lockComp) + if (!TryComp(uid, out var lockComp) || !TryComp(uid, out var itemslots) || !_itemSlotsSystem.TryGetSlot(uid, component.ItemSlot, out var slot, itemslots)) return; @@ -33,9 +32,9 @@ private void LockToggled(EntityUid uid, BatterySlotRequiresLockComponent compone private void LockToggleAttempted(EntityUid uid, BatterySlotRequiresLockComponent component, LockToggleAttemptEvent args) { if (args.User == uid - || !TryComp(uid, out var siliconComp)) + || !HasComp(uid)) return; - + _popupSystem.PopupEntity(Loc.GetString("batteryslotrequireslock-component-alert-owner", ("user", Identity.Entity(args.User, EntityManager))), uid, uid, PopupType.Large); } diff --git a/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs b/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs index 4c3c478c4b8..fe218491708 100644 --- a/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs +++ b/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs @@ -1,28 +1,23 @@ -using Content.Shared.Damage; -using Content.Shared.Tools; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +namespace Content.Server.Silicon.BlindHealing; -namespace Content.Server.Silicon.BlindHealing +[RegisterComponent] +public sealed partial class BlindHealingComponent : Component { - [RegisterComponent] - public sealed partial class BlindHealingComponent : Component - { - [DataField] - public int DoAfterDelay = 3; + [DataField] + public int DoAfterDelay = 3; - /// - /// A multiplier that will be applied to the above if an entity is repairing themselves. - /// - [DataField] - public float SelfHealPenalty = 3f; + /// + /// A multiplier that will be applied to the above if an entity is repairing themselves. + /// + [DataField] + public float SelfHealPenalty = 3f; - /// - /// Whether or not an entity is allowed to repair itself. - /// - [DataField] - public bool AllowSelfHeal = true; + /// + /// Whether or not an entity is allowed to repair itself. + /// + [DataField] + public bool AllowSelfHeal = true; - [DataField(required: true)] - public List DamageContainers; - } + [DataField(required: true)] + public List DamageContainers; } diff --git a/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs b/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs index 6cf60e6ef3f..b9d26b59f7c 100644 --- a/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs +++ b/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs @@ -12,110 +12,87 @@ using Content.Shared.Popups; using Content.Shared.Stacks; -namespace Content.Server.Silicon.BlindHealing +namespace Content.Server.Silicon.BlindHealing; + +public sealed class BlindHealingSystem : SharedBlindHealingSystem { - public sealed class BlindHealingSystem : SharedBlindHealingSystem + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly BlindableSystem _blindableSystem = default!; + [Dependency] private readonly StackSystem _stackSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + + public override void Initialize() { - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly IAdminLogManager _adminLogger= default!; - [Dependency] private readonly BlindableSystem _blindableSystem = default!; - [Dependency] private readonly StackSystem _stackSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; - - public override void Initialize() - { - SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(OnInteract); - SubscribeLocalEvent(OnHealingFinished); - } - - private void OnHealingFinished(EntityUid uid, BlindHealingComponent component, HealingDoAfterEvent args) - { - Log.Info("event started!"); - - if (args.Cancelled || args.Target == null) - return; - - EntityUid target = (EntityUid) args.Target; - - if(!EntityManager.TryGetComponent(target, out BlindableComponent? blindcomp) - || blindcomp is { EyeDamage: 0 }) - return; - - if(EntityManager.TryGetComponent(uid, out StackComponent? stackComponent)) - { - double price = 1; - if (EntityManager.TryGetComponent(uid, out StackPriceComponent? stackPrice)) - price = stackPrice.Price; - _stackSystem.SetCount(uid, (int) (_stackSystem.GetCount(uid, stackComponent) - price), stackComponent); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnHealingFinished); + } - } + private void OnHealingFinished(EntityUid uid, BlindHealingComponent component, HealingDoAfterEvent args) + { + if (args.Cancelled || args.Target == null + || !TryComp(args.Target, out var blindComp) + || blindComp is { EyeDamage: 0 }) + return; - _blindableSystem.AdjustEyeDamage((target, blindcomp), -blindcomp!.EyeDamage); + if (TryComp(uid, out var stackComponent) + && TryComp(uid, out var stackPrice)) + _stackSystem.SetCount(uid, (int) (_stackSystem.GetCount(uid, stackComponent) - stackPrice.Price), stackComponent); - _adminLogger.Add(LogType.Healed, $"{ToPrettyString(args.User):user} repaired {ToPrettyString(uid):target}'s vision"); + _blindableSystem.AdjustEyeDamage((args.Target.Value, blindComp), -blindComp.EyeDamage); - var str = Loc.GetString("comp-repairable-repair", - ("target", uid), - ("tool", args.Used!)); - _popup.PopupEntity(str, uid, args.User); + _adminLogger.Add(LogType.Healed, $"{ToPrettyString(args.User):user} repaired {ToPrettyString(uid):target}'s vision"); - } + var str = Loc.GetString("comp-repairable-repair", + ("target", uid), + ("tool", args.Used!)); + _popup.PopupEntity(str, uid, args.User); - private bool TryHealBlindness(EntityUid uid, EntityUid user, EntityUid target, float delay) - { - var doAfterEventArgs = - new DoAfterArgs(EntityManager, user, delay, new HealingDoAfterEvent(), uid, target: target, used: uid) - { - NeedHand = true, - BreakOnUserMove = true, - BreakOnWeightlessMove = false, - }; + } - _doAfter.TryStartDoAfter(doAfterEventArgs); - return true; - } + private bool TryHealBlindness(EntityUid uid, EntityUid user, EntityUid target, float delay) + { + var doAfterEventArgs = + new DoAfterArgs(EntityManager, user, delay, new HealingDoAfterEvent(), uid, target: target, used: uid) + { + NeedHand = true, + BreakOnUserMove = true, + BreakOnWeightlessMove = false, + }; - private void OnInteract(EntityUid uid, BlindHealingComponent component, ref AfterInteractEvent args) - { + _doAfter.TryStartDoAfter(doAfterEventArgs); + return true; + } - if (args.Handled - || !TryComp(args.User, out DamageableComponent? damageable) - || damageable.DamageContainerID != null - && !component.DamageContainers.Contains(damageable.DamageContainerID) - || !TryComp(args.User, out BlindableComponent? blindcomp) - || blindcomp is { EyeDamage: 0 }) - return; + private void OnInteract(EntityUid uid, BlindHealingComponent component, ref AfterInteractEvent args) + { - float delay = component.DoAfterDelay; + if (args.Handled + || !TryComp(args.User, out var damageable) + || damageable.DamageContainerID != null && !component.DamageContainers.Contains(damageable.DamageContainerID) + || !TryComp(args.User, out var blindcomp) + || blindcomp.EyeDamage == 0 + || args.User == args.Target && !component.AllowSelfHeal) + return; + + TryHealBlindness(uid, args.User, args.User, + args.User == args.Target + ? component.DoAfterDelay * component.SelfHealPenalty + : component.DoAfterDelay); + } - if (args.User == args.Target) - { - if (!component.AllowSelfHeal) - return; - delay *= component.SelfHealPenalty; - } - - TryHealBlindness(uid, args.User, args.User, delay); - } - - private void OnUse(EntityUid uid, BlindHealingComponent component, ref UseInHandEvent args) - { - if (args.Handled - || !TryComp(args.User, out DamageableComponent? damageable) - || damageable.DamageContainerID != null - && !component.DamageContainers.Contains(damageable.DamageContainerID) - || !TryComp(args.User, out BlindableComponent? blindcomp) - || blindcomp is { EyeDamage: 0 } - || !component.AllowSelfHeal) - return; - - float delay = component.DoAfterDelay; - delay *= component.SelfHealPenalty; - - TryHealBlindness(uid, args.User, args.User, delay); - - } + private void OnUse(EntityUid uid, BlindHealingComponent component, ref UseInHandEvent args) + { + if (args.Handled + || !TryComp(args.User, out var damageable) + || damageable.DamageContainerID != null && !component.DamageContainers.Contains(damageable.DamageContainerID) + || !TryComp(args.User, out var blindcomp) + || blindcomp.EyeDamage == 0 + || !component.AllowSelfHeal) + return; + + TryHealBlindness(uid, args.User, args.User, + component.DoAfterDelay * component.SelfHealPenalty); } } diff --git a/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs b/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs index 3080144cd4a..4b3aad33ab6 100644 --- a/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs +++ b/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs @@ -1,5 +1,3 @@ -using System.Threading; - namespace Content.Server.Silicon.Death; /// @@ -15,5 +13,5 @@ public sealed partial class SiliconDownOnDeadComponent : Component /// Is this Silicon currently dead? /// [ViewVariables(VVAccess.ReadOnly)] - public bool Dead { get; set; } = false; + public bool Dead; } diff --git a/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs b/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs index d6fa07a1a14..d4d1db5ed96 100644 --- a/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs +++ b/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs @@ -2,14 +2,9 @@ using Content.Shared.Silicon.Systems; using Content.Server.Bed.Sleep; using Content.Shared.Bed.Sleep; -using Content.Server.Sound.Components; using Content.Server.Silicon.Charge; -using System.Threading; using Content.Server.Humanoid; using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Markings; -using Robust.Shared.Utility; -using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Silicon.Death; @@ -40,7 +35,7 @@ private void OnSiliconChargeStateUpdate(EntityUid uid, SiliconDownOnDeadComponen if (args.ChargePercent == 0 && !siliconDeadComp.Dead) SiliconDead(uid, siliconDeadComp, batteryComp, uid); else if (args.ChargePercent != 0 && siliconDeadComp.Dead) - SiliconUnDead(uid, siliconDeadComp, batteryComp, uid); + SiliconUnDead(uid, siliconDeadComp, batteryComp, uid); } private void SiliconDead(EntityUid uid, SiliconDownOnDeadComponent siliconDeadComp, BatteryComponent? batteryComp, EntityUid batteryUid) @@ -54,7 +49,7 @@ private void SiliconDead(EntityUid uid, SiliconDownOnDeadComponent siliconDeadCo EntityManager.EnsureComponent(uid); EntityManager.EnsureComponent(uid); - if (TryComp(uid, out HumanoidAppearanceComponent? humanoidAppearanceComponent)) + if (TryComp(uid, out var humanoidAppearanceComponent)) { var layers = HumanoidVisualLayersExtension.Sublayers(HumanoidVisualLayers.HeadSide); _humanoidAppearanceSystem.SetLayersVisibility(uid, layers, false, true, humanoidAppearanceComponent); diff --git a/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs b/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs index de50c828bd7..d8b034a69f5 100644 --- a/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs +++ b/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs @@ -10,20 +10,16 @@ using Content.Shared.Silicon.Systems; using Content.Shared.Movement.Systems; using Content.Server.Body.Components; -using Content.Server.Power.EntitySystems; using Robust.Shared.Containers; using Content.Shared.Mind.Components; using System.Diagnostics.CodeAnalysis; using Content.Server.PowerCell; using Robust.Shared.Timing; using Robust.Shared.Configuration; -using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; using Content.Shared.CCVar; using Content.Shared.PowerCell.Components; -using Content.Shared.Mind; using Content.Shared.Alert; -using Content.Server.Silicon.Death; namespace Content.Server.Silicon.Charge; @@ -34,7 +30,6 @@ public sealed class SiliconChargeSystem : EntitySystem [Dependency] private readonly FlammableSystem _flammable = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly MovementSpeedModifierSystem _moveMod = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; @@ -49,27 +44,23 @@ public override void Initialize() public bool TryGetSiliconBattery(EntityUid silicon, [NotNullWhen(true)] out BatteryComponent? batteryComp) { batteryComp = null; - if (!TryComp(silicon, out SiliconComponent? siliconComp)) + if (!HasComp(silicon)) return false; - // try get a battery directly on the inserted entity if (TryComp(silicon, out batteryComp) || _powerCell.TryGetBatteryFromSlot(silicon, out batteryComp)) return true; - //DebugTools.Assert("SiliconComponent does not contain Battery"); return false; } private void OnSiliconStartup(EntityUid uid, SiliconComponent component, ComponentStartup args) { - if (!TryComp(uid, out PowerCellSlotComponent? batterySlot)) + if (!HasComp(uid)) return; - var container = _container.GetContainer(uid, batterySlot.CellSlotId); - if (component.EntityType.GetType() != typeof(SiliconType)) DebugTools.Assert("SiliconComponent.EntityType is not a SiliconType enum."); } @@ -82,7 +73,8 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var silicon, out var siliconComp)) { - if (!siliconComp.BatteryPowered) + if (_mobState.IsDead(silicon) + || !siliconComp.BatteryPowered) continue; // Check if the Silicon is an NPC, and if so, follow the delay as specified in the CVAR. @@ -107,20 +99,11 @@ public override void Update(float frameTime) continue; } - // If the silicon is dead, skip it. - if (_mobState.IsDead(silicon)) + // If the silicon ghosted or is SSD while still being powered, skip it. + if (TryComp(silicon, out var mindContComp) + && !mindContComp.HasMind) continue; - // If the silicon ghosted or is SSD while still being powered, skip it. - DeltaV - if (EntityManager.TryGetComponent(silicon, out var mindContComp) - && EntityManager.TryGetComponent(silicon, out var siliconDeathComp)) - { - if ((mindContComp.HasMind == false || CompOrNull(mindContComp.Mind)?.Session == null) && !siliconDeathComp.Dead) - { - continue; - } - } - var drainRate = siliconComp.DrainPerSecond; // All multipliers will be subtracted by 1, and then added together, and then multiplied by the drain rate. This is then added to the base drain rate. @@ -131,7 +114,7 @@ public override void Update(float frameTime) // Maybe use something similar to refreshmovespeedmodifiers, where it's stored in the component. // Maybe it doesn't matter, and stuff should just use static drain? if (!siliconComp.EntityType.Equals(SiliconType.Npc)) // Don't bother checking heat if it's an NPC. It's a waste of time, and it'd be delayed due to the update time. - drainRateFinalAddi += SiliconHeatEffects(silicon, frameTime) - 1; // This will need to be changed at some point if we allow external batteries, since the heat of the Silicon might not be applicable. + drainRateFinalAddi += SiliconHeatEffects(silicon, siliconComp, frameTime) - 1; // This will need to be changed at some point if we allow external batteries, since the heat of the Silicon might not be applicable. // Ensures that the drain rate is at least 10% of normal, // and would allow at least 4 minutes of life with a max charge, to prevent cheese. @@ -166,14 +149,12 @@ public void UpdateChargeState(EntityUid uid, short chargePercent, SiliconCompone } } - private float SiliconHeatEffects(EntityUid silicon, float frameTime) + private float SiliconHeatEffects(EntityUid silicon, SiliconComponent siliconComp, float frameTime) { - if (!EntityManager.TryGetComponent(silicon, out var temperComp) - || !EntityManager.TryGetComponent(silicon, out var thermalComp)) + if (!TryComp(silicon, out var temperComp) + || !TryComp(silicon, out var thermalComp)) return 0; - var siliconComp = EntityManager.GetComponent(silicon); - // If the Silicon is hot, drain the battery faster, if it's cold, drain it slower, capped. var upperThresh = thermalComp.NormalBodyTemperature + thermalComp.ThermalRegulationTemperatureThreshold; var upperThreshHalf = thermalComp.NormalBodyTemperature + thermalComp.ThermalRegulationTemperatureThreshold * 0.5f; @@ -198,12 +179,11 @@ private float SiliconHeatEffects(EntityUid silicon, float frameTime) return hotTempMulti; _popup.PopupEntity(Loc.GetString("silicon-overheating"), silicon, silicon, PopupType.MediumCaution); - if (_random.Prob(Math.Clamp(temperComp.CurrentTemperature / (upperThresh * 5), 0.001f, 0.9f))) - { - //MaximumFireStacks and MinimumFireStacks doesn't exists on EE - _flammable.AdjustFireStacks(silicon, Math.Clamp(siliconComp.FireStackMultiplier, -10, 10), flamComp); - _flammable.Ignite(silicon, silicon, flamComp); - } + if (!_random.Prob(Math.Clamp(temperComp.CurrentTemperature / (upperThresh * 5), 0.001f, 0.9f))) + return hotTempMulti; + + _flammable.AdjustFireStacks(silicon, Math.Clamp(siliconComp.FireStackMultiplier, -10, 10), flamComp); + _flammable.Ignite(silicon, silicon, flamComp); return hotTempMulti; } diff --git a/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs b/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs index a25f2a22545..1c43a3cff91 100644 --- a/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs +++ b/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Chat.Systems; using Content.Server.Lightning; using Content.Server.Popups; using Content.Server.PowerCell; @@ -25,7 +24,6 @@ public sealed class DeadStartupButtonSystem : SharedDeadStartupButtonSystem [Dependency] private readonly LightningSystem _lightning = default!; [Dependency] private readonly SiliconChargeSystem _siliconChargeSystem = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; /// public override void Initialize() @@ -39,14 +37,13 @@ public override void Initialize() private void OnDoAfter(EntityUid uid, DeadStartupButtonComponent comp, OnDoAfterButtonPressedEvent args) { if (args.Handled || args.Cancelled - || !TryComp(uid, out MobStateComponent? mobStateComponent) + || !TryComp(uid, out var mobStateComponent) || !_mobState.IsDead(uid, mobStateComponent) - || !TryComp(uid, out MobThresholdsComponent? mobThresholdsComponent) - || !TryComp(uid, out DamageableComponent? damageable)) + || !TryComp(uid, out var mobThresholdsComponent) + || !TryComp(uid, out var damageable) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var criticalThreshold, mobThresholdsComponent)) return; - var criticalThreshold = _mobThreshold.GetThresholdForState(uid, MobState.Critical, mobThresholdsComponent); - if (damageable.TotalDamage < criticalThreshold) _mobState.ChangeMobState(uid, MobState.Alive, mobStateComponent); else @@ -59,9 +56,9 @@ private void OnDoAfter(EntityUid uid, DeadStartupButtonComponent comp, OnDoAfter private void OnElectrocuted(EntityUid uid, DeadStartupButtonComponent comp, ElectrocutedEvent args) { - if (!TryComp(uid, out MobStateComponent? mobStateComponent) + if (!TryComp(uid, out var mobStateComponent) || !_mobState.IsDead(uid, mobStateComponent) - || !_siliconChargeSystem.TryGetSiliconBattery(uid, out var bateria) + || !_siliconChargeSystem.TryGetSiliconBattery(uid, out var bateria) || bateria.CurrentCharge <= 0) return; @@ -72,13 +69,10 @@ private void OnElectrocuted(EntityUid uid, DeadStartupButtonComponent comp, Elec private void OnMobStateChanged(EntityUid uid, DeadStartupButtonComponent comp, MobStateChangedEvent args) { + if (args.NewMobState != MobState.Alive) + return; - if (args.NewMobState == MobState.Alive) - { - _popup.PopupEntity(Loc.GetString("dead-startup-system-reboot-success", ("target", MetaData(uid).EntityName)), uid); - _audio.PlayPvs(comp.Sound, uid); - } - + _popup.PopupEntity(Loc.GetString("dead-startup-system-reboot-success", ("target", MetaData(uid).EntityName)), uid); + _audio.PlayPvs(comp.Sound, uid); } - } diff --git a/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs b/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs index 05e5f5af89b..161a912d061 100644 --- a/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs +++ b/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs @@ -1,13 +1,13 @@ using Content.Server.Popups; using Content.Shared.Silicon.EmitBuzzWhileDamaged; using Content.Shared.Audio; -using Content.Shared.Body.Components; using Content.Shared.Damage; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Robust.Shared.Audio.Systems; using Robust.Shared.Random; using Robust.Shared.Timing; +using Content.Shared.Mobs.Components; namespace Content.Server.Silicon.EmitBuzzOnCrit; @@ -27,34 +27,29 @@ public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var emitBuzzOnCritComponent, out var body)) + while (query.MoveNext(out var uid, out var emitBuzzOnCritComponent, out var mobStateComponent, out var thresholdsComponent, out var damageableComponent)) { - - if (_mobState.IsDead(uid) - || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var threshold) - || !TryComp(uid, out DamageableComponent? damageableComponent) - || damageableComponent.TotalDamage < (threshold/2)) + if (_mobState.IsDead(uid, mobStateComponent) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholdsComponent) + || damageableComponent.TotalDamage < threshold / 2) continue; - + // Check update time emitBuzzOnCritComponent.AccumulatedFrametime += frameTime; - if (emitBuzzOnCritComponent.AccumulatedFrametime < emitBuzzOnCritComponent.CycleDelay) continue; emitBuzzOnCritComponent.AccumulatedFrametime -= emitBuzzOnCritComponent.CycleDelay; + if (_gameTiming.CurTime <= emitBuzzOnCritComponent.LastBuzzPopupTime + emitBuzzOnCritComponent.BuzzPopupCooldown) + continue; - // start buzzing - if (_gameTiming.CurTime >= emitBuzzOnCritComponent.LastBuzzPopupTime + emitBuzzOnCritComponent.BuzzPopupCooldown) - { - emitBuzzOnCritComponent.LastBuzzPopupTime = _gameTiming.CurTime; - _popupSystem.PopupEntity(Loc.GetString("silicon-behavior-buzz"), uid); - Spawn("EffectSparks", Transform(uid).Coordinates); - _audio.PlayPvs(emitBuzzOnCritComponent.Sound, uid, AudioHelpers.WithVariation(0.05f, _robustRandom)); - } + // Start buzzing + emitBuzzOnCritComponent.LastBuzzPopupTime = _gameTiming.CurTime; + _popupSystem.PopupEntity(Loc.GetString("silicon-behavior-buzz"), uid); + Spawn("EffectSparks", Transform(uid).Coordinates); + _audio.PlayPvs(emitBuzzOnCritComponent.Sound, uid, AudioHelpers.WithVariation(0.05f, _robustRandom)); } } - } diff --git a/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs b/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs index 5eb4f25c49b..6efb51096c3 100644 --- a/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs +++ b/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs @@ -1,4 +1,3 @@ -using Content.Shared.Containers.ItemSlots; using Content.Shared.Lock; using Content.Shared.Radio.Components; using Content.Shared.Radio.EntitySystems; @@ -8,7 +7,6 @@ namespace Content.Server.Silicon.EncryptionHolderRequiresLock; public sealed class EncryptionHolderRequiresLockSystem : EntitySystem { - [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; [Dependency] private readonly EncryptionKeySystem _encryptionKeySystem = default!; /// @@ -16,11 +14,11 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(LockToggled); - } + private void LockToggled(EntityUid uid, EncryptionHolderRequiresLockComponent component, LockToggledEvent args) { - if (!TryComp(uid, out var lockComp) + if (!TryComp(uid, out var lockComp) || !TryComp(uid, out var keyHolder)) return; diff --git a/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs b/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs index eb01409e854..7f5d216c92f 100644 --- a/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs +++ b/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs @@ -1,39 +1,31 @@ -using Content.Shared.Preferences; using Content.Shared.Roles; using Content.Shared.Radio.Components; using Content.Shared.Containers; using Robust.Shared.Containers; +using Content.Server.Cargo.Components; - // Pretty much copied from StationSpawningSystem.SpawnStartingGear namespace Content.Server.Silicon.IPC; -public static class InternalEncryptionKeySpawner +public sealed partial class InternalEncryptionKeySpawner : EntitySystem { - public static void TryInsertEncryptionKey(EntityUid target, StartingGearPrototype startingGear, IEntityManager entityManager, HumanoidCharacterProfile? profile) + [Dependency] private readonly SharedContainerSystem _container = default!; + public void TryInsertEncryptionKey(EntityUid target, StartingGearPrototype startingGear, IEntityManager entityManager) { - if (entityManager.TryGetComponent(target, out var keyHolderComp)) - { - var earEquipString = startingGear.GetGear("ears", profile); - var containerMan = entityManager.System(); + if (!TryComp(target, out var keyHolderComp) + || !startingGear.Equipment.TryGetValue("ears", out var earEquipString) + || string.IsNullOrEmpty(earEquipString)) + return; - if (!string.IsNullOrEmpty(earEquipString)) - { - var earEntity = entityManager.SpawnEntity(earEquipString, entityManager.GetComponent(target).Coordinates); + var earEntity = entityManager.SpawnEntity(earEquipString, entityManager.GetComponent(target).Coordinates); + if (!entityManager.HasComponent(earEntity) + || !entityManager.TryGetComponent(earEntity, out var fillComp) + || !fillComp.Containers.TryGetValue(EncryptionKeyHolderComponent.KeyContainerName, out var defaultKeys)) + return; - if (entityManager.TryGetComponent(earEntity, out _) && // I had initially wanted this to spawn the headset, and simply move all the keys over, but the headset didn't seem to have any keys in it when spawned... - entityManager.TryGetComponent(earEntity, out var fillComp) && - fillComp.Containers.TryGetValue(EncryptionKeyHolderComponent.KeyContainerName, out var defaultKeys)) - { - containerMan.CleanContainer(keyHolderComp.KeyContainer); + _container.CleanContainer(keyHolderComp.KeyContainer); - foreach (var key in defaultKeys) - { - var keyEntity = entityManager.SpawnEntity(key, entityManager.GetComponent(target).Coordinates); - containerMan.Insert(keyEntity, keyHolderComp.KeyContainer, force: true); - } - } + foreach (var key in defaultKeys) + entityManager.SpawnInContainerOrDrop(key, target, keyHolderComp.KeyContainer.ID, out _); - entityManager.QueueDeleteEntity(earEntity); - } - } + entityManager.QueueDeleteEntity(earEntity); } } diff --git a/Content.Server/Silicon/Systems/SiliconMiscSystem.cs b/Content.Server/Silicon/Systems/SiliconMiscSystem.cs deleted file mode 100644 index 85d76c77bc8..00000000000 --- a/Content.Server/Silicon/Systems/SiliconMiscSystem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Silicon.Components; -using Content.Shared.Bed.Sleep; - -namespace Content.Server.Silicon.Misc; - -// This entire thing is fucking stupid and I hate it. -public sealed class SiliconMiscSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnTryingToSleep); - } - - private void OnTryingToSleep(EntityUid uid, SiliconComponent component, ref TryingToSleepEvent args) - { - args.Cancelled = true; - } -} diff --git a/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs b/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs index 115e9308707..9f4b6c3483f 100644 --- a/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs +++ b/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs @@ -1,9 +1,5 @@ -using Content.Shared.Damage; -using Content.Shared.Tools; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +namespace Content.Server.Silicon.WeldingHealable; + +[RegisterComponent] +public sealed partial class WeldingHealableComponent : Component { } -namespace Content.Server.Silicon.WeldingHealable -{ - [RegisterComponent] - public sealed partial class WeldingHealableComponent : Component { } -} diff --git a/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs b/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs index 09a2ee99fcd..e0783db0c8b 100644 --- a/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs +++ b/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs @@ -1,121 +1,102 @@ -using System.Diagnostics; using Content.Server.Silicon.WeldingHealing; -using Content.Server.Administration.Logs; -using Content.Server.Stack; using Content.Server.Tools.Components; using Content.Shared.Silicon.WeldingHealing; -using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; -using Content.Shared.Database; -using Content.Shared.Eye.Blinding.Components; -using Content.Shared.Eye.Blinding.Systems; -using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Popups; -using Content.Shared.Tools; -using Content.Shared.Stacks; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; -namespace Content.Server.Silicon.WeldingHealable -{ - public sealed class WeldingHealableSystem : SharedWeldingHealableSystem - { - [Dependency] private readonly SharedToolSystem _toolSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly IAdminLogManager _adminLogger= default!; - [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer= default!; - - public override void Initialize() - { - SubscribeLocalEvent(Repair); - SubscribeLocalEvent(OnRepairFinished); - } - - private void OnRepairFinished(EntityUid uid, WeldingHealableComponent healableComponentcomponent, SiliconRepairFinishedEvent args) - { - if (args.Cancelled || args.Used == null - || !EntityManager.TryGetComponent(args.Target, out DamageableComponent? damageable) - || !EntityManager.TryGetComponent(args.Used, out WeldingHealingComponent? component) - || damageable.DamageContainerID != null - && !component.DamageContainers.Contains(damageable.DamageContainerID)) - return; - - var damageChanged = _damageableSystem.TryChangeDamage(uid, component.Damage, true, false, origin: args.User); - - - if (!HasDamage(damageable, component)) - return; - - if (TryComp(args.Used, out WelderComponent? welder) && - TryComp(args.Used, out SolutionContainerManagerComponent? solutionContainer)) - { - if (!_solutionContainer.ResolveSolution(((EntityUid) args.Used, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var solution)) - return; - _solutionContainer.RemoveReagent(welder.FuelSolution.Value, welder.FuelReagent, component.FuelCost); - } - - var str = Loc.GetString("comp-repairable-repair", - ("target", uid), - ("tool", args.Used!)); - _popup.PopupEntity(str, uid, args.User); - - - if (args.Used.HasValue) - { - args.Handled = _toolSystem.UseTool(args.Used.Value, args.User, uid, args.Delay, component.QualityNeeded, new SiliconRepairFinishedEvent - { - Delay = args.Delay - }); - } - } - - +namespace Content.Server.Silicon.WeldingHealable; - private async void Repair(EntityUid uid, WeldingHealableComponent healableComponent, InteractUsingEvent args) - { - if (args.Handled - || !EntityManager.TryGetComponent(args.Used, out WeldingHealingComponent? component) - || !EntityManager.TryGetComponent(args.Target, out DamageableComponent? damageable) - || damageable.DamageContainerID != null - && !component.DamageContainers.Contains(damageable.DamageContainerID) - || !HasDamage(damageable, component) - || !_toolSystem.HasQuality(args.Used, component.QualityNeeded)) - return; +public sealed class WeldingHealableSystem : SharedWeldingHealableSystem +{ + [Dependency] private readonly SharedToolSystem _toolSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; - float delay = component.DoAfterDelay; + public override void Initialize() + { + SubscribeLocalEvent(Repair); + SubscribeLocalEvent(OnRepairFinished); + } - // Add a penalty to how long it takes if the user is repairing itself - if (args.User == args.Target) + private void OnRepairFinished(EntityUid uid, WeldingHealableComponent healableComponent, SiliconRepairFinishedEvent args) + { + if (args.Cancelled || args.Used == null + || !TryComp(args.Target, out var damageable) + || !TryComp(args.Used, out var component) + || damageable.DamageContainerID is null + || !component.DamageContainers.Contains(damageable.DamageContainerID) + || !HasDamage(damageable, component) + || !TryComp(args.Used, out var welder) + || !TryComp(args.Used, out var solutionContainer) + || !_solutionContainer.ResolveSolution(((EntityUid) args.Used, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution)) + return; + + _damageableSystem.TryChangeDamage(uid, component.Damage, true, false, origin: args.User); + + _solutionContainer.RemoveReagent(welder.FuelSolution.Value, welder.FuelReagent, component.FuelCost); + + var str = Loc.GetString("comp-repairable-repair", + ("target", uid), + ("tool", args.Used!)); + _popup.PopupEntity(str, uid, args.User); + + if (!args.Used.HasValue) + return; + + args.Handled = _toolSystem.UseTool + (args.Used.Value, + args.User, + uid, + args.Delay, + component.QualityNeeded, + new SiliconRepairFinishedEvent { - if (!component.AllowSelfHeal) - return; - - delay *= component.SelfHealPenalty; - } + Delay = args.Delay + }); + } - // Run the repairing doafter - args.Handled = _toolSystem.UseTool(args.Used, args.User, args.Target, delay, component.QualityNeeded, new SiliconRepairFinishedEvent + private async void Repair(EntityUid uid, WeldingHealableComponent healableComponent, InteractUsingEvent args) + { + if (args.Handled + || !EntityManager.TryGetComponent(args.Used, out WeldingHealingComponent? component) + || !EntityManager.TryGetComponent(args.Target, out DamageableComponent? damageable) + || damageable.DamageContainerID is null + || !component.DamageContainers.Contains(damageable.DamageContainerID) + || !HasDamage(damageable, component) + || !_toolSystem.HasQuality(args.Used, component.QualityNeeded) + || args.User == args.Target && !component.AllowSelfHeal) + return; + + float delay = args.User == args.Target + ? component.DoAfterDelay * component.SelfHealPenalty + : component.DoAfterDelay; + + args.Handled = _toolSystem.UseTool + (args.Used, + args.User, + args.Target, + delay, + component.QualityNeeded, + new SiliconRepairFinishedEvent { Delay = delay, }); + } - } - private bool HasDamage(DamageableComponent component, WeldingHealingComponent healable) - { - var damageableDict = component.Damage.DamageDict; - var healingDict = healable.Damage.DamageDict; - foreach (var type in healingDict) - { - if (damageableDict[type.Key].Value > 0) - { - return true; - } - } - + private bool HasDamage(DamageableComponent component, WeldingHealingComponent healable) + { + if (healable.Damage.DamageDict is null) return false; - } + + foreach (var type in healable.Damage.DamageDict) + if (component.Damage.DamageDict[type.Key].Value > 0) + return true; + return false; } } + diff --git a/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs b/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs index c381d547180..a7aa1707938 100644 --- a/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs +++ b/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs @@ -2,47 +2,46 @@ using Content.Shared.Tools; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Silicon.WeldingHealing +namespace Content.Server.Silicon.WeldingHealing; + +[RegisterComponent] +public sealed partial class WeldingHealingComponent : Component { - [RegisterComponent] - public sealed partial class WeldingHealingComponent : Component - { - /// - /// All the damage to change information is stored in this . - /// - /// - /// If this data-field is specified, it will change damage by this amount instead of setting all damage to 0. - /// in order to heal/repair the damage values have to be negative. - /// - - [DataField(required: true)] - public DamageSpecifier Damage; - - [DataField(customTypeSerializer:typeof(PrototypeIdSerializer))] - public string QualityNeeded = "Welding"; - - /// - /// The fuel amount needed to repair physical related damage - /// - [DataField] - public int FuelCost = 5; - - [DataField] - public int DoAfterDelay = 3; - - /// - /// A multiplier that will be applied to the above if an entity is repairing themselves. - /// - [DataField] - public float SelfHealPenalty = 3f; - - /// - /// Whether or not an entity is allowed to repair itself. - /// - [DataField] - public bool AllowSelfHeal = true; - - [DataField(required: true)] - public List DamageContainers; - } + /// + /// All the damage to change information is stored in this . + /// + /// + /// If this data-field is specified, it will change damage by this amount instead of setting all damage to 0. + /// in order to heal/repair the damage values have to be negative. + /// + + [DataField(required: true)] + public DamageSpecifier Damage; + + [DataField(customTypeSerializer:typeof(PrototypeIdSerializer))] + public string QualityNeeded = "Welding"; + + /// + /// The fuel amount needed to repair physical related damage + /// + [DataField] + public int FuelCost = 5; + + [DataField] + public int DoAfterDelay = 3; + + /// + /// A multiplier that will be applied to the above if an entity is repairing themselves. + /// + [DataField] + public float SelfHealPenalty = 3f; + + /// + /// Whether or not an entity is allowed to repair itself. + /// + [DataField] + public bool AllowSelfHeal = true; + + [DataField(required: true)] + public List DamageContainers; } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 61fc1e0a51b..92f16de5c73 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -47,7 +47,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; - + [Dependency] private readonly InternalEncryptionKeySpawner _internalEncryption = default!; [Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!; [Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!; @@ -176,7 +176,7 @@ public EntityUid SpawnPlayerMob( EquipStartingGear(entity.Value, startingGear); if (profile != null) EquipIdCard(entity.Value, profile.Name, prototype, station); - InternalEncryptionKeySpawner.TryInsertEncryptionKey(entity.Value, startingGear, EntityManager, profile); // Parkstation - IPC + _internalEncryption.TryInsertEncryptionKey(entity.Value, startingGear, EntityManager); } if (profile != null) diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs index 9606540a74a..875451b5a14 100644 --- a/Content.Shared/Lock/LockComponent.cs +++ b/Content.Shared/Lock/LockComponent.cs @@ -14,88 +14,82 @@ namespace Content.Shared.Lock; public sealed partial class LockComponent : Component { /// - /// Whether or not the lock is locked. + /// Whether or not the lock is locked. /// - [DataField("locked"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public bool Locked = true; + [DataField, AutoNetworkedField] + public bool Locked = true; /// - /// Whether or not the lock is toggled by simply clicking. + /// Whether or not the lock is toggled by simply clicking. /// - [DataField("lockOnClick"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool LockOnClick; /// - /// Whether or not the lock is unlocked by simply clicking. + /// Whether or not the lock is unlocked by simply clicking. /// - [DataField("unlockOnClick"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool UnlockOnClick = true; /// - /// The sound played when unlocked. + /// The sound played when unlocked. /// - [DataField("unlockingSound"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg") { Params = AudioParams.Default.WithVolume(-5f), }; /// - /// The sound played when locked. + /// The sound played when locked. /// - [DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg") { Params = AudioParams.Default.WithVolume(-5f) }; /// - /// Whether or not an emag disables it. + /// Whether or not an emag disables it. /// - [DataField("breakOnEmag")] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool BreakOnEmag = true; /// - /// Amount of do-after time needed to lock the entity. + /// Amount of do-after time needed to lock the entity. /// /// - /// If set to zero, no do-after will be used. + /// If set to zero, no do-after will be used. /// - [DataField] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public TimeSpan LockTime; /// - /// Amount of do-after time needed to unlock the entity. + /// Amount of do-after time needed to unlock the entity. /// /// - /// If set to zero, no do-after will be used. + /// If set to zero, no do-after will be used. /// - [DataField] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public TimeSpan UnlockTime; } /// -/// Event raised on the lock when a toggle is attempted. -/// Can be cancelled to prevent it. +/// Event raised on the lock when a toggle is attempted. +/// Can be cancelled to prevent it. /// [ByRefEvent] public record struct LockToggleAttemptEvent(EntityUid User, bool Silent = false, bool Cancelled = false); /// -/// Event raised on a lock after it has been toggled. +/// Event raised on a lock after it has been toggled. /// [ByRefEvent] public readonly record struct LockToggledEvent(bool Locked); /// -/// Used to lock a lockable entity that has a lock time configured. +/// Used to lock a lockable entity that has a lock time configured. /// /// /// @@ -109,7 +103,7 @@ public override DoAfterEvent Clone() } /// -/// Used to unlock a lockable entity that has an unlock time configured. +/// Used to unlock a lockable entity that has an unlock time configured. /// /// /// diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index b74f17b1525..bd533663876 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -54,7 +54,6 @@ private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup a private void OnActivated(EntityUid uid, LockComponent lockComp, ActivateInWorldEvent args) { - if (args.Handled) return; diff --git a/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs b/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs index 6c50acb9660..d84a1618602 100644 --- a/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs +++ b/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs @@ -15,27 +15,22 @@ public sealed partial class EncryptionKeyHolderComponent : Component /// /// Whether or not encryption keys can be removed from the headset. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keysUnlocked")] + [DataField] public bool KeysUnlocked = true; /// /// The tool required to extract the encryption keys from the headset. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keysExtractionMethod", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string KeysExtractionMethod = "Screwing"; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keySlots")] + [DataField] public int KeySlots = 2; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keyExtractionSound")] + [DataField] public SoundSpecifier KeyExtractionSound = new SoundPathSpecifier("/Audio/Items/pistol_magout.ogg"); - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keyInsertionSound")] + [DataField] public SoundSpecifier KeyInsertionSound = new SoundPathSpecifier("/Audio/Items/pistol_magin.ogg"); [ViewVariables] @@ -45,8 +40,7 @@ public sealed partial class EncryptionKeyHolderComponent : Component /// /// Whether or not the headset can be examined to see the encryption keys while the keys aren't accessible. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("examineWhileLocked")] + [DataField] public bool ExamineWhileLocked = true; /// diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index 712debbafa0..a15b6f76e90 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -6,10 +6,8 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Radio.Components; -using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Wires; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -175,13 +173,9 @@ private void OnStartup(EntityUid uid, EncryptionKeyHolderComponent component, Co private void OnHolderExamined(EntityUid uid, EncryptionKeyHolderComponent component, ExaminedEvent args) { - if (!args.IsInDetailsRange) - return; - - if (!component.ExamineWhileLocked && !component.KeysUnlocked) - return; - - if (!component.ExamineWhileLocked && TryComp(uid, out var panel) && !panel.Open) + if (!args.IsInDetailsRange + || !component.ExamineWhileLocked && !component.KeysUnlocked + || !component.ExamineWhileLocked && TryComp(uid, out var panel) && !panel.Open) return; if (component.KeyContainer.ContainedEntities.Count == 0) diff --git a/Content.Shared/Silicon/BatteryDrinkerEvent.cs b/Content.Shared/Silicon/BatteryDrinkerEvent.cs index 4d9a610055d..99af03df3a1 100644 --- a/Content.Shared/Silicon/BatteryDrinkerEvent.cs +++ b/Content.Shared/Silicon/BatteryDrinkerEvent.cs @@ -6,7 +6,5 @@ namespace Content.Shared.Silicon; [Serializable, NetSerializable] public sealed partial class BatteryDrinkerDoAfterEvent : SimpleDoAfterEvent { - public BatteryDrinkerDoAfterEvent() - { - } + public BatteryDrinkerDoAfterEvent() { } } diff --git a/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs b/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs index be4be9e5d38..bfc5092b644 100644 --- a/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs +++ b/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs @@ -6,8 +6,6 @@ namespace Content.Shared.Silicon.BlindHealing; public abstract partial class SharedBlindHealingSystem : EntitySystem { [Serializable, NetSerializable] - protected sealed partial class HealingDoAfterEvent : SimpleDoAfterEvent - { - } + protected sealed partial class HealingDoAfterEvent : SimpleDoAfterEvent { } } diff --git a/Content.Shared/Silicon/Components/SiliconComponent.cs b/Content.Shared/Silicon/Components/SiliconComponent.cs index c80d9397d93..bcee4d161a5 100644 --- a/Content.Shared/Silicon/Components/SiliconComponent.cs +++ b/Content.Shared/Silicon/Components/SiliconComponent.cs @@ -1,7 +1,6 @@ using Robust.Shared.GameStates; using Content.Shared.Silicon.Systems; using Robust.Shared.Serialization.TypeSerializers.Implementations; -using Robust.Shared.Containers; using Robust.Shared.Prototypes; using Content.Shared.Alert; @@ -36,7 +35,7 @@ public sealed partial class SiliconComponent : Component /// /// Is the Silicon currently dead? /// - public bool Dead = false; + public bool Dead; // BatterySystem took issue with how this was used, so I'm coming back to it at a later date, when more foundational Silicon stuff is implemented. // /// @@ -52,7 +51,7 @@ public sealed partial class SiliconComponent : Component /// Any new types of Silicons should be added to the enum. /// Setting this to Npc will delay charge state updates by LastDrainTime and skip battery heat calculations /// - [DataField("entityType", customTypeSerializer: typeof(EnumSerializer))] + [DataField(customTypeSerializer: typeof(EnumSerializer))] public Enum EntityType = SiliconType.Npc; /// @@ -61,13 +60,13 @@ public sealed partial class SiliconComponent : Component /// /// If true, should go along with a battery component. One will not be added automatically. /// - [DataField("batteryPowered"), ViewVariables(VVAccess.ReadWrite)] - public bool BatteryPowered = false; + [DataField] + public bool BatteryPowered; /// /// How much power is drained by this Silicon every second by default. /// - [DataField("drainPerSecond"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float DrainPerSecond = 50f; @@ -79,15 +78,15 @@ public sealed partial class SiliconComponent : Component /// Setting a value to null will disable that state. /// Setting Critical to 0 will cause the Silicon to never enter the dead state. /// - [DataField("chargeThresholdMid"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float? ChargeThresholdMid = 0.5f; /// - [DataField("chargeThresholdLow"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float? ChargeThresholdLow = 0.25f; /// - [DataField("chargeThresholdCritical"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float? ChargeThresholdCritical = 0.1f; [DataField] @@ -100,9 +99,16 @@ public sealed partial class SiliconComponent : Component /// /// The amount the Silicon will be slowed at each charge state. /// - [DataField("speedModifierThresholds", required: true)] - public Dictionary SpeedModifierThresholds = default!; + [DataField(required: true)] + public Dictionary SpeedModifierThresholds = default!; - [DataField("fireStackMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float FireStackMultiplier = 1f; + + /// + /// Whether or not a Silicon will cancel all sleep events. + /// Maybe you want an android that can sleep as well as drink APCs? I'm not going to judge. + /// + [DataField] + public bool DoSiliconsDreamOfElectricSheep; } diff --git a/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs b/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs index 3390a76439b..9c2e5baf573 100644 --- a/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs +++ b/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs @@ -3,26 +3,26 @@ namespace Content.Shared.Silicon.DeadStartupButton; /// -/// This is used for... +/// This is used for Silicon entities such as IPCs, Cyborgs, Androids, anything "living" with a button people can touch. /// [RegisterComponent] public sealed partial class DeadStartupButtonComponent : Component { - [DataField("verbText")] + [DataField] public string VerbText = "dead-startup-button-verb"; - [DataField("sound")] + [DataField] public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Arcade/newgame.ogg"); - [DataField("buttonSound")] + [DataField] public SoundSpecifier ButtonSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); - [DataField("doAfterInterval"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float DoAfterInterval = 1f; - [DataField("buzzSound")] + [DataField] public SoundSpecifier BuzzSound = new SoundCollectionSpecifier("buzzes"); - [DataField("verbPriority"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public int VerbPriority = 1; } diff --git a/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs b/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs index 2faa6dfde00..605ca48e6cb 100644 --- a/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs +++ b/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.DoAfter; -using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Verbs; using Robust.Shared.Audio.Systems; @@ -10,19 +9,16 @@ namespace Content.Shared.Silicon.DeadStartupButton; /// -/// This creates a Button that can be activated after an entity, usually a silicon or an IPC, died. -/// This will activate a doAfter and then revive the entity, playing a custom afterward sound. +/// This creates a Button that can be activated after an entity, usually a silicon or an IPC, died. +/// This will activate a doAfter and then revive the entity, playing a custom afterward sound. /// -public partial class SharedDeadStartupButtonSystem : EntitySystem +public abstract partial class SharedDeadStartupButtonSystem : EntitySystem { [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly INetManager _net = default!; - - - /// public override void Initialize() { @@ -31,10 +27,8 @@ public override void Initialize() private void AddTurnOnVerb(EntityUid uid, DeadStartupButtonComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || args.Hands == null) - return; - - if (!TryComp(uid, out MobStateComponent? mobStateComponent) || !_mobState.IsDead(uid, mobStateComponent)) + if (!_mobState.IsDead(uid) + || !args.CanAccess || !args.CanInteract || args.Hands == null) return; args.Verbs.Add(new AlternativeVerb() @@ -50,6 +44,7 @@ private void TryStartup(EntityUid user, EntityUid target, DeadStartupButtonCompo { if (!_net.IsServer) return; + _audio.PlayPvs(comp.ButtonSound, target); var args = new DoAfterArgs(EntityManager, user, comp.DoAfterInterval, new OnDoAfterButtonPressedEvent(), target, target:target) { @@ -60,9 +55,5 @@ private void TryStartup(EntityUid user, EntityUid target, DeadStartupButtonCompo } [Serializable, NetSerializable] - public sealed partial class OnDoAfterButtonPressedEvent : SimpleDoAfterEvent - { - } - - + public sealed partial class OnDoAfterButtonPressedEvent : SimpleDoAfterEvent { } } diff --git a/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs b/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs index a8362610e25..986292551d0 100644 --- a/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs +++ b/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs @@ -1,26 +1,26 @@ -using System.ComponentModel.DataAnnotations; using Robust.Shared.Audio; namespace Content.Shared.Silicon.EmitBuzzWhileDamaged; /// -/// This is used for controlling the cadence of the buzzing emitted by EmitBuzzOnCritSystem. -/// This component is used by mechanical species that can get to critical health. +/// This is used for controlling the cadence of the buzzing emitted by EmitBuzzOnCritSystem. +/// This component is used by mechanical species that can get to critical health. /// [RegisterComponent] public sealed partial class EmitBuzzWhileDamagedComponent : Component { - [DataField("buzzPopupCooldown")] - public TimeSpan BuzzPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8); + [DataField] + public TimeSpan BuzzPopupCooldown = TimeSpan.FromSeconds(8); [ViewVariables] public TimeSpan LastBuzzPopupTime; - [DataField("cycleDelay")] + [DataField] public float CycleDelay = 2.0f; + [ViewVariables] public float AccumulatedFrametime; - [DataField("sound")] + [DataField] public SoundSpecifier Sound = new SoundCollectionSpecifier("buzzes"); } diff --git a/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs b/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs index aab9b6e7522..8fe87e162bc 100644 --- a/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs +++ b/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs @@ -1,17 +1,17 @@ using Content.Shared.Silicon.Components; using Content.Shared.Alert; -using Robust.Shared.Serialization; -using Content.Shared.Movement.Systems; +using Content.Shared.Bed.Sleep; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Movement.Systems; using Content.Shared.PowerCell.Components; +using Robust.Shared.Serialization; namespace Content.Shared.Silicon.Systems; - public sealed class SharedSiliconChargeSystem : EntitySystem { [Dependency] private readonly AlertsSystem _alertsSystem = default!; - [Dependency] protected readonly ItemSlotsSystem ItemSlots = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; public override void Initialize() { @@ -22,47 +22,50 @@ public override void Initialize() SubscribeLocalEvent(OnRefreshMovespeed); SubscribeLocalEvent(OnItemSlotInsertAttempt); SubscribeLocalEvent(OnItemSlotEjectAttempt); + SubscribeLocalEvent(OnTryingToSleep); } - private void OnItemSlotInsertAttempt(EntityUid uid, SiliconComponent component, ref ItemSlotInsertAttemptEvent args) + /// + /// Silicon entities can now also be Living player entities. We may want to prevent them from sleeping if they can't sleep. + /// + private void OnTryingToSleep(EntityUid uid, SiliconComponent component, ref TryingToSleepEvent args) { - if (args.Cancelled) - return; - - if (!TryComp(uid, out var cellSlotComp)) - return; + args.Cancelled = !component.DoSiliconsDreamOfElectricSheep; + } - if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot) + private void OnItemSlotInsertAttempt(EntityUid uid, SiliconComponent component, ref ItemSlotInsertAttemptEvent args) + { + if (args.Cancelled + || !TryComp(uid, out var cellSlotComp) + || !_itemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) + || cellSlot != args.Slot || args.User != uid) return; - if (args.User == uid) - args.Cancelled = true; + args.Cancelled = true; } private void OnItemSlotEjectAttempt(EntityUid uid, SiliconComponent component, ref ItemSlotEjectAttemptEvent args) { - if (args.Cancelled) - return; - - if (!TryComp(uid, out var cellSlotComp)) - return; - - if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot) + if (args.Cancelled + || !TryComp(uid, out var cellSlotComp) + || !_itemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) + || cellSlot != args.Slot || args.User != uid) return; - if (args.User == uid) - args.Cancelled = true; + args.Cancelled = true; } private void OnSiliconInit(EntityUid uid, SiliconComponent component, ComponentInit args) { - if (component.BatteryPowered) - _alertsSystem.ShowAlert(uid, AlertType.BorgBattery, component.ChargeState); + if (!component.BatteryPowered) + return; + + _alertsSystem.ShowAlert(uid, AlertType.BorgBattery, component.ChargeState); } private void OnSiliconChargeStateUpdate(EntityUid uid, SiliconComponent component, SiliconChargeStateUpdateEvent ev) { - _alertsSystem.ShowAlert(uid, AlertType.BorgBattery, (short) ev.ChargePercent); + _alertsSystem.ShowAlert(uid, AlertType.BorgBattery, ev.ChargePercent); } private void OnRefreshMovespeed(EntityUid uid, SiliconComponent component, RefreshMovementSpeedModifiersEvent args) @@ -70,17 +73,12 @@ private void OnRefreshMovespeed(EntityUid uid, SiliconComponent component, Refre if (!component.BatteryPowered) return; - var speedModThresholds = component.SpeedModifierThresholds; - - var closest = 0f; - - foreach (var state in speedModThresholds) - { - if (component.ChargeState >= state.Key && (float) state.Key > closest) - closest = (float) state.Key; - } + var closest = 0; + foreach (var state in component.SpeedModifierThresholds) + if (component.ChargeState >= state.Key && state.Key > closest) + closest = state.Key; - var speedMod = speedModThresholds[(short) closest]; + var speedMod = component.SpeedModifierThresholds[closest]; args.ModifySpeed(speedMod, speedMod); } diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 56a8a846b07..9e933181034 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -49,7 +49,6 @@ public void EquipStartingGear(EntityUid entity, StartingGearPrototype? startingG var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, EntityManager.GetComponent(entity).Coordinates); InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, true, force:true); } - } if (TryComp(entity, out HandsComponent? handsComponent)) From ca88477c46b910140073b91946832c3196cf952c Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Tue, 17 Sep 2024 23:43:07 +0000 Subject: [PATCH 12/24] Automatic Changelog Update (#921) --- Resources/Changelog/Changelog.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 66a257b98b2..72a55cd6271 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6413,3 +6413,10 @@ Entries: id: 6366 time: '2024-09-17T23:39:57.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/925 +- author: zelezniciar + changes: + - type: Tweak + message: The fireaxe once again can pry subfloors + id: 6367 + time: '2024-09-17T23:40:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/921 From 46cbafabeb723478796b4a3f046ced6b80d455ad Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 21:16:56 -0400 Subject: [PATCH 13/24] Filespace ContestsSystem.cs (#931) # Description I CAN'T BELIEVE I MISSED THIS. --- Content.Shared/Contests/ContestsSystem.cs | 921 +++++++++++----------- 1 file changed, 460 insertions(+), 461 deletions(-) diff --git a/Content.Shared/Contests/ContestsSystem.cs b/Content.Shared/Contests/ContestsSystem.cs index 35a52d7fc3c..4dc89e2bbc9 100644 --- a/Content.Shared/Contests/ContestsSystem.cs +++ b/Content.Shared/Contests/ContestsSystem.cs @@ -7,468 +7,467 @@ using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; -namespace Content.Shared.Contests +namespace Content.Shared.Contests; + +public sealed partial class ContestsSystem : EntitySystem { - public sealed partial class ContestsSystem : EntitySystem + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + + /// + /// The presumed average mass of a player entity + /// Defaulted to the average mass of an adult human + /// + private const float AverageMass = 71f; + + /// + /// The presumed average sum of a Psionic's Baseline Amplification and Baseline Dampening. + /// Since Baseline casting stats are a random value between 0.4 and 1.2, this is defaulted to 0.8 + 0.8. + /// + private const float AveragePsionicPotential = 1.6f; + + #region Mass Contests + /// + /// Outputs the ratio of mass between a performer and the average human mass + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(EntityUid performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || performerPhysics.Mass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass / otherMass + : Math.Clamp(performerPhysics.Mass / otherMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// + /// MaybeMassContest, in case your entity doesn't exist + /// + public float MassContest(EntityUid? performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerUid is null) + return 1f; + + return MassContest(performerUid.Value, bypassClamp, rangeFactor, otherMass); + } + + /// + /// Outputs the ratio of mass between a performer and the average human mass + /// If a function already has the performer's physics component, this is faster + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(PhysicsComponent performerPhysics, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerPhysics.Mass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass / otherMass + : Math.Clamp(performerPhysics.Mass / otherMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Outputs the ratio of mass between a performer and a target, accepts either EntityUids or PhysicsComponents in any combination + /// If you have physics components already in your function, use instead + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(EntityUid performerUid, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || !TryComp(targetUid, out var targetPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(EntityUid performerUid, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(PhysicsComponent performerPhysics, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(targetUid, out var targetPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(PhysicsComponent performerPhysics, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + #region Stamina Contests + + /// + /// Outputs 1 minus the percentage of an Entity's Stamina, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. + /// This will never return a value >1. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float StaminaContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!TryComp(performer, out var perfStamina) + || perfStamina.StaminaDamage == 0) + return 1f; + + return StaminaContest(perfStamina, bypassClamp, rangeFactor); + } + + /// + public float StaminaContest(StaminaComponent perfStamina, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoStaminaContests)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? 1 - perfStamina.StaminaDamage / perfStamina.CritThreshold + : 1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)); + } + + /// + /// Outputs the ratio of percentage of an Entity's Stamina and a Target Entity's Stamina, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. + /// This does NOT produce the same kind of outputs as a Single-Entity StaminaContest. 2Entity StaminaContest returns the product of two Solo Stamina Contests, and so its values can be very strange. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float StaminaContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoStaminaContests) + || !TryComp(performer, out var perfStamina) + || !TryComp(target, out var targetStamina)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? (1 - perfStamina.StaminaDamage / perfStamina.CritThreshold) + / (1 - targetStamina.StaminaDamage / targetStamina.CritThreshold) + : (1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)) + / (1 - Math.Clamp(targetStamina.StaminaDamage / targetStamina.CritThreshold, 0, 0.25f * rangeFactor))); + } + + #endregion + + #region Health Contests + + /// + /// Outputs 1 minus the percentage of an Entity's Health, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. + /// This will never return a value >1. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float HealthContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoHealthContests) + || !TryComp(performer, out var damage) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var threshold)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? 1 - damage.TotalDamage.Float() / threshold.Value.Float() + : 1 - Math.Clamp(damage.TotalDamage.Float() / threshold.Value.Float(), 0, 0.25f * rangeFactor)); + } + + /// + /// Outputs the ratio of percentage of an Entity's Health and a Target Entity's Health, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. + /// This does NOT produce the same kind of outputs as a Single-Entity HealthContest. 2Entity HealthContest returns the product of two Solo Health Contests, and so its values can be very strange. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float HealthContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoHealthContests) + || !TryComp(performer, out var perfDamage) + || !TryComp(target, out var targetDamage) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var perfThreshold) + || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, out var targetThreshold)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? (1 - perfDamage.TotalDamage.Float() / perfThreshold.Value.Float()) + / (1 - targetDamage.TotalDamage.Float() / targetThreshold.Value.Float()) + : (1 - Math.Clamp(perfDamage.TotalDamage.Float() / perfThreshold.Value.Float(), 0, 0.25f * rangeFactor)) + / (1 - Math.Clamp(targetDamage.TotalDamage.Float() / targetThreshold.Value.Float(), 0, 0.25f * rangeFactor))); + } + #endregion + + #region Mind Contests + + /// + /// Returns the ratio of casting stats between a performer and the presumed average latent psionic. + /// Uniquely among Contests, not being Psionic is not a failure condition, and is instead a variable. + /// If you do not have a PsionicComponent, your combined casting stats are assumed to be 0.1f + /// + /// + /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. + /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. + /// + public float MindContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f, float otherPsion = AveragePsionicPotential) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMindContests)) + return 1f; + + var performerPotential = TryComp(performer, out var performerPsionic) + ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening + : 0.1f; + + if (performerPotential == otherPsion) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPotential / otherPsion + : Math.Clamp(performerPotential / otherPsion, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Returns the ratio of casting stats between a performer and a target. + /// Like with single-Uid MindContests, if an entity does not possess a PsionicComponent, its casting stats are assumed to be 0.1f. + /// + /// + /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. + /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. + /// + public float MindContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMindContests)) + return 1f; + + var performerPotential = TryComp(performer, out var performerPsionic) + ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening + : 0.1f; + + var targetPotential = TryComp(target, out var targetPsionic) + ? targetPsionic.CurrentAmplification + targetPsionic.CurrentDampening + : 0.1f; + + if (performerPotential == targetPotential) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPotential / targetPotential + : Math.Clamp(performerPotential / targetPotential, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + + #region Mood Contests + + /// + /// Outputs the ratio of an Entity's mood level and its Neutral Mood threshold. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MoodContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMoodContests) + || !TryComp(performer, out var mood)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? mood.CurrentMoodLevel / mood.NeutralMoodThreshold + : Math.Clamp(mood.CurrentMoodLevel / mood.NeutralMoodThreshold, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Outputs the ratio of mood level between two Entities. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MoodContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMoodContests) + || !TryComp(performer, out var performerMood) + || !TryComp(target, out var targetMood)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel + : Math.Clamp(performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + + #region EVERY CONTESTS + + /// + /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. + /// + /// + /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. + /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. + /// + public float EveryContest( + EntityUid performer, + bool bypassClampMass = false, + bool bypassClampStamina = false, + bool bypassClampHealth = false, + bool bypassClampMind = false, + bool bypassClampMood = false, + float rangeFactorMass = 1f, + float rangeFactorStamina = 1f, + float rangeFactorHealth = 1f, + float rangeFactorMind = 1f, + float rangeFactorMood = 1f, + float weightMass = 1f, + float weightStamina = 1f, + float weightHealth = 1f, + float weightMind = 1f, + float weightMood = 1f, + bool sumOrMultiply = false) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1f; + + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; + var massMultiplier = weightMass / weightTotal; + var staminaMultiplier = weightStamina / weightTotal; + var healthMultiplier = weightHealth / weightTotal; + var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; + + return sumOrMultiply + ? MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + + StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + + HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + + MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + + MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier + : ContestClamp(MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + * StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + * HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + * MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + * MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier); + } + + /// + /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. + /// + /// + /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. + /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. + /// + public float EveryContest( + EntityUid performer, + EntityUid target, + bool bypassClampMass = false, + bool bypassClampStamina = false, + bool bypassClampHealth = false, + bool bypassClampMind = false, + bool bypassClampMood = false, + float rangeFactorMass = 1f, + float rangeFactorStamina = 1f, + float rangeFactorHealth = 1f, + float rangeFactorMind = 1f, + float rangeFactorMood = 1f, + float weightMass = 1f, + float weightStamina = 1f, + float weightHealth = 1f, + float weightMind = 1f, + float weightMood = 1f, + bool sumOrMultiply = false) { - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; - - /// - /// The presumed average mass of a player entity - /// Defaulted to the average mass of an adult human - /// - private const float AverageMass = 71f; - - /// - /// The presumed average sum of a Psionic's Baseline Amplification and Baseline Dampening. - /// Since Baseline casting stats are a random value between 0.4 and 1.2, this is defaulted to 0.8 + 0.8. - /// - private const float AveragePsionicPotential = 1.6f; - - #region Mass Contests - /// - /// Outputs the ratio of mass between a performer and the average human mass - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float MassContest(EntityUid performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(performerUid, out var performerPhysics) - || performerPhysics.Mass == 0) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPhysics.Mass / otherMass - : Math.Clamp(performerPhysics.Mass / otherMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - /// - /// MaybeMassContest, in case your entity doesn't exist - /// - public float MassContest(EntityUid? performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || performerUid is null) - return 1f; - - return MassContest(performerUid.Value, bypassClamp, rangeFactor, otherMass); - } - - /// - /// Outputs the ratio of mass between a performer and the average human mass - /// If a function already has the performer's physics component, this is faster - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float MassContest(PhysicsComponent performerPhysics, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || performerPhysics.Mass == 0) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPhysics.Mass / otherMass - : Math.Clamp(performerPhysics.Mass / otherMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - /// Outputs the ratio of mass between a performer and a target, accepts either EntityUids or PhysicsComponents in any combination - /// If you have physics components already in your function, use instead - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float MassContest(EntityUid performerUid, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(performerUid, out var performerPhysics) - || !TryComp(targetUid, out var targetPhysics) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - public float MassContest(EntityUid performerUid, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(performerUid, out var performerPhysics) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - public float MassContest(PhysicsComponent performerPhysics, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(targetUid, out var targetPhysics) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - public float MassContest(PhysicsComponent performerPhysics, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - #endregion - #region Stamina Contests - - /// - /// Outputs 1 minus the percentage of an Entity's Stamina, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. - /// This will never return a value >1. - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float StaminaContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!TryComp(performer, out var perfStamina) - || perfStamina.StaminaDamage == 0) - return 1f; - - return StaminaContest(perfStamina, bypassClamp, rangeFactor); - } - - /// - public float StaminaContest(StaminaComponent perfStamina, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoStaminaContests)) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? 1 - perfStamina.StaminaDamage / perfStamina.CritThreshold - : 1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)); - } - - /// - /// Outputs the ratio of percentage of an Entity's Stamina and a Target Entity's Stamina, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. - /// This does NOT produce the same kind of outputs as a Single-Entity StaminaContest. 2Entity StaminaContest returns the product of two Solo Stamina Contests, and so its values can be very strange. - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float StaminaContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoStaminaContests) - || !TryComp(performer, out var perfStamina) - || !TryComp(target, out var targetStamina)) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? (1 - perfStamina.StaminaDamage / perfStamina.CritThreshold) - / (1 - targetStamina.StaminaDamage / targetStamina.CritThreshold) - : (1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)) - / (1 - Math.Clamp(targetStamina.StaminaDamage / targetStamina.CritThreshold, 0, 0.25f * rangeFactor))); - } - - #endregion - - #region Health Contests - - /// - /// Outputs 1 minus the percentage of an Entity's Health, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. - /// This will never return a value >1. - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float HealthContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoHealthContests) - || !TryComp(performer, out var damage) - || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var threshold)) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? 1 - damage.TotalDamage.Float() / threshold.Value.Float() - : 1 - Math.Clamp(damage.TotalDamage.Float() / threshold.Value.Float(), 0, 0.25f * rangeFactor)); - } - - /// - /// Outputs the ratio of percentage of an Entity's Health and a Target Entity's Health, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. - /// This does NOT produce the same kind of outputs as a Single-Entity HealthContest. 2Entity HealthContest returns the product of two Solo Health Contests, and so its values can be very strange. - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float HealthContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoHealthContests) - || !TryComp(performer, out var perfDamage) - || !TryComp(target, out var targetDamage) - || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var perfThreshold) - || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, out var targetThreshold)) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? (1 - perfDamage.TotalDamage.Float() / perfThreshold.Value.Float()) - / (1 - targetDamage.TotalDamage.Float() / targetThreshold.Value.Float()) - : (1 - Math.Clamp(perfDamage.TotalDamage.Float() / perfThreshold.Value.Float(), 0, 0.25f * rangeFactor)) - / (1 - Math.Clamp(targetDamage.TotalDamage.Float() / targetThreshold.Value.Float(), 0, 0.25f * rangeFactor))); - } - #endregion - - #region Mind Contests - - /// - /// Returns the ratio of casting stats between a performer and the presumed average latent psionic. - /// Uniquely among Contests, not being Psionic is not a failure condition, and is instead a variable. - /// If you do not have a PsionicComponent, your combined casting stats are assumed to be 0.1f - /// - /// - /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. - /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. - /// - public float MindContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f, float otherPsion = AveragePsionicPotential) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMindContests)) - return 1f; - - var performerPotential = TryComp(performer, out var performerPsionic) - ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening - : 0.1f; - - if (performerPotential == otherPsion) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPotential / otherPsion - : Math.Clamp(performerPotential / otherPsion, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - /// Returns the ratio of casting stats between a performer and a target. - /// Like with single-Uid MindContests, if an entity does not possess a PsionicComponent, its casting stats are assumed to be 0.1f. - /// - /// - /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. - /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. - /// - public float MindContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMindContests)) - return 1f; - - var performerPotential = TryComp(performer, out var performerPsionic) - ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening - : 0.1f; - - var targetPotential = TryComp(target, out var targetPsionic) - ? targetPsionic.CurrentAmplification + targetPsionic.CurrentDampening - : 0.1f; - - if (performerPotential == targetPotential) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerPotential / targetPotential - : Math.Clamp(performerPotential / targetPotential, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - #endregion - - #region Mood Contests - - /// - /// Outputs the ratio of an Entity's mood level and its Neutral Mood threshold. - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float MoodContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMoodContests) - || !TryComp(performer, out var mood)) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? mood.CurrentMoodLevel / mood.NeutralMoodThreshold - : Math.Clamp(mood.CurrentMoodLevel / mood.NeutralMoodThreshold, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - /// - /// Outputs the ratio of mood level between two Entities. - /// - /// - /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. - /// - public float MoodContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMoodContests) - || !TryComp(performer, out var performerMood) - || !TryComp(target, out var targetMood)) - return 1f; - - return ContestClamp(ContestClampOverride(bypassClamp) - ? performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel - : Math.Clamp(performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); - } - - #endregion - - #region EVERY CONTESTS - - /// - /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. - /// - /// - /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. - /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. - /// - public float EveryContest( - EntityUid performer, - bool bypassClampMass = false, - bool bypassClampStamina = false, - bool bypassClampHealth = false, - bool bypassClampMind = false, - bool bypassClampMood = false, - float rangeFactorMass = 1f, - float rangeFactorStamina = 1f, - float rangeFactorHealth = 1f, - float rangeFactorMind = 1f, - float rangeFactorMood = 1f, - float weightMass = 1f, - float weightStamina = 1f, - float weightHealth = 1f, - float weightMind = 1f, - float weightMood = 1f, - bool sumOrMultiply = false) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem)) - return 1f; - - var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; - var massMultiplier = weightMass / weightTotal; - var staminaMultiplier = weightStamina / weightTotal; - var healthMultiplier = weightHealth / weightTotal; - var mindMultiplier = weightMind / weightTotal; - var moodMultiplier = weightMood / weightTotal; - - return sumOrMultiply - ? MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier - + StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - + HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier - + MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier - + MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier - : ContestClamp(MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier - * StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - * HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier - * MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier - * MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier); - } - - /// - /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. - /// - /// - /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. - /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. - /// - public float EveryContest( - EntityUid performer, - EntityUid target, - bool bypassClampMass = false, - bool bypassClampStamina = false, - bool bypassClampHealth = false, - bool bypassClampMind = false, - bool bypassClampMood = false, - float rangeFactorMass = 1f, - float rangeFactorStamina = 1f, - float rangeFactorHealth = 1f, - float rangeFactorMind = 1f, - float rangeFactorMood = 1f, - float weightMass = 1f, - float weightStamina = 1f, - float weightHealth = 1f, - float weightMind = 1f, - float weightMood = 1f, - bool sumOrMultiply = false) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem)) - return 1f; - - var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; - var massMultiplier = weightMass / weightTotal; - var staminaMultiplier = weightStamina / weightTotal; - var healthMultiplier = weightHealth / weightTotal; - var mindMultiplier = weightMind / weightTotal; - var moodMultiplier = weightMood / weightTotal; - - return sumOrMultiply - ? MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier - + StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - + HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier - + MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier - + MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier - : ContestClamp(MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier - * StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - * HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier - * MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier - * MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier); - } - #endregion + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1f; + + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; + var massMultiplier = weightMass / weightTotal; + var staminaMultiplier = weightStamina / weightTotal; + var healthMultiplier = weightHealth / weightTotal; + var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; + + return sumOrMultiply + ? MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + + StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + + HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + + MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + + MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier + : ContestClamp(MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + * StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + * HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + * MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + * MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier); } + #endregion } From 364b2378bec71ec63a543091a7c80a71680f5d4b Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 18 Sep 2024 00:30:38 -0400 Subject: [PATCH 14/24] Fix Uncloneable Feedback Message (#926) # Description Clone Pod isn't the one that has the ability to say the message, the cloning console does. This fixes a bug where Uncloneable messages aren't being played. # Changelog :cl: - fix: Cloning Consoles will now correctly state when a body has the Uncloneable trait. --- Content.Server/Cloning/CloningSystem.Utility.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.Utility.cs b/Content.Server/Cloning/CloningSystem.Utility.cs index 408e1cf24a3..aa2ce27902c 100644 --- a/Content.Server/Cloning/CloningSystem.Utility.cs +++ b/Content.Server/Cloning/CloningSystem.Utility.cs @@ -68,9 +68,10 @@ private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodCo if (ev.Cancelled && ev.CloningFailMessage is not null) { - _chatSystem.TrySendInGameICMessage(uid, - Loc.GetString(ev.CloningFailMessage), - InGameICChatType.Speak, false); + if (clonePod.ConnectedConsole is not null) + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, + Loc.GetString(ev.CloningFailMessage), + InGameICChatType.Speak, false); return false; } From 4d4afb6b83d354bdaa4dbceb43c69981bc500c70 Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Wed, 18 Sep 2024 04:31:05 +0000 Subject: [PATCH 15/24] Automatic Changelog Update (#926) --- Resources/Changelog/Changelog.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 72a55cd6271..bf97bb66370 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6420,3 +6420,12 @@ Entries: id: 6367 time: '2024-09-17T23:40:13.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/921 +- author: VMSolidus + changes: + - type: Fix + message: >- + Cloning Consoles will now correctly state when a body has the + Uncloneable trait. + id: 6368 + time: '2024-09-18T04:30:39.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/926 From d5f0c550a3bebe1426d76640f7275ebdbe23b84f Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 18 Sep 2024 22:18:35 -0400 Subject: [PATCH 16/24] Better Lying Down System (From White Dream) (#815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Port of https://github.com/WWhiteDreamProject/wwdpublic/pull/2 And now also https://github.com/WWhiteDreamProject/wwdpublic/pull/8 Because Lying Down System is dependent on the Telescope System. # TODO - [x] Reconcile the code with core code, do code cleanup. I'll undraft this when I'm done. Probably not going to be tonight, because I will have to get some sleep soon to get up early for my calculus classes. # Changelog :cl: Spatison (White Dream) - add: Added lying down system / Добавлена система лежания - tweak: Lying down now uses do-afters that are visible to other people to indicate what is going on. - add: Added telescope system / Добавлена система прицеливания - tweak: Now you can aim from Hristov / Теперь можно прицеливаться из Христова --------- Signed-off-by: VMSolidus Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com> Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- Content.Client/Buckle/BuckleSystem.cs | 12 +- Content.Client/Input/ContentContexts.cs | 1 + .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 15 ++ Content.Client/Standing/LayingDownSystem.cs | 69 +++++ Content.Client/Telescope/TelescopeSystem.cs | 128 +++++++++ .../Standing/LayingDownComponent.cs | 14 - Content.Server/Standing/LayingDownSystem.cs | 95 +------ Content.Server/Telescope/TelescopeSystem.cs | 5 + .../Assorted/LayingDownModifierSystem.cs | 6 +- Content.Shared/CCVar/CCVars.cs | 10 + Content.Shared/Input/ContentKeyFunctions.cs | 1 + .../Standing/LayingDownComponent.cs | 26 ++ .../Standing/SharedLayingDownSystem.cs | 155 +++++++++++ .../Standing/StandingStateComponent.cs | 41 +-- .../Standing/StandingStateSystem.cs | 242 +++++++++--------- Content.Shared/Stunnable/SharedStunSystem.cs | 71 +++-- .../Telescope/SharedTelescopeSystem.cs | 110 ++++++++ .../Telescope/TelescopeComponent.cs | 16 ++ .../en-US/escape-menu/ui/options-menu.ftl | 5 + .../Locale/ru-RU/Escape-Menu/options-menu.ftl | 3 + .../Objects/Weapons/Guns/Snipers/snipers.yml | 1 + Resources/keybinds.yml | 3 + 22 files changed, 742 insertions(+), 287 deletions(-) create mode 100644 Content.Client/Standing/LayingDownSystem.cs create mode 100644 Content.Client/Telescope/TelescopeSystem.cs delete mode 100644 Content.Server/Standing/LayingDownComponent.cs create mode 100644 Content.Server/Telescope/TelescopeSystem.cs create mode 100644 Content.Shared/Standing/LayingDownComponent.cs create mode 100644 Content.Shared/Standing/SharedLayingDownSystem.cs create mode 100644 Content.Shared/Telescope/SharedTelescopeSystem.cs create mode 100644 Content.Shared/Telescope/TelescopeComponent.cs create mode 100644 Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index fea18e5cf3c..d4614210d9f 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -50,17 +50,11 @@ private void OnBuckleAfterAutoHandleState(EntityUid uid, BuckleComponent compone private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args) { - if (!TryComp(uid, out var rotVisuals)) + if (!TryComp(uid, out var rotVisuals) + || !Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) + || !buckled || args.Sprite == null) return; - if (!Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || - !buckled || - args.Sprite == null) - { - _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); - return; - } - // Animate strapping yourself to something at a given angle // TODO: Dump this when buckle is better _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f); diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 503a9ac953b..0e56153752d 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -82,6 +82,7 @@ public static void SetupContexts(IInputContextContainer contexts) human.AddFunction(ContentKeyFunctions.Arcade1); human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + human.AddFunction(ContentKeyFunctions.LookUp); // actions should be common (for ghosts, mobs, etc) common.AddFunction(ContentKeyFunctions.OpenActionsMenu); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 198eb5e4fb0..ab4ebd83fa7 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -97,6 +97,12 @@ private void HandleToggleWalk(BaseButton.ButtonToggledEventArgs args) _deferCommands.Add(_inputManager.SaveToUserData); } + private void HandleHoldLookUp(BaseButton.ButtonToggledEventArgs args) + { + _cfg.SetCVar(CCVars.HoldLookUp, args.Pressed); + _cfg.SaveToFile(); + } + private void HandleDefaultWalk(BaseButton.ButtonToggledEventArgs args) { _cfg.SetCVar(CCVars.DefaultWalk, args.Pressed); @@ -109,6 +115,12 @@ private void HandleStaticStorageUI(BaseButton.ButtonToggledEventArgs args) _cfg.SaveToFile(); } + private void HandleToggleAutoGetUp(BaseButton.ButtonToggledEventArgs args) + { + _cfg.SetCVar(CCVars.AutoGetUp, args.Pressed); + _cfg.SaveToFile(); + } + public KeyRebindTab() { IoCManager.InjectDependencies(this); @@ -193,6 +205,9 @@ void AddCheckBox(string checkBoxName, bool currentState, Action(OnMovementInput); + + SubscribeNetworkEvent(OnCheckAutoGetUp); + } + + private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args) + { + if (!_timing.IsFirstTimePredicted + || !_actionBlocker.CanMove(uid) + || _animation.HasRunningAnimation(uid, "rotate") + || !TryComp(uid, out var transform) + || !TryComp(uid, out var sprite) + || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + sprite.Rotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + sprite.Rotation = Angle.FromDegrees(90); + } + + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var uid = GetEntity(ev.User); + + if (!TryComp(uid, out var transform) || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + } +} diff --git a/Content.Client/Telescope/TelescopeSystem.cs b/Content.Client/Telescope/TelescopeSystem.cs new file mode 100644 index 00000000000..ac2270aa971 --- /dev/null +++ b/Content.Client/Telescope/TelescopeSystem.cs @@ -0,0 +1,128 @@ +using System.Numerics; +using Content.Client.Viewport; +using Content.Shared.CCVar; +using Content.Shared.Telescope; +using Content.Shared.Input; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Shared.Configuration; +using Robust.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.Timing; + +namespace Content.Client.Telescope; + +public sealed class TelescopeSystem : SharedTelescopeSystem +{ + [Dependency] private readonly InputSystem _inputSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IInputManager _input = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private ScalingViewport? _viewport; + private bool _holdLookUp; + private bool _toggled; + + public override void Initialize() + { + base.Initialize(); + + _cfg.OnValueChanged(CCVars.HoldLookUp, + val => + { + var input = val ? null : InputCmdHandler.FromDelegate(_ => _toggled = !_toggled); + _input.SetInputCommand(ContentKeyFunctions.LookUp, input); + _holdLookUp = val; + _toggled = false; + }, + true); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + if (_timing.ApplyingState + || !_timing.IsFirstTimePredicted + || !_input.MouseScreenPosition.IsValid) + return; + + var player = _player.LocalEntity; + + var telescope = GetRightTelescope(player); + + if (telescope == null) + { + _toggled = false; + return; + } + + if (!TryComp(player, out var eye)) + return; + + var offset = Vector2.Zero; + + if (_holdLookUp) + { + if (_inputSystem.CmdStates.GetState(ContentKeyFunctions.LookUp) != BoundKeyState.Down) + { + RaiseEvent(offset); + return; + } + } + else if (!_toggled) + { + RaiseEvent(offset); + return; + } + + var mousePos = _input.MouseScreenPosition; + + if (_uiManager.MouseGetControl(mousePos) as ScalingViewport is { } viewport) + _viewport = viewport; + + if (_viewport == null) + return; + + var centerPos = _eyeManager.WorldToScreen(eye.Eye.Position.Position + eye.Offset); + + var diff = mousePos.Position - centerPos; + var len = diff.Length(); + + var size = _viewport.PixelSize; + + var maxLength = Math.Min(size.X, size.Y) * 0.4f; + var minLength = maxLength * 0.2f; + + if (len > maxLength) + { + diff *= maxLength / len; + len = maxLength; + } + + var divisor = maxLength * telescope.Divisor; + + if (len > minLength) + { + diff -= diff * minLength / len; + offset = new Vector2(diff.X / divisor, -diff.Y / divisor); + offset = new Angle(-eye.Rotation.Theta).RotateVec(offset); + } + + RaiseEvent(offset); + } + + private void RaiseEvent(Vector2 offset) + { + RaisePredictiveEvent(new EyeOffsetChangedEvent + { + Offset = offset + }); + } +} diff --git a/Content.Server/Standing/LayingDownComponent.cs b/Content.Server/Standing/LayingDownComponent.cs deleted file mode 100644 index 7921749f148..00000000000 --- a/Content.Server/Standing/LayingDownComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Standing; - -[RegisterComponent] -public sealed partial class LayingDownComponent : Component -{ - [DataField] - public float DownedSpeedMultiplier = 0.15f; - - [DataField] - public TimeSpan Cooldown = TimeSpan.FromSeconds(2.5f); - - [DataField] - public TimeSpan NextToggleAttempt = TimeSpan.Zero; -} diff --git a/Content.Server/Standing/LayingDownSystem.cs b/Content.Server/Standing/LayingDownSystem.cs index 73a929fdfc4..e5054bdd706 100644 --- a/Content.Server/Standing/LayingDownSystem.cs +++ b/Content.Server/Standing/LayingDownSystem.cs @@ -1,101 +1,28 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Input; -using Content.Shared.Movement.Systems; -using Content.Shared.Popups; using Content.Shared.Standing; -using Robust.Shared.Input.Binding; -using Robust.Shared.Player; -using Robust.Shared.Timing; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; namespace Content.Server.Standing; -/// Unfortunately cannot be shared because some standing conditions are server-side only -public sealed class LayingDownSystem : EntitySystem +public sealed class LayingDownSystem : SharedLayingDownSystem { - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly Shared.Standing.StandingStateSystem _standing = default!; // WHY IS THERE TWO DIFFERENT STANDING SYSTEMS?! - [Dependency] private readonly IGameTiming _timing = default!; - + [Dependency] private readonly INetConfigurationManager _cfg = default!; public override void Initialize() { - CommandBinds.Builder - .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding, handle: false, outsidePrediction: false)) - .Register(); - - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(OnRefreshMovementSpeed); - SubscribeLocalEvent(OnParentChanged); - } - - public override void Shutdown() - { - base.Shutdown(); - - CommandBinds.Unregister(); - } + base.Initialize(); - private void DoRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, object args) - { - _movement.RefreshMovementSpeedModifiers(uid); + SubscribeNetworkEvent(OnCheckAutoGetUp); } - private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) { - if (TryComp(uid, out var standingState) && standingState.Standing) - return; + var uid = GetEntity(ev.User); - args.ModifySpeed(component.DownedSpeedMultiplier, component.DownedSpeedMultiplier, bypassImmunity: true); - } - - private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) - { - // If the entity is not on a grid, try to make it stand up to avoid issues - if (!TryComp(uid, out var standingState) - || standingState.Standing - || Transform(uid).GridUid != null) + if (!TryComp(uid, out LayingDownComponent? layingDown)) return; - _standing.Stand(uid, standingState); - } - - private void ToggleStanding(ICommonSession? session) - { - if (session is not { AttachedEntity: { Valid: true } uid } playerSession - || !Exists(uid) - || !TryComp(uid, out var standingState) - || !TryComp(uid, out var layingDown)) - return; - - // If successful, show popup to self and others. Otherwise, only to self. - if (ToggleStandingImpl(uid, standingState, layingDown, out var popupBranch)) - { - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-other", ("entity", uid)), uid, Filter.PvsExcept(uid), true); - layingDown.NextToggleAttempt = _timing.CurTime + layingDown.Cooldown; - } - - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-self", ("entity", uid)), uid, uid); - } - - private bool ToggleStandingImpl(EntityUid uid, StandingStateComponent standingState, LayingDownComponent layingDown, out string popupBranch) - { - var success = layingDown.NextToggleAttempt <= _timing.CurTime; - - if (_standing.IsDown(uid, standingState)) - { - success = success && _standing.Stand(uid, standingState, force: false); - popupBranch = success ? "stand-success" : "stand-fail"; - } - else - { - success = success && Transform(uid).GridUid != null; // Do not allow laying down when not on a surface. - success = success && _standing.Down(uid, standingState: standingState, playSound: true, dropHeldItems: false); - popupBranch = success ? "lay-success" : "lay-fail"; - } - - return success; + layingDown.AutoGetUp = _cfg.GetClientCVar(args.SenderSession.Channel, CCVars.AutoGetUp); + Dirty(uid, layingDown); } } diff --git a/Content.Server/Telescope/TelescopeSystem.cs b/Content.Server/Telescope/TelescopeSystem.cs new file mode 100644 index 00000000000..0e53cc15a20 --- /dev/null +++ b/Content.Server/Telescope/TelescopeSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Telescope; + +namespace Content.Server.Telescope; + +public sealed class TelescopeSystem : SharedTelescopeSystem; diff --git a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs index dc6dcd2de3b..e4a63d6108b 100644 --- a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs +++ b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs @@ -1,5 +1,5 @@ using Content.Server.Traits.Assorted; -using Content.Server.Standing; +using Content.Shared.Standing; namespace Content.Shared.Traits.Assorted.Systems; @@ -16,7 +16,7 @@ private void OnStartup(EntityUid uid, LayingDownModifierComponent component, Com if (!TryComp(uid, out var layingDown)) return; - layingDown.Cooldown *= component.LayingDownCooldownMultiplier; - layingDown.DownedSpeedMultiplier *= component.DownedSpeedMultiplierMultiplier; + layingDown.StandingUpTime *= component.LayingDownCooldownMultiplier; + layingDown.SpeedModify *= component.DownedSpeedMultiplierMultiplier; } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index bad706f8154..27ad9641070 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -2457,6 +2457,16 @@ public static readonly CVarDef #endregion + #region Lying Down System + + public static readonly CVarDef AutoGetUp = + CVarDef.Create("rest.auto_get_up", true, CVar.CLIENT | CVar.ARCHIVE | CVar.REPLICATED); + + public static readonly CVarDef HoldLookUp = + CVarDef.Create("rest.hold_look_up", false, CVar.CLIENT | CVar.ARCHIVE); + + #endregion + #region Material Reclaimer /// diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index dac780783c7..f85983282c7 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -57,6 +57,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction ResetZoom = "ResetZoom"; public static readonly BoundKeyFunction OfferItem = "OfferItem"; public static readonly BoundKeyFunction ToggleStanding = "ToggleStanding"; + public static readonly BoundKeyFunction LookUp = "LookUp"; public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp"; public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown"; diff --git a/Content.Shared/Standing/LayingDownComponent.cs b/Content.Shared/Standing/LayingDownComponent.cs new file mode 100644 index 00000000000..1499704c53b --- /dev/null +++ b/Content.Shared/Standing/LayingDownComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LayingDownComponent : Component +{ + [DataField, AutoNetworkedField] + public float StandingUpTime { get; set; } = 1f; + + [DataField, AutoNetworkedField] + public float SpeedModify { get; set; } = 0.4f; + + [DataField, AutoNetworkedField] + public bool AutoGetUp; +} + +[Serializable, NetSerializable] +public sealed class ChangeLayingDownEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed class CheckAutoGetUpEvent(NetEntity user) : CancellableEntityEventArgs +{ + public NetEntity User = user; +} diff --git a/Content.Shared/Standing/SharedLayingDownSystem.cs b/Content.Shared/Standing/SharedLayingDownSystem.cs new file mode 100644 index 00000000000..47ad01949cb --- /dev/null +++ b/Content.Shared/Standing/SharedLayingDownSystem.cs @@ -0,0 +1,155 @@ +using Content.Shared.DoAfter; +using Content.Shared.Gravity; +using Content.Shared.Input; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +public abstract class SharedLayingDownSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + + public override void Initialize() + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding)) + .Register(); + + SubscribeNetworkEvent(OnChangeState); + + SubscribeLocalEvent(OnStandingUpDoAfter); + SubscribeLocalEvent(OnRefreshMovementSpeed); + SubscribeLocalEvent(OnParentChanged); + } + + public override void Shutdown() + { + base.Shutdown(); + + CommandBinds.Unregister(); + } + + private void ToggleStanding(ICommonSession? session) + { + if (session?.AttachedEntity == null + || !HasComp(session.AttachedEntity) + || _gravity.IsWeightless(session.AttachedEntity.Value)) + return; + + RaiseNetworkEvent(new ChangeLayingDownEvent()); + } + + private void OnChangeState(ChangeLayingDownEvent ev, EntitySessionEventArgs args) + { + if (!args.SenderSession.AttachedEntity.HasValue) + return; + + var uid = args.SenderSession.AttachedEntity.Value; + + // TODO: Wizard + //if (HasComp(uid)) + // return; + + if (!TryComp(uid, out StandingStateComponent? standing) + || !TryComp(uid, out LayingDownComponent? layingDown)) + return; + + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + + if (HasComp(uid) + || !_mobState.IsAlive(uid)) + return; + + if (_standing.IsDown(uid, standing)) + TryStandUp(uid, layingDown, standing); + else + TryLieDown(uid, layingDown, standing); + } + + private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component, StandingUpDoAfterEvent args) + { + if (args.Handled || args.Cancelled + || HasComp(uid) + || _mobState.IsIncapacitated(uid) + || !_standing.Stand(uid)) + component.CurrentState = StandingState.Lying; + + component.CurrentState = StandingState.Standing; + } + + private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (_standing.IsDown(uid)) + args.ModifySpeed(component.SpeedModify, component.SpeedModify); + else + args.ModifySpeed(1f, 1f); + } + + private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) + { + // If the entity is not on a grid, try to make it stand up to avoid issues + if (!TryComp(uid, out var standingState) + || standingState.CurrentState is StandingState.Standing + || Transform(uid).GridUid != null) + return; + + _standing.Stand(uid, standingState); + } + + public bool TryStandUp(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false) + || !Resolve(uid, ref layingDown, false) + || standingState.CurrentState is not StandingState.Lying + || !_mobState.IsAlive(uid) + || TerminatingOrDeleted(uid)) + return false; + + var args = new DoAfterArgs(EntityManager, uid, layingDown.StandingUpTime, new StandingUpDoAfterEvent(), uid) + { + BreakOnHandChange = false, + RequireCanInteract = false + }; + + if (!_doAfter.TryStartDoAfter(args)) + return false; + + standingState.CurrentState = StandingState.GettingUp; + return true; + } + + public bool TryLieDown(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null, DropHeldItemsBehavior behavior = DropHeldItemsBehavior.NoDrop) + { + if (!Resolve(uid, ref standingState, false) + || !Resolve(uid, ref layingDown, false) + || standingState.CurrentState is not StandingState.Standing) + { + if (behavior == DropHeldItemsBehavior.AlwaysDrop) + RaiseLocalEvent(uid, new DropHandItemsEvent()); + + return false; + } + + _standing.Down(uid, true, behavior != DropHeldItemsBehavior.NoDrop, standingState); + return true; + } +} + +[Serializable, NetSerializable] +public sealed partial class StandingUpDoAfterEvent : SimpleDoAfterEvent; + +public enum DropHeldItemsBehavior : byte +{ + NoDrop, + DropIfStanding, + AlwaysDrop +} diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 5d7bb0a59fd..5b9759a0252 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -1,24 +1,31 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StandingStateComponent : Component { - [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] - [Access(typeof(StandingStateSystem))] - public sealed partial class StandingStateComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + [DataField] + public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + + [DataField, AutoNetworkedField] + public StandingState CurrentState { get; set; } = StandingState.Standing; - [DataField, AutoNetworkedField] - public bool Standing { get; set; } = true; + [DataField, AutoNetworkedField] + public bool Standing { get; set; } = true; - /// - /// List of fixtures that had their collision mask changed when the entity was downed. - /// Required for re-adding the collision mask. - /// - [DataField, AutoNetworkedField] - public List ChangedFixtures = new(); - } + /// + /// List of fixtures that had their collision mask changed when the entity was downed. + /// Required for re-adding the collision mask. + /// + [DataField, AutoNetworkedField] + public List ChangedFixtures = new(); +} + +public enum StandingState +{ + Lying, + GettingUp, + Standing, } diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index 517831b8a1b..69e72033bba 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -1,166 +1,160 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; using Content.Shared.Hands.Components; +using Content.Shared.Movement.Systems; using Content.Shared.Physics; using Content.Shared.Rotation; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +public sealed class StandingStateSystem : EntitySystem { - public sealed class StandingStateSystem : EntitySystem - { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; + [Dependency] private readonly SharedBuckleSystem _buckle = default!; - // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. - private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; + // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. + private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; - public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) - { - if (!Resolve(uid, ref standingState, false)) - return false; - - return !standingState.Standing; - } + public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false)) + return false; - public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - HandsComponent? hands = null) - { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; + return standingState.CurrentState is StandingState.Lying or StandingState.GettingUp; + } - // Optional component. - Resolve(uid, ref appearance, ref hands, false); + public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + HandsComponent? hands = null) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; - if (!standingState.Standing) - return true; + // Optional component. + Resolve(uid, ref appearance, ref hands, false); - // This is just to avoid most callers doing this manually saving boilerplate - // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. - // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway - // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. - if (dropHeldItems && hands != null) - { - RaiseLocalEvent(uid, new DropHandItemsEvent(), false); - } + if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp) + return true; - var msg = new DownAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + // This is just to avoid most callers doing this manually saving boilerplate + // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. + // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway + // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. + if (dropHeldItems && hands != null) + RaiseLocalEvent(uid, new DropHandItemsEvent(), false); - if (msg.Cancelled) - return false; + if (TryComp(uid, out BuckleComponent? buckle) && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) + return false; - standingState.Standing = false; - Dirty(standingState); - RaiseLocalEvent(uid, new DownedEvent(), false); + var msg = new DownAttemptEvent(); + RaiseLocalEvent(uid, msg, false); - // Seemed like the best place to put it - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); + if (msg.Cancelled) + return false; - // Change collision masks to allow going under certain entities like flaps and tables - if (TryComp(uid, out FixturesComponent? fixtureComponent)) - { - foreach (var (key, fixture) in fixtureComponent.Fixtures) - { - if ((fixture.CollisionMask & StandingCollisionLayer) == 0) - continue; - - standingState.ChangedFixtures.Add(key); - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); - } - } + standingState.CurrentState = StandingState.Lying; + Dirty(standingState); + RaiseLocalEvent(uid, new DownedEvent(), false); - // check if component was just added or streamed to client - // if true, no need to play sound - mob was down before player could seen that - if (standingState.LifeStage <= ComponentLifeStage.Starting) - return true; + // Seemed like the best place to put it + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); - if (playSound) + // Change collision masks to allow going under certain entities like flaps and tables + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + foreach (var (key, fixture) in fixtureComponent.Fixtures) { - _audio.PlayPredicted(standingState.DownSound, uid, uid); + if ((fixture.CollisionMask & StandingCollisionLayer) == 0) + continue; + + standingState.ChangedFixtures.Add(key); + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); } + // check if component was just added or streamed to client + // if true, no need to play sound - mob was down before player could seen that + if (standingState.LifeStage <= ComponentLifeStage.Starting) return true; - } - public bool Stand(EntityUid uid, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - bool force = false) - { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; + if (playSound) + _audio.PlayPredicted(standingState.DownSound, uid, null); - // Optional component. - Resolve(uid, ref appearance, false); + _movement.RefreshMovementSpeedModifiers(uid); + return true; + } - if (standingState.Standing) - return true; + public bool Stand(EntityUid uid, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + bool force = false) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; - if (!force) - { - var msg = new StandAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + // Optional component. + Resolve(uid, ref appearance, false); - if (msg.Cancelled) - return false; - } + if (standingState.CurrentState is StandingState.Standing + || TryComp(uid, out BuckleComponent? buckle) + && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) + return true; - standingState.Standing = true; - Dirty(uid, standingState); - RaiseLocalEvent(uid, new StoodEvent(), false); + if (!force) + { + var msg = new StandAttemptEvent(); + RaiseLocalEvent(uid, msg, false); - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); + if (msg.Cancelled) + return false; + } - if (TryComp(uid, out FixturesComponent? fixtureComponent)) + standingState.CurrentState = StandingState.Standing; + Dirty(uid, standingState); + RaiseLocalEvent(uid, new StoodEvent(), false); + + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); + + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + { + foreach (var key in standingState.ChangedFixtures) { - foreach (var key in standingState.ChangedFixtures) - { - if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); - } + if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); } - standingState.ChangedFixtures.Clear(); - - return true; } - } + standingState.ChangedFixtures.Clear(); + _movement.RefreshMovementSpeedModifiers(uid); - public sealed class DropHandItemsEvent : EventArgs - { + return true; } +} - /// - /// Subscribe if you can potentially block a down attempt. - /// - public sealed class DownAttemptEvent : CancellableEntityEventArgs - { - } +public sealed class DropHandItemsEvent : EventArgs { } - /// - /// Subscribe if you can potentially block a stand attempt. - /// - public sealed class StandAttemptEvent : CancellableEntityEventArgs - { - } +/// +/// Subscribe if you can potentially block a down attempt. +/// +public sealed class DownAttemptEvent : CancellableEntityEventArgs { } - /// - /// Raised when an entity becomes standing - /// - public sealed class StoodEvent : EntityEventArgs - { - } +/// +/// Subscribe if you can potentially block a stand attempt. +/// +public sealed class StandAttemptEvent : CancellableEntityEventArgs { } - /// - /// Raised when an entity is not standing - /// - public sealed class DownedEvent : EntityEventArgs - { - } -} +/// +/// Raised when an entity becomes standing +/// +public sealed class StoodEvent : EntityEventArgs { } + +/// +/// Raised when an entity is not standing +/// +public sealed class DownedEvent : EntityEventArgs { } diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 976b0ab500d..52225843f23 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Throwing; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Player; @@ -32,6 +33,8 @@ public abstract class SharedStunSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly StandingStateSystem _standingState = default!; [Dependency] private readonly StatusEffectsSystem _statusEffect = default!; + [Dependency] private readonly SharedLayingDownSystem _layingDown = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; /// /// Friction modifier for knocked down players. @@ -76,15 +79,12 @@ public override void Initialize() private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args) { if (!TryComp(uid, out var status)) - { return; - } + switch (args.NewMobState) { case MobState.Alive: - { break; - } case MobState.Critical: { _statusEffect.TryRemoveStatusEffect(uid, "Stun"); @@ -109,12 +109,23 @@ private void UpdateCanMove(EntityUid uid, StunnedComponent component, EntityEven private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args) { - _standingState.Down(uid); + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + _layingDown.TryLieDown(uid, null, null, DropHeldItemsBehavior.DropIfStanding); } private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args) { - _standingState.Stand(uid); + if (!TryComp(uid, out StandingStateComponent? standing)) + return; + + if (TryComp(uid, out LayingDownComponent? layingDown)) + { + if (layingDown.AutoGetUp && !_container.IsEntityInContainer(uid)) + _layingDown.TryStandUp(uid, layingDown); + return; + } + + _standingState.Stand(uid, standing); } private void OnStandAttempt(EntityUid uid, KnockedDownComponent component, StandAttemptEvent args) @@ -148,13 +159,9 @@ private void OnRefreshMovespeed(EntityUid uid, SlowedDownComponent component, Re public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { - if (time <= TimeSpan.Zero) - return false; - - if (!Resolve(uid, ref status, false)) - return false; - - if (!_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) + if (time <= TimeSpan.Zero + || !Resolve(uid, ref status, false) + || !_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) return false; var ev = new StunnedEvent(); @@ -170,13 +177,9 @@ public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { - if (time <= TimeSpan.Zero) - return false; - - if (!Resolve(uid, ref status, false)) - return false; - - if (!_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh)) + if (time <= TimeSpan.Zero + || !Resolve(uid, ref status, false) + || !_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh)) return false; var ev = new KnockedDownEvent(); @@ -204,10 +207,8 @@ public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f, StatusEffectsComponent? status = null) { - if (!Resolve(uid, ref status, false)) - return false; - - if (time <= TimeSpan.Zero) + if (!Resolve(uid, ref status, false) + || time <= TimeSpan.Zero) return false; if (_statusEffect.TryAddStatusEffect(uid, "SlowedDown", time, refresh, status)) @@ -230,14 +231,8 @@ public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args) { - // This is currently disabled in favor of an interaction verb with the same effect, but more obvious usage. - return; - - if (args.Handled || knocked.HelpTimer > 0f) - return; - - // TODO: This should be an event. - if (HasComp(uid)) + if (args.Handled || knocked.HelpTimer > 0f + || HasComp(uid)) return; // Set it to half the help interval so helping is actually useful... @@ -273,15 +268,19 @@ private void OnAttempt(EntityUid uid, StunnedComponent stunned, CancellableEntit private void OnEquipAttempt(EntityUid uid, StunnedComponent stunned, IsEquippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Equipee == uid) - args.Cancel(); + if (args.Equipee != uid) + return; + + args.Cancel(); } private void OnUnequipAttempt(EntityUid uid, StunnedComponent stunned, IsUnequippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Unequipee == uid) - args.Cancel(); + if (args.Unequipee != uid) + return; + + args.Cancel(); } #endregion diff --git a/Content.Shared/Telescope/SharedTelescopeSystem.cs b/Content.Shared/Telescope/SharedTelescopeSystem.cs new file mode 100644 index 00000000000..5f9896cc359 --- /dev/null +++ b/Content.Shared/Telescope/SharedTelescopeSystem.cs @@ -0,0 +1,110 @@ +using System.Numerics; +using Content.Shared.Camera; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Item; +using Robust.Shared.Serialization; + +namespace Content.Shared.Telescope; + +public abstract class SharedTelescopeSystem : EntitySystem +{ + [Dependency] private readonly SharedEyeSystem _eye = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeAllEvent(OnEyeOffsetChanged); + SubscribeLocalEvent(OnUnequip); + SubscribeLocalEvent(OnHandDeselected); + SubscribeLocalEvent(OnShutdown); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent.Comp.LastEntity, out EyeComponent? eye) + || ent.Comp.LastEntity == ent && TerminatingOrDeleted(ent)) + return; + + SetOffset((ent.Comp.LastEntity.Value, eye), Vector2.Zero, ent); + } + + private void OnHandDeselected(Entity ent, ref HandDeselectedEvent args) + { + if (!TryComp(args.User, out EyeComponent? eye)) + return; + + SetOffset((args.User, eye), Vector2.Zero, ent); + } + + private void OnUnequip(Entity ent, ref GotUnequippedHandEvent args) + { + if (!TryComp(args.User, out EyeComponent? eye) + || !HasComp(ent.Owner)) + return; + + SetOffset((args.User, eye), Vector2.Zero, ent); + } + + public TelescopeComponent? GetRightTelescope(EntityUid? ent) + { + TelescopeComponent? telescope = null; + + if (TryComp(ent, out var hands) + && hands.ActiveHandEntity.HasValue + && TryComp(hands.ActiveHandEntity, out var handTelescope)) + telescope = handTelescope; + else if (TryComp(ent, out var entityTelescope)) + telescope = entityTelescope; + + return telescope; + } + + private void OnEyeOffsetChanged(EyeOffsetChangedEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not { } ent + || !TryComp(ent, out var eye)) + return; + + var telescope = GetRightTelescope(ent); + + if (telescope == null) + return; + + var offset = Vector2.Lerp(eye.Offset, msg.Offset, telescope.LerpAmount); + + SetOffset((ent, eye), offset, telescope); + } + + private void SetOffset(Entity ent, Vector2 offset, TelescopeComponent telescope) + { + telescope.LastEntity = ent; + + if (TryComp(ent, out CameraRecoilComponent? recoil)) + { + recoil.BaseOffset = offset; + _eye.SetOffset(ent, offset + recoil.CurrentKick, ent); + } + else + { + _eye.SetOffset(ent, offset, ent); + } + } + + public void SetParameters(Entity ent, float? divisor = null, float? lerpAmount = null) + { + var telescope = ent.Comp; + + telescope.Divisor = divisor ?? telescope.Divisor; + telescope.LerpAmount = lerpAmount ?? telescope.LerpAmount; + + Dirty(ent.Owner, telescope); + } +} + +[Serializable, NetSerializable] +public sealed class EyeOffsetChangedEvent : EntityEventArgs +{ + public Vector2 Offset; +} diff --git a/Content.Shared/Telescope/TelescopeComponent.cs b/Content.Shared/Telescope/TelescopeComponent.cs new file mode 100644 index 00000000000..529cc58324f --- /dev/null +++ b/Content.Shared/Telescope/TelescopeComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Telescope; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TelescopeComponent : Component +{ + [DataField, AutoNetworkedField] + public float Divisor = 0.1f; + + [DataField, AutoNetworkedField] + public float LerpAmount = 0.1f; + + [ViewVariables] + public EntityUid? LastEntity; +} diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index 7d6af11f379..eaab10df027 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -269,3 +269,8 @@ ui-options-net-pvs-leave-tooltip = This limits the rate at which the client will ## Toggle window console command cmd-options-desc = Opens options menu, optionally with a specific tab selected. cmd-options-help = Usage: options [tab] + +## Combat Options +ui-options-function-look-up = Look up/Take aim +ui-options-function-auto-get-up = Automatically get up after falling +ui-options-function-hold-look-up = Hold down the key to aim diff --git a/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl b/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl new file mode 100644 index 00000000000..a39bc37af90 --- /dev/null +++ b/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl @@ -0,0 +1,3 @@ +ui-options-function-look-up = Присмотреться/Прицелиться +ui-options-function-auto-get-up = Автоматически вставать при падении +ui-options-function-hold-look-up = Удерживать клавишу для прицеливания diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml index 4b58642c307..c85314e91ca 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml @@ -81,6 +81,7 @@ - CartridgeAntiMateriel capacity: 5 proto: CartridgeAntiMateriel + - type: Telescope - type: entity name: musket diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 2cca749317a..33b4166161a 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -543,3 +543,6 @@ binds: - function: Hotbar9 type: State key: Num9 +- function: LookUp + type: State + key: Space From a8e435cd9b9db45db4f7f1a3e543f2c469ad7641 Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Thu, 19 Sep 2024 02:18:59 +0000 Subject: [PATCH 17/24] Automatic Changelog Update (#815) --- Resources/Changelog/Changelog.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index bf97bb66370..6f10c74b5ff 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6429,3 +6429,18 @@ Entries: id: 6368 time: '2024-09-18T04:30:39.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/926 +- author: Spatison + changes: + - type: Add + message: Added lying down system / Добавлена система лежания + - type: Tweak + message: >- + Lying down now uses do-afters that are visible to other people to + indicate what is going on. + - type: Add + message: Added telescope system / Добавлена система прицеливания + - type: Tweak + message: Now you can aim from Hristov / Теперь можно прицеливаться из Христова + id: 6369 + time: '2024-09-19T02:18:35.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/815 From 2bfbc72d2dcfb4b00d3f568ac64bd463f70801e2 Mon Sep 17 00:00:00 2001 From: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com> Date: Thu, 19 Sep 2024 04:24:24 +0200 Subject: [PATCH 18/24] Remove DeltaV Options (#928) # Description This PR Remove and the DeltaV Option Tab while moving the specie filter to the general accessibility tab. View Media for images. This PR also Rebase the option and removed useless duplicated, CVars are moved, .ftl files. Tho cvar name is unchanged so the option will still be enable if you enabled it before. This PR fixes #481 and put PR #630 stale. ---

Media

![image](https://github.com/user-attachments/assets/55fdf75d-2c02-4c25-b0df-e76cce564a33) ![image](https://github.com/user-attachments/assets/88a170fb-ba32-467c-b432-b0a8c554a489)

--- # Changelog :cl: - remove: DeltaV Option Tab (Options moved) --- .../DeltaV/Options/UI/Tabs/DeltaTab.xaml | 23 ---------- .../DeltaV/Options/UI/Tabs/DeltaTab.xaml.cs | 46 ------------------- Content.Client/Options/UI/OptionsMenu.xaml | 2 - Content.Client/Options/UI/OptionsMenu.xaml.cs | 1 - Content.Client/Options/UI/Tabs/MiscTab.xaml | 1 + .../Options/UI/Tabs/MiscTab.xaml.cs | 7 ++- Content.Client/Overlays/DogVisionSystem.cs | 8 ++-- Content.Client/Overlays/UltraVisionSystem.cs | 8 ++-- Content.Shared/CCVar/CCVars.cs | 6 +++ Content.Shared/DeltaV/CCVars/DCCVars.cs | 6 --- .../en-US/deltav/escape-menu/options-menu.ftl | 4 -- .../en-US/escape-menu/ui/options-menu.ftl | 1 + 12 files changed, 22 insertions(+), 91 deletions(-) delete mode 100644 Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml delete mode 100644 Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml.cs delete mode 100644 Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl diff --git a/Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml b/Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml deleted file mode 100644 index f1dae68077d..00000000000 --- a/Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -
public static readonly CVarDef RoundEndPacifist = CVarDef.Create("game.round_end_pacifist", false, CVar.SERVERONLY); - - /// - /// Disables all vision filters for species like Vulpkanin or Harpies. There are good reasons someone might want to disable these. - /// - public static readonly CVarDef NoVisionFilters = - CVarDef.Create("accessibility.no_vision_filters", false, CVar.CLIENTONLY | CVar.ARCHIVE); } diff --git a/Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl b/Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl deleted file mode 100644 index 50d55cb76d0..00000000000 --- a/Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl +++ /dev/null @@ -1,4 +0,0 @@ -ui-options-tab-deltav = DeltaV -ui-options-general-forknotice = Note: These settings are fork-specific and might not apply on other servers. - -ui-options-no-filters = Disable species vision filters diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index eaab10df027..ea24439f708 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -52,6 +52,7 @@ ui-options-fancy-speech = Show names in speech bubbles ui-options-fancy-name-background = Add background to speech bubble names ui-options-enable-color-name = Add colors to character names ui-options-colorblind-friendly = Colorblind friendly mode +ui-options-no-filters = Disable species vision filters ui-options-reduced-motion = Reduce motion of visual effects ui-options-chat-window-opacity = Chat window opacity ui-options-chat-window-opacity-percent = { TOSTRING($opacity, "P0") } From 0cf7803501f319461a86f7faff8e0fde3e20b392 Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Thu, 19 Sep 2024 02:24:51 +0000 Subject: [PATCH 19/24] Automatic Changelog Update (#928) --- Resources/Changelog/Changelog.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6f10c74b5ff..eb0238bdb2a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6444,3 +6444,10 @@ Entries: id: 6369 time: '2024-09-19T02:18:35.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/815 +- author: FoxxoTrystan + changes: + - type: Remove + message: DeltaV Option Tab (Options moved) + id: 6370 + time: '2024-09-19T02:24:25.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/928 From d6dd2ea8bc95388ffc127325865d52a53751349a Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 18 Sep 2024 23:29:28 -0400 Subject: [PATCH 20/24] Fix SpawnAndDeleteAllEntities (#932) # Description After doing an archaological exploration of the fucking SpawnAndDeleteAllEntities test fail, I eventually tracked down the bug to an issue where InternalEncryptionKeySpawner is randomly being handed a Null EntityUid which was Null Forgiven to make the compiler shut up. The actual EntityUid factually cannot be null during ordinary operation, except for the dumbass race condition provided by TestSpawnAndDeleteAllEntities. --- Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs b/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs index 7f5d216c92f..5f799a102e7 100644 --- a/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs +++ b/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs @@ -2,7 +2,6 @@ using Content.Shared.Radio.Components; using Content.Shared.Containers; using Robust.Shared.Containers; -using Content.Server.Cargo.Components; namespace Content.Server.Silicon.IPC; public sealed partial class InternalEncryptionKeySpawner : EntitySystem @@ -10,7 +9,11 @@ public sealed partial class InternalEncryptionKeySpawner : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; public void TryInsertEncryptionKey(EntityUid target, StartingGearPrototype startingGear, IEntityManager entityManager) { - if (!TryComp(target, out var keyHolderComp) +#pragma warning disable CS8073 + if (target == null // target can be null during race conditions intentionally created by awful tests. +#pragma warning restore CS8073 + || !TryComp(target, out var keyHolderComp) + || keyHolderComp is null || !startingGear.Equipment.TryGetValue("ears", out var earEquipString) || string.IsNullOrEmpty(earEquipString)) return; From dee5a409aecd50ded7610f7c6f8d727f4b76e97e Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 18 Sep 2024 23:29:41 -0400 Subject: [PATCH 21/24] [Port] StepTriggerGroup From WhiteDream (#929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This is a port of https://github.com/WWhiteDreamProject/wwdpublic/pull/53 from White Dream. This PR improves the StepTriggerImmune component by making it operate on a more granular Blacklist system, such that StepTriggerImmune entities can further clarify via prototypes which kinds of floor traps they are immune to, such as landmines/mousetraps, and not have blanket immunity to everything. Because it turns out things like Lava and Soap also were caught by the immunity, when really we just wanted Harpies & Felinids to not trigger landmines.

Media

> # Описание > Необходимо настроить модификатор урона, чтобы IPC не получали урон от осколков стекла. > > Иммунитет StepTriggerImmuneComponent доработан. Теперь имеются несколько типов (types): Lava - тип тайла, наступив на который появляется урон. Это собственно лава и LiquidPlasma Landmine - мины. Chasm - дырка в карте, куда можно провалиться Mousetrap - Мышеловка SlipTile - Все, что должно подскальзывать игроков, имеющее размер тайла SlipEntity - Все, что должно подскальзывать игроков, имеющее развер энтити. Разделено для баланса. Самые ловки могут игнорировать мелкие предметы (энтити), т.е. уворачиваться от них. Но большие по площади вещи (тайлы по типу разлитой воды, бананиума) просчитываются отдельно. > > # Изменения > * [x] Улучшить StepTriggerSystem (Immune) > * [x] Добавлены типы триггера. - Lava Landmine Shard Chasm Mousetrap SlipTile SlipEntity > * [x] Исправить осколки у IPC > * [x] Исправить отсутствие урона от лавы и падение в дыры у фелинидов и гарпий. > > 🆑 Hell_Cat > > * Feature: StepTriggerSystem is improved | Улучшена StepTriggerSystem > * fix: IPC: Immunity for shards and SpiderWeb | Иммунитет осколкам. > * fix: Felinid | Фелиниды : Immunity for Shard Landmine Mousetrap SlipEntities | Иммунитет для осколков, жидкости, мин, мышеловок, мыла и бананов. > * fix: Harpy | Гарпия : Immunity for Shards Landmine Mousetrap | Иммунитет для осколков, жидкости, мин и мышеловок. > * fix: Mice | Мыши : Don't blow up on landmines | Мыши не подрываются на минах.

# Changelog :cl: Hell_Cat Feature: StepTriggerSystem has been improved with new StepTriggerGroups. Additionally, the StepTriggerImmune component now allows declaring for specific StepTriggerGroups for a given entity to be immune to. Some examples may be, Felinids, Mice, and Harpies being unable to set off Landmines. --------- Signed-off-by: VMSolidus Co-authored-by: Ivan <126400932+HellCatten@users.noreply.github.com> Co-authored-by: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com> --- .../Components/StepTriggerComponent.cs | 14 ++-- .../Components/StepTriggerImmuneComponent.cs | 12 +++- .../Prototypes/StepTriggerGroup.cs | 72 +++++++++++++++++++ .../Prototypes/StepTriggerTypePrototype.cs | 15 ++++ .../StepTrigger/Systems/StepTriggerSystem.cs | 9 ++- .../Entities/Effects/chemistry_effects.yml | 5 +- .../Prototypes/Entities/Effects/puddle.yml | 3 + .../Prototypes/Entities/Mobs/NPCs/animals.yml | 4 ++ .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 3 + .../Prototypes/Entities/Mobs/Player/ipc.yml | 4 ++ .../Entities/Mobs/Species/diona.yml | 4 ++ .../Entities/Mobs/Species/harpy.yml | 5 ++ .../Objects/Consumable/Food/produce.yml | 3 + .../Entities/Objects/Devices/mousetrap.yml | 3 + .../Entities/Objects/Devices/pda.yml | 3 + .../Prototypes/Entities/Objects/Fun/dice.yml | 3 + .../Entities/Objects/Materials/shards.yml | 3 + .../Entities/Objects/Misc/land_mine.yml | 3 + .../Entities/Objects/Misc/spider_web.yml | 3 + .../Objects/Specific/Janitorial/soap.yml | 9 +++ .../Prototypes/Entities/Tiles/bananium.yml | 3 + Resources/Prototypes/Entities/Tiles/chasm.yml | 9 ++- Resources/Prototypes/Entities/Tiles/lava.yml | 3 + .../Entities/Tiles/liquid_plasma.yml | 3 + .../Entities/Mobs/Species/felinid.yml | 6 ++ .../StepTrigger/StepTriggerTypes.yml | 20 ++++++ Resources/Prototypes/Traits/skills.yml | 6 ++ 27 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs create mode 100644 Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs create mode 100644 Resources/Prototypes/StepTrigger/StepTriggerTypes.yml diff --git a/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs b/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs index b8483d021a4..d12c2c983ee 100644 --- a/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs +++ b/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.StepTrigger.Prototypes; using Content.Shared.StepTrigger.Systems; using Content.Shared.Whitelist; using Robust.Shared.GameStates; @@ -53,15 +54,18 @@ public sealed partial class StepTriggerComponent : Component public bool IgnoreWeightless; /// - /// Does this have separate "StepOn" and "StepOff" triggers. + /// Does this have separate "StepOn" and "StepOff" triggers. /// [DataField, AutoNetworkedField] public bool StepOn = false; + + /// + /// If TriggerGroups is specified, it will check StepTriggerImmunityComponent to have the same TriggerType to activate immunity + /// + [DataField] + public StepTriggerGroup? TriggerGroups; } [RegisterComponent] [Access(typeof(StepTriggerSystem))] -public sealed partial class StepTriggerActiveComponent : Component -{ - -} +public sealed partial class StepTriggerActiveComponent : Component { } diff --git a/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs b/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs index 4321334a3ae..1b92905fa61 100644 --- a/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs +++ b/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs @@ -1,3 +1,5 @@ +using Content.Shared.StepTrigger.Prototypes; +using Content.Shared.StepTrigger.Systems; using Robust.Shared.GameStates; namespace Content.Shared.StepTrigger.Components; @@ -12,4 +14,12 @@ namespace Content.Shared.StepTrigger.Components; /// Consider using a subscription to StepTriggerAttemptEvent if you wish to be more selective. /// [RegisterComponent, NetworkedComponent] -public sealed partial class StepTriggerImmuneComponent : Component { } +[Access(typeof(StepTriggerSystem))] +public sealed partial class StepTriggerImmuneComponent : Component +{ + /// + /// WhiteList of immunity step triggers. + /// + [DataField] + public StepTriggerGroup? Whitelist; +} diff --git a/Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs b/Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs new file mode 100644 index 00000000000..5b64085e9a9 --- /dev/null +++ b/Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs @@ -0,0 +1,72 @@ +using Content.Shared.StepTrigger.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.StepTrigger.Prototypes; + +/// +/// A group of +/// Used to determine StepTriggerTypes like Tags. +/// Used for better work with Immunity. +/// StepTriggerTypes in StepTriggerTypes.yml +/// +/// +/// stepTriggerGroups: +/// types: +/// - Lava +/// - Landmine +/// - Shard +/// - Chasm +/// - Mousetrap +/// - SlipTile +/// - SlipEntity +/// +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class StepTriggerGroup +{ + [DataField] + public List>? Types = null; + + /// + /// Checks if types of this StepTriggerGroup is similar to types of AnotherGroup + /// + public bool IsValid(StepTriggerGroup? anotherGroup) + { + if (Types is null) + return false; + + foreach (var type in Types) + { + if (anotherGroup != null + && anotherGroup.Types != null + && anotherGroup.Types.Contains(type)) + return true; + } + return false; + } + + /// + /// Checks validation (if types of this StepTriggerGroup are similar to types of + /// another StepTriggerComponent. + /// + public bool IsValid(StepTriggerComponent component) + { + if (component.TriggerGroups is null) + return false; + + return IsValid(component.TriggerGroups); + } + + /// + /// Checks validation (if types of this StepTriggerGroup are similar to types of + /// another StepTriggerImmuneComponent. + /// + public bool IsValid(StepTriggerImmuneComponent component) + { + if (component.Whitelist is null) + return false; + + return IsValid(component.Whitelist); + } +} diff --git a/Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs b/Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs new file mode 100644 index 00000000000..732eb4b732e --- /dev/null +++ b/Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.StepTrigger.Prototypes; + +/// +/// Prototype representing a StepTriggerType in YAML. +/// Meant to only have an ID property, as that is the only thing that +/// gets saved in StepTriggerGroup. +/// +[Prototype] +public sealed partial class StepTriggerTypePrototype : IPrototype +{ + [ViewVariables, IdDataField] + public string ID { get; private set; } = default!; +} diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs index 89655afac23..d0cd5c4b4e2 100644 --- a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs +++ b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs @@ -118,11 +118,16 @@ private void UpdateColliding(EntityUid uid, StepTriggerComponent component, Tran private bool CanTrigger(EntityUid uid, EntityUid otherUid, StepTriggerComponent component) { - if (HasComp(otherUid) - || !component.Active + if (!component.Active || component.CurrentlySteppedOn.Contains(otherUid)) return false; + // Immunity checks + if (TryComp(otherUid, out var stepTriggerImmuneComponent) + && component.TriggerGroups != null + && component.TriggerGroups.IsValid(stepTriggerImmuneComponent)) + return false; + // Can't trigger if we don't ignore weightless entities // and the entity is flying or currently weightless // Makes sense simulation wise to have this be part of steptrigger directly IMO diff --git a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml index 096e88bcb6f..469bab32782 100644 --- a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml +++ b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml @@ -77,6 +77,9 @@ animationState: foam-dissolve - type: Slippery - type: StepTrigger + triggerGroups: + types: + - SlipTile # disabled until foam reagent duplication is fixed #- type: ScoopableSolution # solution: solutionArea @@ -135,7 +138,7 @@ - type: RCDDeconstructable cost: 2 delay: 2 - fx: EffectRCDDeconstruct2 + fx: EffectRCDDeconstruct2 - type: Clickable - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index 2c845e1d0f0..d3156c50a3f 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -149,6 +149,9 @@ - type: EdgeSpreader id: Puddle - type: StepTrigger + triggerGroups: + types: + - SlipTile - type: Drink delay: 3 transferAmount: 1 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 29234ea34cf..01eed043139 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1689,6 +1689,10 @@ - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning + - type: StepTriggerImmune + whitelist: + types: + - Landmine - type: entity parent: MobMouse diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index d01fc8b8de2..90d9a5e5c97 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -137,6 +137,9 @@ - type: Speech speechVerb: Cluwne - type: StepTrigger + triggerGroups: + types: + - SlipEntity intersectRatio: 0.2 - type: Fixtures fixtures: diff --git a/Resources/Prototypes/Entities/Mobs/Player/ipc.yml b/Resources/Prototypes/Entities/Mobs/Player/ipc.yml index 1bed477b7d1..247226dc7d5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/ipc.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/ipc.yml @@ -111,6 +111,10 @@ - type: OfferItem - type: LayingDown - type: Carriable + - type: StepTriggerImmune + whitelist: + types: + - Shard - type: entity save: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 42383d9a426..81c9e596163 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -114,6 +114,10 @@ walkModifier: 0.75 - type: SpeedModifierImmunity - type: NoSlip + - type: StepTriggerImmune + whitelist: + types: + - Shard - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml index 05ac3de8bb3..8882da868b1 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml @@ -123,6 +123,11 @@ - GalacticCommon - SolCommon - type: StepTriggerImmune + whitelist: + types: + - Shard + - Landmine + - Mousetrap - type: entity save: false diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 3f0277e1bc3..930d7fa64dc 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -314,6 +314,9 @@ launchForwardsMultiplier: 1.5 - type: StepTrigger intersectRatio: 0.2 + triggerGroups: + types: + - SlipEntity - type: CollisionWake enabled: false - type: Physics diff --git a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml index c3bd74dd751..a93cd545bfc 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml @@ -13,6 +13,9 @@ - type: StepTrigger intersectRatio: 0.2 requiredTriggeredSpeed: 0 + triggerGroups: + types: + - Mousetrap - type: Mousetrap - type: TriggerOnStepTrigger - type: ShoesRequiredStepTrigger diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index f0d07f9434a..1a388997835 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -272,6 +272,9 @@ paralyzeTime: 4 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipEntity - type: CollisionWake enabled: false - type: Physics diff --git a/Resources/Prototypes/Entities/Objects/Fun/dice.yml b/Resources/Prototypes/Entities/Objects/Fun/dice.yml index 852a1c2699c..f7395ef9ca6 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/dice.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/dice.yml @@ -119,6 +119,9 @@ mask: - ItemMask - type: StepTrigger + triggerGroups: + types: + - Shard intersectRatio: 0.2 - type: TriggerOnStepTrigger - type: ShoesRequiredStepTrigger diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index 6cdc066cf10..34685343145 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -64,6 +64,9 @@ - !type:DoActsBehavior acts: [ "Destruction" ] - type: StepTrigger + triggerGroups: + types: + - Shard intersectRatio: 0.2 - type: ShoesRequiredStepTrigger - type: Slippery diff --git a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml index a3e3485bc65..97053660a19 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml @@ -40,6 +40,9 @@ params: maxDistance: 10 - type: StepTrigger + triggerGroups: + types: + - Landmine requiredTriggeredSpeed: 0 stepOn: true diff --git a/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml b/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml index 9561fa3538f..bb284000a76 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml @@ -111,6 +111,9 @@ paralyzeTime: 2 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipTile intersectRatio: 0.2 - type: Physics - type: Fixtures diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 5678de6bafc..5fe88f8d0cf 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -24,6 +24,9 @@ paralyzeTime: 2 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipEntity intersectRatio: 0.2 - type: CollisionWake enabled: false @@ -157,6 +160,9 @@ paralyzeTime: 5 launchForwardsMultiplier: 2.5 - type: StepTrigger + triggerGroups: + types: + - SlipEntity intersectRatio: 0.04 - type: Item heldPrefix: syndie @@ -198,6 +204,9 @@ - type: Slippery paralyzeTime: 2 - type: StepTrigger + triggerGroups: + types: + - SlipEntity - type: Item heldPrefix: gibs - type: FlavorProfile diff --git a/Resources/Prototypes/Entities/Tiles/bananium.yml b/Resources/Prototypes/Entities/Tiles/bananium.yml index c9a6ec28441..9e8a46b2c35 100644 --- a/Resources/Prototypes/Entities/Tiles/bananium.yml +++ b/Resources/Prototypes/Entities/Tiles/bananium.yml @@ -47,6 +47,9 @@ paralyzeTime: 2 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipTile intersectRatio: 0.2 - type: Physics bodyType: Static diff --git a/Resources/Prototypes/Entities/Tiles/chasm.yml b/Resources/Prototypes/Entities/Tiles/chasm.yml index 23f3ad83950..85bc7b5ab34 100644 --- a/Resources/Prototypes/Entities/Tiles/chasm.yml +++ b/Resources/Prototypes/Entities/Tiles/chasm.yml @@ -14,6 +14,9 @@ blacklist: tags: - Catwalk + triggerGroups: + types: + - Chasm - type: Transform anchored: true - type: Clickable @@ -55,7 +58,7 @@ sprite: Tiles/Planet/Chasms/chromite_chasm.rsi - type: Icon sprite: Tiles/Planet/Chasms/chromite_chasm.rsi - + - type: entity parent: FloorChasmEntity id: FloorDesertChasm @@ -65,7 +68,7 @@ sprite: Tiles/Planet/Chasms/desert_chasm.rsi - type: Icon sprite: Tiles/Planet/Chasms/desert_chasm.rsi - + - type: entity parent: FloorChasmEntity id: FloorSnowChasm @@ -74,4 +77,4 @@ - type: Sprite sprite: Tiles/Planet/Chasms/snow_chasm.rsi - type: Icon - sprite: Tiles/Planet/Chasms/snow_chasm.rsi \ No newline at end of file + sprite: Tiles/Planet/Chasms/snow_chasm.rsi diff --git a/Resources/Prototypes/Entities/Tiles/lava.yml b/Resources/Prototypes/Entities/Tiles/lava.yml index 72641309b31..9d61304af9d 100644 --- a/Resources/Prototypes/Entities/Tiles/lava.yml +++ b/Resources/Prototypes/Entities/Tiles/lava.yml @@ -13,6 +13,9 @@ blacklist: tags: - Catwalk + triggerGroups: + types: + - Lava - type: Lava fireStacks: 0.75 - type: Transform diff --git a/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml b/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml index 08f46538631..500286ead31 100644 --- a/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml +++ b/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml @@ -13,6 +13,9 @@ blacklist: tags: - Catwalk + triggerGroups: + types: + - Lava - type: Lava fireStacks: 0.75 - type: Transform diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml index a1ca357b080..411eb13444b 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml @@ -80,6 +80,12 @@ stripTimeReduction: 0 stripTimeMultiplier: 0.667 - type: StepTriggerImmune + whitelist: + types: + - Shard + - Landmine + - Mousetrap + - SlipEntity - type: entity save: false diff --git a/Resources/Prototypes/StepTrigger/StepTriggerTypes.yml b/Resources/Prototypes/StepTrigger/StepTriggerTypes.yml new file mode 100644 index 00000000000..6d03908a17f --- /dev/null +++ b/Resources/Prototypes/StepTrigger/StepTriggerTypes.yml @@ -0,0 +1,20 @@ +- type: stepTriggerType + id: Lava + +- type: stepTriggerType + id: Landmine + +- type: stepTriggerType + id: Shard + +- type: stepTriggerType + id: Chasm + +- type: stepTriggerType + id: Mousetrap + +- type: stepTriggerType + id: SlipTile + +- type: stepTriggerType + id: SlipEntity diff --git a/Resources/Prototypes/Traits/skills.yml b/Resources/Prototypes/Traits/skills.yml index d757cbcd564..fa79666c7ab 100644 --- a/Resources/Prototypes/Traits/skills.yml +++ b/Resources/Prototypes/Traits/skills.yml @@ -270,6 +270,12 @@ points: -3 components: - type: StepTriggerImmune + whitelist: + types: + - Shard + - Landmine + - Mousetrap + - SlipEntity requirements: - !type:CharacterSpeciesRequirement inverted: true From 6d1aae9844dcdb31d28d6070c0f0d067c8ee8cca Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Thu, 19 Sep 2024 03:30:24 +0000 Subject: [PATCH 22/24] Automatic Changelog Update (#929) --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index eb0238bdb2a..d7568628f6f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6451,3 +6451,8 @@ Entries: id: 6370 time: '2024-09-19T02:24:25.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/928 +- author: Hell_Cat + changes: [] + id: 6371 + time: '2024-09-19T03:29:41.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/929 From 0aa49c14917355fecdd44cfc65d85c1f334e2d7e Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 19 Sep 2024 16:37:51 -0400 Subject: [PATCH 23/24] Fix Laying Down Bugs (#933) # Description This fixes prediction issues, both where pressing the Lay down key causes all entities on your screen to glitch and lay down, as well as the bug where upon receiving ANY movement input, a player would be visually changed on their screen to be laying down. # Changelog :cl: - fix: Fixed issues with the LayingDownSystem. Laying down no longer causes all entities on your screen to lay down at once. Using any movement input no longer causes the character to appear to the player as to be laying down. --- Content.Client/Standing/LayingDownSystem.cs | 8 +++++--- Content.Shared/Standing/SharedLayingDownSystem.cs | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Content.Client/Standing/LayingDownSystem.cs b/Content.Client/Standing/LayingDownSystem.cs index 6cc5f7921ac..594883ac001 100644 --- a/Content.Client/Standing/LayingDownSystem.cs +++ b/Content.Client/Standing/LayingDownSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.ActionBlocker; +using Content.Shared.Buckle; using Content.Shared.Rotation; using Content.Shared.Standing; using Robust.Client.GameObjects; @@ -11,8 +11,9 @@ public sealed class LayingDownSystem : SharedLayingDownSystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; [Dependency] private readonly AnimationPlayerSystem _animation = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedBuckleSystem _buckle = default!; public override void Initialize() { @@ -26,7 +27,8 @@ public override void Initialize() private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args) { if (!_timing.IsFirstTimePredicted - || !_actionBlocker.CanMove(uid) + || !_standing.IsDown(uid) + || _buckle.IsBuckled(uid) || _animation.HasRunningAnimation(uid, "rotate") || !TryComp(uid, out var transform) || !TryComp(uid, out var sprite) diff --git a/Content.Shared/Standing/SharedLayingDownSystem.cs b/Content.Shared/Standing/SharedLayingDownSystem.cs index 47ad01949cb..7a82d4b7aa8 100644 --- a/Content.Shared/Standing/SharedLayingDownSystem.cs +++ b/Content.Shared/Standing/SharedLayingDownSystem.cs @@ -40,7 +40,8 @@ public override void Shutdown() private void ToggleStanding(ICommonSession? session) { - if (session?.AttachedEntity == null + if (session is not { AttachedEntity: { Valid: true } uid } _ + || !Exists(uid) || !HasComp(session.AttachedEntity) || _gravity.IsWeightless(session.AttachedEntity.Value)) return; From 7bce1e92a30359ff674a585925caf4884d01017c Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Thu, 19 Sep 2024 20:38:15 +0000 Subject: [PATCH 24/24] Automatic Changelog Update (#933) --- Resources/Changelog/Changelog.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d7568628f6f..a233a61a2d4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6456,3 +6456,14 @@ Entries: id: 6371 time: '2024-09-19T03:29:41.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/929 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed issues with the LayingDownSystem. Laying down no longer causes all + entities on your screen to lay down at once. Using any movement input no + longer causes the character to appear to the player as to be laying + down. + id: 6372 + time: '2024-09-19T20:37:51.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/933