Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test PR #1

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Content.Client/Rootable/RootableSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Content.Shared.Rootable;

namespace Content.Client.Rootable;

public sealed class RootableSystem : SharedRootableSystem
{

}
4 changes: 1 addition & 3 deletions Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
97 changes: 97 additions & 0 deletions Content.Server/Rootable/RootableSystem.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Adds an action to toggle rooting to the ground, primarily for the Diona species.
/// </summary>
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<RootableComponent>();
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);
}
}

/// <summary>
/// Determines if the puddle is set up properly and if so, moves on to reacting.
/// </summary>
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);
}

/// <summary>
/// Attempt to transfer an amount of the solution to the entity's bloodstream.
/// </summary>
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<BloodstreamComponent>(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<ReagentPrototype>(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)}");
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
60 changes: 60 additions & 0 deletions Content.Shared/Rootable/RootableComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Content.Shared.Alert;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

namespace Content.Shared.Rootable;

/// <summary>
/// A rooting action, for Diona.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class RootableComponent : Component
{
[DataField]
public EntProtoId Action = "ActionToggleRootable";

[DataField]
public ProtoId<AlertPrototype> RootedAlert = "Rooted";

[DataField]
public EntityUid? ActionEntity;

/// <summary>
/// Is the entity currently rooted?
/// </summary>
[DataField, AutoNetworkedField]
public bool Rooted = false;

/// <summary>
/// The puddle that is currently affecting this entity.
/// </summary>
[DataField]
public EntityUid? PuddleEntity;

/// <summary>
/// The time at which the next absorption metabolism will occur.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextSecond;

/// <summary>
/// The max rate at which chemicals are transferred from the puddle to the rooted entity.
/// </summary>
[DataField]
public FixedPoint2 TransferRate = 0.75;

/// <summary>
/// The movement speed modifier for when rooting is active.
/// </summary>
[DataField]
public float SpeedModifier = 0.8f;

/// <summary>
/// Sound that plays when rooting is toggled.
/// </summary>
public SoundSpecifier RootSound = new SoundPathSpecifier("/Audio/Voice/Diona/diona_salute.ogg");
}
159 changes: 159 additions & 0 deletions Content.Shared/Rootable/SharedRootableSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using Content.Shared.Damage.Components;
using Content.Shared.Actions;
using Content.Shared.Alert;
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 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;

/// <summary>
/// Adds an action to toggle rooting to the ground, primarily for the Diona species.
/// </summary>
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!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;

private EntityQuery<PuddleComponent> _puddleQuery;

public override void Initialize()
{
base.Initialize();

_puddleQuery = GetEntityQuery<PuddleComponent>();

SubscribeLocalEvent<RootableComponent, MapInitEvent>(OnRootableMapInit);
SubscribeLocalEvent<RootableComponent, ComponentShutdown>(OnRootableShutdown);
SubscribeLocalEvent<RootableComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<RootableComponent, EndCollideEvent>(OnEndCollide);
SubscribeLocalEvent<RootableComponent, ToggleActionEvent>(OnRootableToggle);
SubscribeLocalEvent<RootableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<RootableComponent, IsWeightlessEvent>(OnIsWeightless);
SubscribeLocalEvent<RootableComponent, SlipAttemptEvent>(OnSlipAttempt);
SubscribeLocalEvent<RootableComponent, RefreshMovementSpeedModifiersEvent>(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);

_audioSystem.PlayPredicted(rooted.RootSound, uid.ToCoordinates(), uid);

return true;
}

private void OnIsWeightless(Entity<RootableComponent> 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<RootableComponent> ent, ref SlipAttemptEvent args)
{
if (!ent.Comp.Rooted)
return;

if (args.SlipCausingEntity != null && HasComp<DamageUserOnTriggerComponent>(args.SlipCausingEntity))
return;

args.NoSlip = true;
}

private void OnStartCollide(Entity<RootableComponent> entity, ref StartCollideEvent args)
{
if (!_entityManager.HasComponent<PuddleComponent>(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<RootableComponent> entity, ref EndCollideEvent args)
{
if (entity.Comp.PuddleEntity != args.OtherEntity)
return;

var exists = Exists(args.OtherEntity);

if (!TryComp<PhysicsComponent>(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<RootableComponent> entity, ref RefreshMovementSpeedModifiersEvent args)
{
if (entity.Comp.Rooted)
args.ModifySpeed(entity.Comp.SpeedModifier);
}
}
13 changes: 10 additions & 3 deletions Content.Shared/Slippery/SlipperySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

namespace Content.Shared.Slippery;

[UsedImplicitly]
[UsedImplicitly]
public sealed class SlipperySystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
Expand Down Expand Up @@ -83,7 +83,7 @@ private void OnEntityExit(EntityUid uid, SlipperyComponent component, ref EndCol
{
if (HasComp<SpeedModifiedByContactComponent>(args.OtherEntity))
_speedModifier.AddModifiedEntity(args.OtherEntity);
}
}

private bool CanSlip(EntityUid uid, EntityUid toSlip)
{
Expand All @@ -96,7 +96,7 @@ public void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other,
if (HasComp<KnockedDownComponent>(other) && !component.SuperSlippery)
return;

var attemptEv = new SlipAttemptEvent();
var attemptEv = new SlipAttemptEvent(uid);
RaiseLocalEvent(other, attemptEv);
if (attemptEv.SlowOverSlippery)
_speedModifier.AddModifiedEntity(other);
Expand Down Expand Up @@ -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;
}
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/actions/actions/rootable.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
action-name-toggle-rootable = Rootable
action-description-toggle-rootable = Begin or stop being rooted to the floor.
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/alerts/alerts.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading
Loading