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);
}
-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;
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()
{
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);
{
// 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;
}
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)
+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;
}