Fix status effect prediction (#8475)

This commit is contained in:
Leon Friedrich
2022-09-05 05:21:21 +12:00
committed by GitHub
parent 1b00f70dcc
commit e3d9d4df02
4 changed files with 50 additions and 29 deletions

View File

@@ -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;
}
}
}

View File

@@ -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]

View File

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

View File

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