Fix status effect prediction (#8475)
This commit is contained in:
@@ -8,12 +8,14 @@ using Robust.Shared.Physics;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Standing
|
||||
{
|
||||
public sealed class StandingStateSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
|
||||
// If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited.
|
||||
private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable;
|
||||
@@ -29,11 +31,12 @@ namespace Content.Shared.Standing
|
||||
if (args.Current is not StandingComponentState state) return;
|
||||
|
||||
component.Standing = state.Standing;
|
||||
component.ChangedFixtures = new(state.ChangedFixtures);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, StandingStateComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new StandingComponentState(component.Standing);
|
||||
args.State = new StandingComponentState(component.Standing, component.ChangedFixtures);
|
||||
}
|
||||
|
||||
public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null)
|
||||
@@ -78,9 +81,6 @@ namespace Content.Shared.Standing
|
||||
Dirty(standingState);
|
||||
RaiseLocalEvent(uid, new DownedEvent(), false);
|
||||
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
return true;
|
||||
|
||||
// Seemed like the best place to put it
|
||||
appearance?.SetData(RotationVisuals.RotationState, RotationState.Horizontal);
|
||||
|
||||
@@ -97,9 +97,11 @@ namespace Content.Shared.Standing
|
||||
}
|
||||
}
|
||||
|
||||
// Currently shit is only downed by server but when it's predicted we can probably only play this on server / client
|
||||
// > no longer true with door crushing. There just needs to be a better way to handle audio prediction.
|
||||
if (playSound)
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
return true;
|
||||
|
||||
// TODO audio prediction
|
||||
if (playSound && _netMan.IsServer)
|
||||
{
|
||||
SoundSystem.Play(standingState.DownSound.GetSound(), Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.25f));
|
||||
}
|
||||
@@ -151,10 +153,12 @@ namespace Content.Shared.Standing
|
||||
private sealed class StandingComponentState : ComponentState
|
||||
{
|
||||
public bool Standing { get; }
|
||||
public List<string> ChangedFixtures { get; }
|
||||
|
||||
public StandingComponentState(bool standing)
|
||||
public StandingComponentState(bool standing, List<string> changedFixtures)
|
||||
{
|
||||
Standing = standing;
|
||||
ChangedFixtures = changedFixtures;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.StatusEffect
|
||||
@@ -53,6 +53,13 @@ namespace Content.Shared.StatusEffect
|
||||
CooldownRefresh = refresh;
|
||||
RelevantComponent = relevantComponent;
|
||||
}
|
||||
|
||||
public StatusEffectState(StatusEffectState toCopy)
|
||||
{
|
||||
Cooldown = (toCopy.Cooldown.Item1, toCopy.Cooldown.Item2);
|
||||
CooldownRefresh = toCopy.CooldownRefresh;
|
||||
RelevantComponent = toCopy.RelevantComponent;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -61,22 +61,24 @@ namespace Content.Shared.StatusEffect
|
||||
{
|
||||
if (!state.ActiveEffects.ContainsKey(effect))
|
||||
{
|
||||
TryRemoveStatusEffect(uid, effect, component);
|
||||
TryRemoveStatusEffect(uid, effect, component, remComp: false);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var effect in state.ActiveEffects)
|
||||
foreach (var (key, effect) in state.ActiveEffects)
|
||||
{
|
||||
// don't bother with anything if we already have it
|
||||
if (component.ActiveEffects.ContainsKey(effect.Key))
|
||||
if (component.ActiveEffects.ContainsKey(key))
|
||||
{
|
||||
component.ActiveEffects[effect.Key] = effect.Value;
|
||||
component.ActiveEffects[key] = new(effect);
|
||||
continue;
|
||||
}
|
||||
|
||||
var time = effect.Value.Cooldown.Item2 - effect.Value.Cooldown.Item1;
|
||||
var time = effect.Cooldown.Item2 - effect.Cooldown.Item1;
|
||||
|
||||
TryAddStatusEffect(uid, effect.Key, time, true, component);
|
||||
TryAddStatusEffect(uid, key, time, true, component, effect.Cooldown.Item1);
|
||||
component.ActiveEffects[key].RelevantComponent = effect.RelevantComponent;
|
||||
// state handling should not add networked components, that is handled separately by the client game state manager.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ namespace Content.Shared.StatusEffect
|
||||
if (!EntityManager.HasComponent<T>(uid))
|
||||
{
|
||||
var comp = EntityManager.AddComponent<T>(uid);
|
||||
status.ActiveEffects[key].RelevantComponent = comp.Name;
|
||||
status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(comp.GetType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -143,6 +145,8 @@ namespace Content.Shared.StatusEffect
|
||||
/// <param name="time">How long the effect should last for.</param>
|
||||
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
|
||||
/// <param name="status">The status effects component to change, if you already have it.</param>
|
||||
/// <param name="startTime">The time at which the status effect started. This exists mostly for prediction
|
||||
/// resetting.</param>
|
||||
/// <returns>False if the effect could not be added, or if the effect already existed.</returns>
|
||||
/// <remarks>
|
||||
/// This obviously does not add any actual 'effects' on its own. Use the generic overload,
|
||||
@@ -152,7 +156,7 @@ namespace Content.Shared.StatusEffect
|
||||
/// If you want special 'effect merging' behavior, do it your own damn self!
|
||||
/// </remarks>
|
||||
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh,
|
||||
StatusEffectsComponent? status = null)
|
||||
StatusEffectsComponent? status = null, TimeSpan? startTime = null)
|
||||
{
|
||||
if (!Resolve(uid, ref status, false))
|
||||
return false;
|
||||
@@ -163,7 +167,8 @@ namespace Content.Shared.StatusEffect
|
||||
// is fine
|
||||
var proto = _prototypeManager.Index<StatusEffectPrototype>(key);
|
||||
|
||||
(TimeSpan, TimeSpan) cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + time);
|
||||
var start = startTime ?? _gameTiming.CurTime;
|
||||
(TimeSpan, TimeSpan) cooldown = (start, start + time);
|
||||
|
||||
if (HasStatusEffect(uid, key, status))
|
||||
{
|
||||
@@ -232,13 +237,15 @@ namespace Content.Shared.StatusEffect
|
||||
/// <param name="uid">The entity to remove an effect from.</param>
|
||||
/// <param name="key">The effect ID to remove.</param>
|
||||
/// <param name="status">The status effects component to change, if you already have it.</param>
|
||||
/// <param name="remComp">If true, status effect removal will also remove the relevant component. This option
|
||||
/// exists mostly for prediction resetting.</param>
|
||||
/// <returns>False if the effect could not be removed, true otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// Obviously this doesn't automatically clear any effects a status effect might have.
|
||||
/// That's up to the removed component to handle itself when it's removed.
|
||||
/// </remarks>
|
||||
public bool TryRemoveStatusEffect(EntityUid uid, string key,
|
||||
StatusEffectsComponent? status = null)
|
||||
StatusEffectsComponent? status = null, bool remComp = true)
|
||||
{
|
||||
if (!Resolve(uid, ref status, false))
|
||||
return false;
|
||||
@@ -250,16 +257,12 @@ namespace Content.Shared.StatusEffect
|
||||
var state = status.ActiveEffects[key];
|
||||
|
||||
// There are cases where a status effect component might be server-only, so TryGetRegistration...
|
||||
if (state.RelevantComponent != null && _componentFactory.TryGetRegistration(state.RelevantComponent, out var registration))
|
||||
if (remComp
|
||||
&& state.RelevantComponent != null
|
||||
&& _componentFactory.TryGetRegistration(state.RelevantComponent, out var registration))
|
||||
{
|
||||
var type = registration.Type;
|
||||
|
||||
// Make sure the component is actually there first.
|
||||
// Maybe a badmin badminned the component away,
|
||||
// or perhaps, on the client, the component deletion sync
|
||||
// was faster than prediction could predict. Either way, let's not assume the component exists.
|
||||
if (EntityManager.HasComponent(uid, type))
|
||||
EntityManager.RemoveComponent(uid, type);
|
||||
EntityManager.RemoveComponent(uid, type);
|
||||
}
|
||||
|
||||
if (proto.Alert != null)
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace Content.Shared.Stunnable
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<KnockedDownComponent, ComponentInit>(OnKnockInit);
|
||||
SubscribeLocalEvent<KnockedDownComponent, ComponentRemove>(OnKnockRemove);
|
||||
SubscribeLocalEvent<KnockedDownComponent, ComponentShutdown>(OnKnockShutdown);
|
||||
SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
|
||||
|
||||
SubscribeLocalEvent<SlowedDownComponent, ComponentInit>(OnSlowInit);
|
||||
SubscribeLocalEvent<SlowedDownComponent, ComponentShutdown>(OnSlowRemove);
|
||||
@@ -96,11 +97,17 @@ namespace Content.Shared.Stunnable
|
||||
_standingStateSystem.Down(uid);
|
||||
}
|
||||
|
||||
private void OnKnockRemove(EntityUid uid, KnockedDownComponent component, ComponentRemove args)
|
||||
private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args)
|
||||
{
|
||||
_standingStateSystem.Stand(uid);
|
||||
}
|
||||
|
||||
private void OnStandAttempt(EntityUid uid, KnockedDownComponent component, StandAttemptEvent args)
|
||||
{
|
||||
if (component.LifeStage <= ComponentLifeStage.Running)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSlowInit(EntityUid uid, SlowedDownComponent component, ComponentInit args)
|
||||
{
|
||||
_movementSpeedModifierSystem.RefreshMovementSpeedModifiers(uid);
|
||||
|
||||
Reference in New Issue
Block a user