Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
SlamBamActionman committed Oct 13, 2024
1 parent 740288e commit 261747c
Show file tree
Hide file tree
Showing 15 changed files with 349 additions and 9 deletions.
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
54 changes: 54 additions & 0 deletions Content.Shared/Rootable/RootableComponent.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <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;
}
154 changes: 154 additions & 0 deletions Content.Shared/Rootable/SharedRootableSystem.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <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!;

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);

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

0 comments on commit 261747c

Please sign in to comment.