From 261747c0241a0e313b84cee17318a908a0892f78 Mon Sep 17 00:00:00 2001 From: SlamBamActionman Date: Sun, 13 Oct 2024 15:18:03 +0200 Subject: [PATCH] Initial commit --- Content.Client/Rootable/RootableSystem.cs | 8 + .../Systems/DamageUserOnTriggerSystem.cs | 4 +- Content.Server/Rootable/RootableSystem.cs | 97 +++++++++++ .../DamageUserOnTriggerComponent.cs | 4 +- Content.Shared/Rootable/RootableComponent.cs | 54 ++++++ .../Rootable/SharedRootableSystem.cs | 154 ++++++++++++++++++ Content.Shared/Slippery/SlipperySystem.cs | 13 +- .../Locale/en-US/actions/actions/rootable.ftl | 2 + Resources/Locale/en-US/alerts/alerts.ftl | 3 + Resources/Prototypes/Actions/types.yml | 12 ++ Resources/Prototypes/Alerts/alerts.yml | 1 + Resources/Prototypes/Alerts/rooted.yml | 5 + .../Entities/Mobs/Species/diona.yml | 1 + .../Textures/Interface/Actions/rooting.png | Bin 0 -> 15369 bytes .../Interface/Alerts/Rooted/rooted.png | Bin 0 -> 15637 bytes 15 files changed, 349 insertions(+), 9 deletions(-) create mode 100644 Content.Client/Rootable/RootableSystem.cs create mode 100644 Content.Server/Rootable/RootableSystem.cs rename {Content.Server => Content.Shared}/Damage/Components/DamageUserOnTriggerComponent.cs (77%) create mode 100644 Content.Shared/Rootable/RootableComponent.cs create mode 100644 Content.Shared/Rootable/SharedRootableSystem.cs create mode 100644 Resources/Locale/en-US/actions/actions/rootable.ftl create mode 100644 Resources/Prototypes/Alerts/rooted.yml create mode 100644 Resources/Textures/Interface/Actions/rooting.png create mode 100644 Resources/Textures/Interface/Alerts/Rooted/rooted.png diff --git a/Content.Client/Rootable/RootableSystem.cs b/Content.Client/Rootable/RootableSystem.cs new file mode 100644 index 00000000000000..647ea2144579cd --- /dev/null +++ b/Content.Client/Rootable/RootableSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Rootable; + +namespace Content.Client.Rootable; + +public sealed class RootableSystem : SharedRootableSystem +{ + +} diff --git a/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs b/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs index 5051751be9d0fe..8a0ee510769611 100644 --- a/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs +++ b/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs @@ -1,8 +1,6 @@ -using Content.Server.Damage.Components; using Content.Server.Explosion.EntitySystems; using Content.Shared.Damage; -using Content.Shared.StepTrigger; -using Content.Shared.StepTrigger.Systems; +using Content.Shared.Damage.Components; namespace Content.Server.Damage.Systems; diff --git a/Content.Server/Rootable/RootableSystem.cs b/Content.Server/Rootable/RootableSystem.cs new file mode 100644 index 00000000000000..d92e3499409f38 --- /dev/null +++ b/Content.Server/Rootable/RootableSystem.cs @@ -0,0 +1,97 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Shared.Administration.Logs; +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Database; +using Content.Shared.FixedPoint; +using Content.Shared.Fluids.Components; +using Content.Shared.Rootable; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Rootable; + +/// +/// Adds an action to toggle rooting to the ground, primarily for the Diona species. +/// +public sealed class RootableSystem : SharedRootableSystem +{ + + [Dependency] private readonly ISharedAdminLogManager _logger = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly ReactiveSystem _reactive = default!; + [Dependency] private readonly BloodstreamSystem _blood = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + var curTime = _timing.CurTime; + while (query.MoveNext(out var uid, out var rooted)) + { + if (!rooted.Rooted || rooted.PuddleEntity == null || curTime < rooted.NextSecond) + continue; + + rooted.NextSecond += TimeSpan.FromSeconds(1); + + PuddleReact(uid, rooted.PuddleEntity.Value); + } + } + + /// + /// Determines if the puddle is set up properly and if so, moves on to reacting. + /// + private void PuddleReact(EntityUid entity, EntityUid puddleUid, RootableComponent? rootableComponent = null, PuddleComponent? puddleComponent = null) + { + if (!Resolve(entity, ref rootableComponent) || !Resolve(puddleUid, ref puddleComponent)) + return; + + if (!_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName, ref puddleComponent.Solution, out var solution) || + solution.Contents.Count == 0) + { + return; + } + + ReactWithEntity(entity, puddleUid, solution, rootableComponent, puddleComponent); + } + + /// + /// Attempt to transfer an amount of the solution to the entity's bloodstream. + /// + private void ReactWithEntity(EntityUid entity, EntityUid puddleUid, Solution solution, RootableComponent? rootableComponent = null, PuddleComponent? puddleComponent = null) + { + if (!Resolve(entity, ref rootableComponent) || !Resolve(puddleUid, ref puddleComponent) || puddleComponent.Solution == null) + return; + + if (!TryComp(entity, out var bloodstream)) + return; + + if (!_solutionContainerSystem.ResolveSolution(entity, bloodstream.ChemicalSolutionName, ref bloodstream.ChemicalSolution, out var chemSolution) || chemSolution.AvailableVolume <= 0) + return; + + var availableTransfer = FixedPoint2.Min(solution.Volume, rootableComponent.TransferRate); + var transferAmount = FixedPoint2.Min(availableTransfer, chemSolution.AvailableVolume); + var transferSolution = _solutionContainerSystem.SplitSolution(puddleComponent.Solution.Value, transferAmount); + + foreach (var reagentQuantity in transferSolution.Contents.ToArray()) + { + if (reagentQuantity.Quantity == FixedPoint2.Zero) + continue; + var reagentProto = _prototype.Index(reagentQuantity.Reagent.Prototype); + + _reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentProto, reagentQuantity, transferSolution); + } + + if (_blood.TryAddToChemicals(entity, transferSolution, bloodstream)) + { + // Log solution addition by puddle + _logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity):target} absorbed puddle {SharedSolutionContainerSystem.ToPrettyString(transferSolution)}"); + } + } +} diff --git a/Content.Server/Damage/Components/DamageUserOnTriggerComponent.cs b/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs similarity index 77% rename from Content.Server/Damage/Components/DamageUserOnTriggerComponent.cs rename to Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs index 2a30374709b6a9..87adc0cc905e89 100644 --- a/Content.Server/Damage/Components/DamageUserOnTriggerComponent.cs +++ b/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs @@ -1,6 +1,4 @@ -using Content.Shared.Damage; - -namespace Content.Server.Damage.Components; +namespace Content.Shared.Damage.Components; [RegisterComponent] public sealed partial class DamageUserOnTriggerComponent : Component diff --git a/Content.Shared/Rootable/RootableComponent.cs b/Content.Shared/Rootable/RootableComponent.cs new file mode 100644 index 00000000000000..a668df0fc476be --- /dev/null +++ b/Content.Shared/Rootable/RootableComponent.cs @@ -0,0 +1,54 @@ +using Content.Shared.Alert; +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Rootable; + +/// +/// A rooting action, for Diona. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class RootableComponent : Component +{ + [DataField] + public EntProtoId Action = "ActionToggleRootable"; + + [DataField] + public ProtoId RootedAlert = "Rooted"; + + [DataField] + public EntityUid? ActionEntity; + + /// + /// Is the entity currently rooted? + /// + [DataField, AutoNetworkedField] + public bool Rooted = false; + + /// + /// The puddle that is currently affecting this entity. + /// + [DataField] + public EntityUid? PuddleEntity; + + /// + /// The time at which the next absorption metabolism will occur. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoPausedField] + public TimeSpan NextSecond; + + /// + /// The max rate at which chemicals are transferred from the puddle to the rooted entity. + /// + [DataField] + public FixedPoint2 TransferRate = 0.75; + + /// + /// The movement speed modifier for when rooting is active. + /// + [DataField] + public float SpeedModifier = 0.8f; +} diff --git a/Content.Shared/Rootable/SharedRootableSystem.cs b/Content.Shared/Rootable/SharedRootableSystem.cs new file mode 100644 index 00000000000000..7d9a1424722ec3 --- /dev/null +++ b/Content.Shared/Rootable/SharedRootableSystem.cs @@ -0,0 +1,154 @@ +using Content.Shared.Damage.Components; +using Content.Shared.Actions; +using Content.Shared.Alert; +using Content.Shared.Fluids.Components; +using Content.Shared.Gravity; +using Content.Shared.Mobs; +using Content.Shared.Movement.Systems; +using Content.Shared.Slippery; +using Content.Shared.Toggleable; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; + +namespace Content.Shared.Rootable; + +/// +/// Adds an action to toggle rooting to the ground, primarily for the Diona species. +/// +public abstract class SharedRootableSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + + private EntityQuery _puddleQuery; + + public override void Initialize() + { + base.Initialize(); + + _puddleQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnRootableMapInit); + SubscribeLocalEvent(OnRootableShutdown); + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnEndCollide); + SubscribeLocalEvent(OnRootableToggle); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnIsWeightless); + SubscribeLocalEvent(OnSlipAttempt); + SubscribeLocalEvent(OnRefreshMovementSpeed); + } + + private void OnRootableMapInit(EntityUid uid, RootableComponent component, MapInitEvent args) + { + _actions.AddAction(uid, ref component.ActionEntity, component.Action, uid); + } + + private void OnRootableShutdown(EntityUid uid, RootableComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.ActionEntity); + } + + private void OnRootableToggle(EntityUid uid, RootableComponent component, ref ToggleActionEvent args) + { + args.Handled = TryToggleRooting(uid, rooted: component); + } + + private void OnMobStateChanged(EntityUid uid, RootableComponent component, MobStateChangedEvent args) + { + if (component.Rooted) + TryToggleRooting(uid, rooted: component); + } + + public bool TryToggleRooting(EntityUid uid, RootableComponent? rooted = null) + { + if (!Resolve(uid, ref rooted)) + return false; + + rooted.Rooted = !rooted.Rooted; + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + Dirty(uid, rooted); + + if (rooted.Rooted) + _alerts.ShowAlert(uid, rooted.RootedAlert); + else + _alerts.ClearAlert(uid, rooted.RootedAlert); + + return true; + } + + private void OnIsWeightless(Entity ent, ref IsWeightlessEvent args) + { + if (args.Handled || !ent.Comp.Rooted) + return; + + // do not cancel weightlessness if the person is in off-grid. + if (!_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner)) + return; + + args.IsWeightless = false; + args.Handled = true; + } + + private void OnSlipAttempt(Entity ent, ref SlipAttemptEvent args) + { + if (!ent.Comp.Rooted) + return; + + if (args.SlipCausingEntity != null && HasComp(args.SlipCausingEntity)) + return; + + args.NoSlip = true; + } + + private void OnStartCollide(Entity entity, ref StartCollideEvent args) + { + if (!_entityManager.HasComponent(args.OtherEntity)) + { + return; + } + + entity.Comp.PuddleEntity = args.OtherEntity; + + if (entity.Comp.NextSecond < _timing.CurTime) // To prevent constantly moving to new puddles resetting the timer + entity.Comp.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1); + } + + private void OnEndCollide(Entity entity, ref EndCollideEvent args) + { + if (entity.Comp.PuddleEntity != args.OtherEntity) + return; + + var exists = Exists(args.OtherEntity); + + if (!TryComp(entity, out var body)) + return; + + foreach (var ent in _physics.GetContactingEntities(entity, body)) + { + if (exists && ent == args.OtherEntity) + continue; + + if (!_puddleQuery.HasComponent(ent)) + continue; + + entity.Comp.PuddleEntity = ent; + return; // New puddle found, no need to continue + } + + entity.Comp.PuddleEntity = null; + } + + private void OnRefreshMovementSpeed(Entity entity, ref RefreshMovementSpeedModifiersEvent args) + { + if (entity.Comp.Rooted) + args.ModifySpeed(entity.Comp.SpeedModifier); + } +} diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 19cc19aa19c3b4..a658464e62ac11 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -19,7 +19,7 @@ namespace Content.Shared.Slippery; -[UsedImplicitly] +[UsedImplicitly] public sealed class SlipperySystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; @@ -83,7 +83,7 @@ private void OnEntityExit(EntityUid uid, SlipperyComponent component, ref EndCol { if (HasComp(args.OtherEntity)) _speedModifier.AddModifiedEntity(args.OtherEntity); - } + } private bool CanSlip(EntityUid uid, EntityUid toSlip) { @@ -96,7 +96,7 @@ public void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other, if (HasComp(other) && !component.SuperSlippery) return; - var attemptEv = new SlipAttemptEvent(); + var attemptEv = new SlipAttemptEvent(uid); RaiseLocalEvent(other, attemptEv); if (attemptEv.SlowOverSlippery) _speedModifier.AddModifiedEntity(other); @@ -148,7 +148,14 @@ public sealed class SlipAttemptEvent : EntityEventArgs, IInventoryRelayEvent public bool SlowOverSlippery; + public EntityUid? SlipCausingEntity; + public SlotFlags TargetSlots { get; } = SlotFlags.FEET; + + public SlipAttemptEvent(EntityUid? slipCausingEntity) + { + SlipCausingEntity = slipCausingEntity; + } } /// diff --git a/Resources/Locale/en-US/actions/actions/rootable.ftl b/Resources/Locale/en-US/actions/actions/rootable.ftl new file mode 100644 index 00000000000000..ac853a06af96a1 --- /dev/null +++ b/Resources/Locale/en-US/actions/actions/rootable.ftl @@ -0,0 +1,2 @@ +action-name-toggle-rootable = Rootable +action-description-toggle-rootable = Begin or stop being rooted to the floor. diff --git a/Resources/Locale/en-US/alerts/alerts.ftl b/Resources/Locale/en-US/alerts/alerts.ftl index 37af416c3a1758..58b99b91795227 100644 --- a/Resources/Locale/en-US/alerts/alerts.ftl +++ b/Resources/Locale/en-US/alerts/alerts.ftl @@ -110,3 +110,6 @@ alerts-revenant-essence-desc = The power of souls. It sustains you and is used f alerts-revenant-corporeal-name = Corporeal alerts-revenant-corporeal-desc = You have manifested physically. People around you can see and hurt you. + +alerts-rooted-name = Rooted +alerts-rooted-desc = You are attached to the ground. You can't slip, but you absorb fluids under you. diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index 5430a3f005a538..2c2150bf4978ff 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -326,3 +326,15 @@ itemIconStyle: NoItem useDelay: 1 # emote spam event: !type:ToggleActionEvent + +- type: entity + id: ActionToggleRootable + name: action-name-toggle-rootable + description: action-description-toggle-rootable + components: + - type: InstantAction + icon: Interface/Actions/rooting.png + iconOn: Interface/Actions/rooting.png + itemIconStyle: NoItem + useDelay: 1 + event: !type:ToggleActionEvent diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 80fcc44a559e02..c13f0b785189a4 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -23,6 +23,7 @@ - category: Hunger - category: Thirst - alertType: Magboots + - alertType: Rooted - alertType: Pacified - type: entity diff --git a/Resources/Prototypes/Alerts/rooted.yml b/Resources/Prototypes/Alerts/rooted.yml new file mode 100644 index 00000000000000..088e4be2b62e96 --- /dev/null +++ b/Resources/Prototypes/Alerts/rooted.yml @@ -0,0 +1,5 @@ +- type: alert + id: Rooted + icons: [ /Textures/Interface/Alerts/Rooted/rooted.png ] + name: alerts-rooted-name + description: alerts-rooted-desc diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index fdb7ac1e954f13..5675aed7bf463b 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -109,6 +109,7 @@ 32: sprite: Mobs/Species/Human/displacement.rsi state: jumpsuit-female + - type: Rootable - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Textures/Interface/Actions/rooting.png b/Resources/Textures/Interface/Actions/rooting.png new file mode 100644 index 0000000000000000000000000000000000000000..4fc05fb3f32eb4bdfa53e4c475324c41b6f5c21c GIT binary patch literal 15369 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 literal 0 HcmV?d00001