Predict GlueSystem (#39079)

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
This commit is contained in:
Kyle Tyo
2025-07-30 15:57:50 -04:00
committed by GitHub
parent a8b65f2da7
commit 68ba22548d
5 changed files with 64 additions and 49 deletions

View File

@@ -2,41 +2,44 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Prototypes;
namespace Content.Shared.Glue;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedGlueSystem))]
/// <summary>
/// This component indicates that an item is glue and can be used as such.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(GlueSystem))]
public sealed partial class GlueComponent : Component
{
/// <summary>
/// Noise made when glue applied.
/// </summary>
[DataField("squeeze")]
[DataField, AutoNetworkedField]
public SoundSpecifier Squeeze = new SoundPathSpecifier("/Audio/Items/squeezebottle.ogg");
/// <summary>
/// Solution on the entity that contains the glue.
/// </summary>
[DataField("solution")]
[DataField, AutoNetworkedField]
public string Solution = "drink";
/// <summary>
/// Reagent that will be used as glue.
/// </summary>
[DataField("reagent", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
public string Reagent = "SpaceGlue";
[DataField, AutoNetworkedField]
public ProtoId<ReagentPrototype> Reagent = "SpaceGlue";
/// <summary>
/// Reagent consumption per use.
/// </summary>
[DataField("consumptionUnit"), ViewVariables(VVAccess.ReadWrite)]
[DataField, AutoNetworkedField]
public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5);
/// <summary>
/// Duration per unit
/// </summary>
[DataField("durationPerUnit"), ViewVariables(VVAccess.ReadWrite)]
[DataField, AutoNetworkedField]
public TimeSpan DurationPerUnit = TimeSpan.FromSeconds(6);
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Administration.Logs;
using Content.Shared.Administration.Logs;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Database;
using Content.Shared.Glue;
using Content.Shared.Hands;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
@@ -13,17 +12,17 @@ using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
namespace Content.Server.Glue;
namespace Content.Shared.Glue;
public sealed class GlueSystem : SharedGlueSystem
public sealed class GlueSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly NameModifierSystem _nameMod = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
[Dependency] private readonly NameModifierSystem _nameMod = default!;
public override void Initialize()
{
@@ -62,7 +61,7 @@ public sealed class GlueSystem : SharedGlueSystem
Act = () => TryGlue(entity, target, user),
IconEntity = GetNetEntity(entity),
Text = Loc.GetString("glue-verb-text"),
Message = Loc.GetString("glue-verb-message")
Message = Loc.GetString("glue-verb-message"),
};
args.Verbs.Add(verb);
@@ -72,26 +71,29 @@ public sealed class GlueSystem : SharedGlueSystem
{
// if item is glued then don't apply glue again so it can be removed for reasonable time
// If glue is applied to an unremoveable item, the component will disappear after the duration.
// This effecitvely means any unremoveable item could be removed with a bottle of glue.
// This effectively means any unremoveable item could be removed with a bottle of glue.
if (HasComp<GluedComponent>(target) || !HasComp<ItemComponent>(target) || HasComp<UnremoveableComponent>(target))
{
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
_popup.PopupClient(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
return false;
}
if (HasComp<ItemComponent>(target) && _solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
if (HasComp<ItemComponent>(target) && _solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solutionEntity, out _))
{
var quantity = solution.RemoveReagent(entity.Comp.Reagent, entity.Comp.ConsumptionUnit);
var quantity = _solutionContainer.RemoveReagent(solutionEntity.Value, entity.Comp.Reagent, entity.Comp.ConsumptionUnit);
if (quantity > 0)
{
EnsureComp<GluedComponent>(target).Duration = quantity.Double() * entity.Comp.DurationPerUnit;
_audio.PlayPredicted(entity.Comp.Squeeze, entity.Owner, actor);
_popup.PopupClient(Loc.GetString("glue-success", ("target", target)), actor, actor, PopupType.Medium);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(actor):actor} glued {ToPrettyString(target):subject} with {ToPrettyString(entity.Owner):tool}");
_audio.PlayPvs(entity.Comp.Squeeze, entity.Owner);
_popup.PopupEntity(Loc.GetString("glue-success", ("target", target)), actor, actor, PopupType.Medium);
var gluedComp = EnsureComp<GluedComponent>(target);
gluedComp.Duration = quantity.Double() * entity.Comp.DurationPerUnit;
Dirty(target, gluedComp);
return true;
}
}
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
_popup.PopupClient(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
return false;
}
@@ -119,9 +121,16 @@ public sealed class GlueSystem : SharedGlueSystem
private void OnHandPickUp(Entity<GluedComponent> entity, ref GotEquippedHandEvent args)
{
// When predicting dropping a glued item prediction will reinsert the item into the hand when rerolling the state to a previous one.
// So dropping the item would add UnRemoveableComponent on the client without this guard statement.
if (_timing.ApplyingState)
return;
var comp = EnsureComp<UnremoveableComponent>(entity);
comp.DeleteOnDrop = false;
entity.Comp.Until = _timing.CurTime + entity.Comp.Duration;
Dirty(entity.Owner, comp);
Dirty(entity);
}
private void OnRefreshNameModifiers(Entity<GluedComponent> entity, ref RefreshNameModifiersEvent args)

View File

@@ -1,15 +1,26 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Glue;
[RegisterComponent]
[Access(typeof(SharedGlueSystem))]
/// <summary>
/// This component gets attached to an item that has been glued.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(GlueSystem))]
public sealed partial class GluedComponent : Component
{
[DataField("until", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
/// <summary>
/// The TimeSpan this effect expires at.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField, AutoPausedField]
public TimeSpan Until;
[DataField("duration", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
/// <summary>
/// The duration this effect will last. Determined by the quantity of the reagent that is applied.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField]
public TimeSpan Duration;
}

View File

@@ -1,5 +0,0 @@
namespace Content.Shared.Glue;
public abstract class SharedGlueSystem : EntitySystem
{
}

View File

@@ -1,17 +1,14 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Interaction.Components
namespace Content.Shared.Interaction.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class UnremoveableComponent : Component
{
[RegisterComponent]
[NetworkedComponent]
public sealed partial class UnremoveableComponent : Component
{
/// <summary>
/// If this is true then unremovable items that are removed from inventory are deleted (typically from corpse gibbing).
/// Items within unremovable containers are not deleted when removed.
/// </summary>
[DataField("deleteOnDrop")]
public bool DeleteOnDrop = true;
}
/// <summary>
/// If this is true then unremovable items that are removed from inventory are deleted (typically from corpse gibbing).
/// Items within unremovable containers are not deleted when removed.
/// </summary>
[DataField, AutoNetworkedField]
public bool DeleteOnDrop = true;
}