Add Diona rooting (#32782)
* Initial commit * Add sound * Review commets * addressing review * I think this is what Slart meant? * Review fixes * More fixes * tiny formatting * Review fixes * Review fixes * Fix small timing error * Follow new action system * review --------- Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: ScarKy0 <scarky0@onet.eu>
This commit is contained in:
5
Content.Client/Rootable/RootableSystem.cs
Normal file
5
Content.Client/Rootable/RootableSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Rootable;
|
||||
|
||||
namespace Content.Client.Rootable;
|
||||
|
||||
public sealed class RootableSystem : SharedRootableSystem;
|
||||
@@ -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;
|
||||
|
||||
|
||||
77
Content.Server/Rootable/RootableSystem.cs
Normal file
77
Content.Server/Rootable/RootableSystem.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
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.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Rootable;
|
||||
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 _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<RootableComponent, BloodstreamComponent>();
|
||||
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!));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the puddle is set up properly and if so, moves on to reacting.
|
||||
/// </summary>
|
||||
private void PuddleReact(Entity<RootableComponent, BloodstreamComponent> entity, Entity<PuddleComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to transfer an amount of the solution to the entity's bloodstream.
|
||||
/// </summary>
|
||||
private void ReactWithEntity(Entity<RootableComponent, BloodstreamComponent> entity, Entity<PuddleComponent> 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, transferSolution, entity.Comp2))
|
||||
{
|
||||
// Log solution addition by puddle
|
||||
_logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity):target} absorbed puddle {SharedSolutionContainerSystem.ToPrettyString(transferSolution)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
76
Content.Shared/Rootable/RootableComponent.cs
Normal file
76
Content.Shared/Rootable/RootableComponent.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// The action prototype that toggles the rootable state.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId Action = "ActionToggleRootable";
|
||||
|
||||
/// <summary>
|
||||
/// Entity to hold the action prototype.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype for the "rooted" alert, indicating the user that they are rooted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> RootedAlert = "Rooted";
|
||||
|
||||
/// <summary>
|
||||
/// Is the entity currently rooted?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Rooted;
|
||||
|
||||
/// <summary>
|
||||
/// The puddle that is currently affecting this entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? PuddleEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the next absorption metabolism will occur.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// The max rate (in reagent units per transfer) at which chemicals are transferred from the puddle to the rooted entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 TransferRate = 0.75;
|
||||
|
||||
/// <summary>
|
||||
/// The frequency of which chemicals are transferred from the puddle to the rooted entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan TransferFrequency = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <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>
|
||||
[DataField]
|
||||
public SoundSpecifier RootSound = new SoundPathSpecifier("/Audio/Voice/Diona/diona_salute.ogg");
|
||||
}
|
||||
177
Content.Shared/Rootable/SharedRootableSystem.cs
Normal file
177
Content.Shared/Rootable/SharedRootableSystem.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
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.
|
||||
/// Being rooted prevents weighlessness and slipping, but causes any floor contents to transfer its reagents to the bloodstream.
|
||||
/// </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 MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
protected EntityQuery<PuddleComponent> PuddleQuery;
|
||||
protected EntityQuery<PhysicsComponent> PhysicsQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
PuddleQuery = GetEntityQuery<PuddleComponent>();
|
||||
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
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(Entity<RootableComponent> 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<RootableComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp(entity, out ActionsComponent? comp))
|
||||
return;
|
||||
|
||||
var actions = new Entity<ActionsComponent?>(entity, comp);
|
||||
_actions.RemoveAction(actions, entity.Comp.ActionEntity);
|
||||
}
|
||||
|
||||
private void OnRootableToggle(Entity<RootableComponent> entity, ref ToggleActionEvent args)
|
||||
{
|
||||
args.Handled = TryToggleRooting((entity, entity));
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<RootableComponent> entity, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (entity.Comp.Rooted)
|
||||
TryToggleRooting((entity, entity));
|
||||
}
|
||||
|
||||
public bool TryToggleRooting(Entity<RootableComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return false;
|
||||
|
||||
entity.Comp.Rooted = !entity.Comp.Rooted;
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(entity);
|
||||
Dirty(entity);
|
||||
|
||||
if (entity.Comp.Rooted)
|
||||
{
|
||||
_alerts.ShowAlert(entity, entity.Comp.RootedAlert);
|
||||
var curTime = _timing.CurTime;
|
||||
if (curTime > entity.Comp.NextUpdate)
|
||||
{
|
||||
entity.Comp.NextUpdate = curTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(entity, entity.Comp.RootedAlert);
|
||||
}
|
||||
|
||||
_audio.PlayPredicted(entity.Comp.RootSound, entity.Owner.ToCoordinates(), entity);
|
||||
|
||||
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 (!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<RootableComponent> 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<RootableComponent> entity, ref RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (entity.Comp.Rooted)
|
||||
args.ModifySpeed(entity.Comp.SpeedModifier);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ public sealed class SlipperySystem : EntitySystem
|
||||
if (HasComp<KnockedDownComponent>(other) && !component.SlipData.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
2
Resources/Locale/en-US/actions/actions/rootable.ftl
Normal file
2
Resources/Locale/en-US/actions/actions/rootable.ftl
Normal file
@@ -0,0 +1,2 @@
|
||||
action-name-toggle-rootable = Rootable
|
||||
action-description-toggle-rootable = Begin or stop being rooted to the floor.
|
||||
@@ -113,3 +113,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.
|
||||
|
||||
@@ -399,6 +399,18 @@
|
||||
useDelay: 1
|
||||
itemIconStyle: BigAction
|
||||
|
||||
- type: entity
|
||||
parent: BaseToggleAction
|
||||
id: ActionToggleRootable
|
||||
name: action-name-toggle-rootable
|
||||
description: action-description-toggle-rootable
|
||||
components:
|
||||
- type: Action
|
||||
icon: Interface/Actions/rooting.png
|
||||
iconOn: Interface/Actions/rooting.png
|
||||
itemIconStyle: NoItem
|
||||
useDelay: 1
|
||||
|
||||
- type: entity
|
||||
id: ActionChameleonController
|
||||
name: Control clothing
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
- category: Hunger
|
||||
- category: Thirst
|
||||
- alertType: Magboots
|
||||
- alertType: Rooted
|
||||
- alertType: Pacified
|
||||
|
||||
- type: entity
|
||||
|
||||
5
Resources/Prototypes/Alerts/rooted.yml
Normal file
5
Resources/Prototypes/Alerts/rooted.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
- type: alert
|
||||
id: Rooted
|
||||
icons: [ /Textures/Interface/Alerts/Rooted/rooted.png ]
|
||||
name: alerts-rooted-name
|
||||
description: alerts-rooted-desc
|
||||
@@ -112,6 +112,7 @@
|
||||
32:
|
||||
sprite: Mobs/Species/Human/displacement.rsi
|
||||
state: jumpsuit-female
|
||||
- type: Rootable
|
||||
|
||||
- type: entity
|
||||
parent: BaseSpeciesDummy
|
||||
|
||||
BIN
Resources/Textures/Interface/Actions/rooting.png
Normal file
BIN
Resources/Textures/Interface/Actions/rooting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
Resources/Textures/Interface/Alerts/Rooted/rooted.png
Normal file
BIN
Resources/Textures/Interface/Alerts/Rooted/rooted.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user