using System.Numerics; using Content.Server.Physics.Components; using Content.Shared.Follower.Components; using Content.Shared.Throwing; using Robust.Server.GameObjects; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Controllers; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.Physics.Controllers; /// /// The entity system responsible for managing s. /// Handles updating the direction they move in when their cooldown elapses. /// internal sealed class RandomWalkController : VirtualController { #region Dependencies [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly PhysicsSystem _physics = default!; #endregion Dependencies public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnRandomWalkStartup); } /// /// Updates the cooldowns of all random walkers. /// If each of them is off cooldown it updates their velocity and resets its cooldown. /// /// ??? Not documented anywhere I can see ??? // TODO: Document this. /// The amount of time that has elapsed since the last time random walk cooldowns were updated. public override void UpdateBeforeSolve(bool prediction, float frameTime) { base.UpdateBeforeSolve(prediction, frameTime); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var randomWalk, out var physics)) { if (EntityManager.HasComponent(uid) || EntityManager.HasComponent(uid) || EntityManager.HasComponent(uid)) continue; var curTime = _timing.CurTime; if (randomWalk.NextStepTime <= curTime) Update(uid, randomWalk, physics); } } /// /// Updates the direction and speed a random walker is moving at. /// Also resets the random walker's cooldown. /// /// The random walker state. /// The physics body associated with the random walker. public void Update(EntityUid uid, RandomWalkComponent? randomWalk = null, PhysicsComponent? physics = null) { if(!Resolve(uid, ref randomWalk)) return; var curTime = _timing.CurTime; randomWalk.NextStepTime = curTime + TimeSpan.FromSeconds(_random.NextDouble(randomWalk.MinStepCooldown.TotalSeconds, randomWalk.MaxStepCooldown.TotalSeconds)); if(!Resolve(uid, ref physics)) return; var pushVec = _random.NextAngle().ToVec(); pushVec += randomWalk.BiasVector; pushVec.Normalize(); if (randomWalk.ResetBiasOnWalk) randomWalk.BiasVector *= 0f; var pushStrength = _random.NextFloat(randomWalk.MinSpeed, randomWalk.MaxSpeed); _physics.SetLinearVelocity(uid, physics.LinearVelocity * randomWalk.AccumulatorRatio + pushVec * pushStrength, body: physics); } /// /// Syncs up a random walker step timing when the component starts up. /// /// The uid of the random walker to start up. /// The state of the random walker to start up. /// The startup prompt arguments. private void OnRandomWalkStartup(EntityUid uid, RandomWalkComponent comp, ComponentStartup args) { if (comp.StepOnStartup) Update(uid, comp); else comp.NextStepTime = _timing.CurTime + TimeSpan.FromSeconds(_random.NextDouble(comp.MinStepCooldown.TotalSeconds, comp.MaxStepCooldown.TotalSeconds)); } }