From: Pok <113675512+Pok27@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:27:06 +0000 (+0200) Subject: Predict RootableSystem (#41729) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=19bad6266f3fb4ccd34e9d944299da0d6530c629;p=space-station-14.git Predict RootableSystem (#41729) RootableSystem-move-to-shared --- diff --git a/Content.Client/Rootable/RootableSystem.cs b/Content.Client/Rootable/RootableSystem.cs deleted file mode 100644 index 33e68ae594..0000000000 --- a/Content.Client/Rootable/RootableSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.Rootable; - -namespace Content.Client.Rootable; - -public sealed class RootableSystem : SharedRootableSystem; diff --git a/Content.Server/Rootable/RootableSystem.cs b/Content.Server/Rootable/RootableSystem.cs deleted file mode 100644 index cd18315bd0..0000000000 --- a/Content.Server/Rootable/RootableSystem.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Content.Server.Body.Systems; -using Content.Shared.Administration.Logs; -using Content.Shared.Body.Components; -using Content.Shared.Chemistry; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Database; -using Content.Shared.FixedPoint; -using Content.Shared.Fluids.Components; -using Content.Shared.Rootable; -using Robust.Shared.Timing; - -namespace Content.Server.Rootable; - -// TODO: Move all of this to shared -/// -/// 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 _solutionContainer = 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, out var bloodstream)) - { - if (!rooted.Rooted || rooted.PuddleEntity == null || curTime < rooted.NextUpdate || !PuddleQuery.TryComp(rooted.PuddleEntity, out var puddleComp)) - continue; - - rooted.NextUpdate += rooted.TransferFrequency; - - PuddleReact((uid, rooted, bloodstream), (rooted.PuddleEntity.Value, puddleComp!)); - } - } - - /// - /// Determines if the puddle is set up properly and if so, moves on to reacting. - /// - private void PuddleReact(Entity entity, Entity puddleEntity) - { - if (!_solutionContainer.ResolveSolution(puddleEntity.Owner, puddleEntity.Comp.SolutionName, ref puddleEntity.Comp.Solution, out var solution) || - solution.Contents.Count == 0) - { - return; - } - - ReactWithEntity(entity, puddleEntity, solution); - } - - /// - /// Attempt to transfer an amount of the solution to the entity's bloodstream. - /// - private void ReactWithEntity(Entity entity, Entity puddleEntity, Solution solution) - { - if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp2.ChemicalSolutionName, ref entity.Comp2.ChemicalSolution, out var chemSolution) || chemSolution.AvailableVolume <= 0) - return; - - var availableTransfer = FixedPoint2.Min(solution.Volume, entity.Comp1.TransferRate); - var transferAmount = FixedPoint2.Min(availableTransfer, chemSolution.AvailableVolume); - var transferSolution = _solutionContainer.SplitSolution(puddleEntity.Comp.Solution!.Value, transferAmount); - - _reactive.DoEntityReaction(entity, transferSolution, ReactionMethod.Ingestion); - - if (_blood.TryAddToChemicals((entity, entity.Comp2), transferSolution)) - { - // Log solution addition by puddle - _logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity):target} absorbed puddle {SharedSolutionContainerSystem.ToPrettyString(transferSolution)}"); - } - } -} diff --git a/Content.Shared/Rootable/RootableSystem.cs b/Content.Shared/Rootable/RootableSystem.cs new file mode 100644 index 0000000000..edb04f959e --- /dev/null +++ b/Content.Shared/Rootable/RootableSystem.cs @@ -0,0 +1,258 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.Components; +using Content.Shared.Administration.Logs; +using Content.Shared.Alert; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Cloning.Events; +using Content.Shared.Coordinates; +using Content.Shared.Database; +using Content.Shared.FixedPoint; +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 Content.Shared.Trigger.Components.Effects; +using Robust.Shared.Audio.Systems; +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. +/// Being rooted prevents weighlessness and slipping, but causes any floor contents to transfer its reagents to the bloodstream. +/// +public sealed class RootableSystem : EntitySystem +{ + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ISharedAdminLogManager _logger = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly ReactiveSystem _reactive = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedBloodstreamSystem _blood = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + private EntityQuery _puddleQuery; + private EntityQuery _physicsQuery; + + public override void Initialize() + { + base.Initialize(); + + _puddleQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnRootableMapInit); + SubscribeLocalEvent(OnRootableShutdown); + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnEndCollide); + SubscribeLocalEvent(OnRootableToggle); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnIsWeightless); + SubscribeLocalEvent(OnSlipAttempt); + SubscribeLocalEvent(OnRefreshMovementSpeed); + SubscribeLocalEvent(OnCloning); + } + + 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, out var bloodstream)) + { + if (!rooted.Rooted || rooted.PuddleEntity == null || curTime < rooted.NextUpdate || !_puddleQuery.TryComp(rooted.PuddleEntity, out var puddleComp)) + continue; + + rooted.NextUpdate += rooted.TransferFrequency; + Dirty(uid, rooted); + PuddleReact((uid, rooted, bloodstream), (rooted.PuddleEntity.Value, puddleComp!)); + } + } + + /// + /// Determines if the puddle is set up properly and if so, moves on to reacting. + /// + private void PuddleReact(Entity ent, Entity puddleEntity) + { + if (!_solutionContainer.ResolveSolution(puddleEntity.Owner, puddleEntity.Comp.SolutionName, ref puddleEntity.Comp.Solution, out var solution) || + solution.Contents.Count == 0) + { + return; + } + + ReactWithEntity(ent, puddleEntity, solution); + } + + /// + /// Attempt to transfer an amount of the solution to the ent's bloodstream. + /// + private void ReactWithEntity(Entity ent, Entity puddleEntity, Solution solution) + { + if (!_solutionContainer.ResolveSolution(ent.Owner, ent.Comp2.ChemicalSolutionName, ref ent.Comp2.ChemicalSolution, out var chemSolution) || chemSolution.AvailableVolume <= 0) + return; + + var availableTransfer = FixedPoint2.Min(solution.Volume, ent.Comp1.TransferRate); + var transferAmount = FixedPoint2.Min(availableTransfer, chemSolution.AvailableVolume); + var transferSolution = _solutionContainer.SplitSolution(puddleEntity.Comp.Solution!.Value, transferAmount); + + _reactive.DoEntityReaction(ent, transferSolution, ReactionMethod.Ingestion); + + // Log solution addition by puddle. + if (_blood.TryAddToChemicals((ent, ent.Comp2), transferSolution)) + _logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(ent):target} absorbed puddle {SharedSolutionContainerSystem.ToPrettyString(transferSolution)}"); + } + + private void OnCloning(Entity ent, ref CloningEvent args) + { + if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) + return; + + var cloneComp = EnsureComp(args.CloneUid); + cloneComp.TransferRate = ent.Comp.TransferRate; + cloneComp.TransferFrequency = ent.Comp.TransferFrequency; + cloneComp.SpeedModifier = ent.Comp.SpeedModifier; + cloneComp.RootSound = ent.Comp.RootSound; + Dirty(args.CloneUid, cloneComp); + } + + private void OnRootableMapInit(Entity ent, ref MapInitEvent args) + { + if (!TryComp(ent, out ActionsComponent? comp)) + return; + + ent.Comp.NextUpdate = _timing.CurTime; + Dirty(ent); + _actions.AddAction(ent, ref ent.Comp.ActionEntity, ent.Comp.Action, component: comp); + } + + private void OnRootableShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out ActionsComponent? comp)) + return; + + var actions = new Entity(ent, comp); + _actions.RemoveAction(actions, ent.Comp.ActionEntity); + _alerts.ClearAlert(ent.Owner, ent.Comp.RootedAlert); + } + + private void OnRootableToggle(Entity ent, ref ToggleActionEvent args) + { + args.Handled = TryToggleRooting((ent, ent)); + } + + private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + if (ent.Comp.Rooted) + TryToggleRooting((ent, ent)); + } + + public bool TryToggleRooting(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + ent.Comp.Rooted = !ent.Comp.Rooted; + _movementSpeedModifier.RefreshMovementSpeedModifiers(ent); + _gravity.RefreshWeightless(ent.Owner); + + if (ent.Comp.Rooted) + { + _alerts.ShowAlert(ent.Owner, ent.Comp.RootedAlert); + var curTime = _timing.CurTime; + if (curTime > ent.Comp.NextUpdate) + ent.Comp.NextUpdate = curTime; + } + else + { + _alerts.ClearAlert(ent.Owner, ent.Comp.RootedAlert); + } + + _audio.PlayPredicted(ent.Comp.RootSound, ent.Owner.ToCoordinates(), ent); + Dirty(ent); + + 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 ent, ref StartCollideEvent args) + { + if (!_puddleQuery.HasComp(args.OtherEntity)) + return; + + ent.Comp.PuddleEntity = args.OtherEntity; + + if (ent.Comp.NextUpdate < _timing.CurTime) // To prevent constantly moving to new puddles resetting the timer. + ent.Comp.NextUpdate = _timing.CurTime; + + Dirty(ent); + } + + private void OnEndCollide(Entity ent, ref EndCollideEvent args) + { + if (ent.Comp.PuddleEntity != args.OtherEntity) + return; + + var exists = Exists(args.OtherEntity); + + if (!_physicsQuery.TryComp(ent, out var body)) + return; + + foreach (var entContact in _physics.GetContactingEntities(ent, body)) + { + if (exists && entContact == args.OtherEntity) + continue; + + if (!_puddleQuery.HasComponent(entContact)) + continue; + + ent.Comp.PuddleEntity = ent; + return; // New puddle found, no need to continue. + } + + ent.Comp.PuddleEntity = null; + Dirty(ent); + } + + private void OnRefreshMovementSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args) + { + if (ent.Comp.Rooted) + args.ModifySpeed(ent.Comp.SpeedModifier); + } +} diff --git a/Content.Shared/Rootable/SharedRootableSystem.cs b/Content.Shared/Rootable/SharedRootableSystem.cs deleted file mode 100644 index 569fdf8e4d..0000000000 --- a/Content.Shared/Rootable/SharedRootableSystem.cs +++ /dev/null @@ -1,193 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Actions.Components; -using Content.Shared.Alert; -using Content.Shared.Cloning.Events; -using Content.Shared.Coordinates; -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 Content.Shared.Trigger.Components.Effects; -using Robust.Shared.Audio.Systems; -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. -/// Being rooted prevents weighlessness and slipping, but causes any floor contents to transfer its reagents to the bloodstream. -/// -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 MovementSpeedModifierSystem _movementSpeedModifier = default!; - [Dependency] private readonly AlertsSystem _alerts = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - - protected EntityQuery PuddleQuery; - protected EntityQuery PhysicsQuery; - - public override void Initialize() - { - base.Initialize(); - - PuddleQuery = GetEntityQuery(); - PhysicsQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnRootableMapInit); - SubscribeLocalEvent(OnRootableShutdown); - SubscribeLocalEvent(OnStartCollide); - SubscribeLocalEvent(OnEndCollide); - SubscribeLocalEvent(OnRootableToggle); - SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnIsWeightless); - SubscribeLocalEvent(OnSlipAttempt); - SubscribeLocalEvent(OnRefreshMovementSpeed); - SubscribeLocalEvent(OnCloning); - } - - private void OnCloning(Entity ent, ref CloningEvent args) - { - if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) - return; - - var cloneComp = EnsureComp(args.CloneUid); - cloneComp.TransferRate = ent.Comp.TransferRate; - cloneComp.TransferFrequency = ent.Comp.TransferFrequency; - cloneComp.SpeedModifier = ent.Comp.SpeedModifier; - cloneComp.RootSound = ent.Comp.RootSound; - Dirty(args.CloneUid, cloneComp); - } - - private void OnRootableMapInit(Entity entity, ref MapInitEvent args) - { - if (!TryComp(entity, out ActionsComponent? comp)) - return; - - entity.Comp.NextUpdate = _timing.CurTime; - _actions.AddAction(entity, ref entity.Comp.ActionEntity, entity.Comp.Action, component: comp); - } - - private void OnRootableShutdown(Entity entity, ref ComponentShutdown args) - { - if (!TryComp(entity, out ActionsComponent? comp)) - return; - - var actions = new Entity(entity, comp); - _actions.RemoveAction(actions, entity.Comp.ActionEntity); - _alerts.ClearAlert(entity.Owner, entity.Comp.RootedAlert); - } - - private void OnRootableToggle(Entity entity, ref ToggleActionEvent args) - { - args.Handled = TryToggleRooting((entity, entity)); - } - - private void OnMobStateChanged(Entity entity, ref MobStateChangedEvent args) - { - if (entity.Comp.Rooted) - TryToggleRooting((entity, entity)); - } - - public bool TryToggleRooting(Entity entity) - { - if (!Resolve(entity, ref entity.Comp)) - return false; - - entity.Comp.Rooted = !entity.Comp.Rooted; - _movementSpeedModifier.RefreshMovementSpeedModifiers(entity); - _gravity.RefreshWeightless(entity.Owner); - Dirty(entity); - - if (entity.Comp.Rooted) - { - _alerts.ShowAlert(entity.Owner, entity.Comp.RootedAlert); - var curTime = _timing.CurTime; - if (curTime > entity.Comp.NextUpdate) - { - entity.Comp.NextUpdate = curTime; - } - } - else - { - _alerts.ClearAlert(entity.Owner, entity.Comp.RootedAlert); - } - _audio.PlayPredicted(entity.Comp.RootSound, entity.Owner.ToCoordinates(), entity); - - 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 (!PuddleQuery.HasComp(args.OtherEntity)) - return; - - entity.Comp.PuddleEntity = args.OtherEntity; - - if (entity.Comp.NextUpdate < _timing.CurTime) // To prevent constantly moving to new puddles resetting the timer - entity.Comp.NextUpdate = _timing.CurTime; - } - - private void OnEndCollide(Entity entity, ref EndCollideEvent args) - { - if (entity.Comp.PuddleEntity != args.OtherEntity) - return; - - var exists = Exists(args.OtherEntity); - - if (!PhysicsQuery.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); - } -}