Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Envelopes #553

Merged
merged 4 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Content.Client/Paper/EnvelopeSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Content.Shared.Paper;
using Robust.Client.GameObjects;

namespace Content.Client.Paper;

public sealed class EnvelopeSystem : VisualizerSystem<EnvelopeComponent>
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EnvelopeComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}

private void OnAfterAutoHandleState(Entity<EnvelopeComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateAppearance(ent);
}

private void UpdateAppearance(Entity<EnvelopeComponent> ent, SpriteComponent? sprite = null)
{
if (!Resolve(ent.Owner, ref sprite))
return;

sprite.LayerSetVisible(EnvelopeVisualLayers.Open, ent.Comp.State == EnvelopeComponent.EnvelopeState.Open);
sprite.LayerSetVisible(EnvelopeVisualLayers.Sealed, ent.Comp.State == EnvelopeComponent.EnvelopeState.Sealed);
sprite.LayerSetVisible(EnvelopeVisualLayers.Torn, ent.Comp.State == EnvelopeComponent.EnvelopeState.Torn);
}

public enum EnvelopeVisualLayers : byte
{
Open,
Sealed,
Torn
}
}
63 changes: 63 additions & 0 deletions Content.Shared/Paper/EnvelopeComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Content.Shared.DoAfter;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;

namespace Content.Shared.Paper;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class EnvelopeComponent : Component
{
/// <summary>
/// The current open/sealed/torn state of the envelope
/// </summary>
[ViewVariables, DataField, AutoNetworkedField]
public EnvelopeState State = EnvelopeState.Open;

[DataField, ViewVariables]
public string SlotId = "letter_slot";

/// <summary>
/// Stores the current sealing/tearing doafter of the envelope
/// to prevent doafter spam/prediction issues
/// </summary>
[DataField, ViewVariables]
public DoAfterId? EnvelopeDoAfter;

/// <summary>
/// How long it takes to seal the envelope closed
/// </summary>
[DataField, ViewVariables]
public TimeSpan SealDelay = TimeSpan.FromSeconds(1);

/// <summary>
/// How long it takes to tear open the envelope
/// </summary>
[DataField, ViewVariables]
public TimeSpan TearDelay = TimeSpan.FromSeconds(1);

/// <summary>
/// The sound to play when the envelope is sealed closed
/// </summary>
[DataField, ViewVariables]
public SoundPathSpecifier? SealSound = new SoundPathSpecifier("/Audio/Effects/packetrip.ogg");

/// <summary>
/// The sound to play when the envelope is torn open
/// </summary>
[DataField, ViewVariables]
public SoundPathSpecifier? TearSound = new SoundPathSpecifier("/Audio/Effects/poster_broken.ogg");

[Serializable, NetSerializable]
public enum EnvelopeState : byte
{
Open,
Sealed,
Torn
}
}

[Serializable, NetSerializable]
public sealed partial class EnvelopeDoAfterEvent : SimpleDoAfterEvent
{
}
108 changes: 108 additions & 0 deletions Content.Shared/Paper/EnvelopeSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using Content.Shared.DoAfter;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Content.Shared.Examine;

namespace Content.Shared.Paper;

public sealed class EnvelopeSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<EnvelopeComponent, ItemSlotInsertAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<EnvelopeComponent, ItemSlotEjectAttemptEvent>(OnEjectAttempt);
SubscribeLocalEvent<EnvelopeComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
SubscribeLocalEvent<EnvelopeComponent, EnvelopeDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<EnvelopeComponent, ExaminedEvent>(OnExamine);
}

private void OnExamine(Entity<EnvelopeComponent> ent, ref ExaminedEvent args)
{
if (ent.Comp.State == EnvelopeComponent.EnvelopeState.Sealed)
{
args.PushMarkup(Loc.GetString("envelope-sealed-examine", ("envelope", ent.Owner)));
}
else if (ent.Comp.State == EnvelopeComponent.EnvelopeState.Torn)
{
args.PushMarkup(Loc.GetString("envelope-torn-examine", ("envelope", ent.Owner)));
}
}

private void OnGetAltVerbs(Entity<EnvelopeComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
return;

if (ent.Comp.State == EnvelopeComponent.EnvelopeState.Torn)
return;

var user = args.User;
args.Verbs.Add(new AlternativeVerb()
{
Text = Loc.GetString(ent.Comp.State == EnvelopeComponent.EnvelopeState.Open ? "envelope-verb-seal" : "envelope-verb-tear"),
IconEntity = GetNetEntity(ent.Owner),
Act = () =>
{
TryStartDoAfter(ent, user, ent.Comp.State == EnvelopeComponent.EnvelopeState.Open ? ent.Comp.SealDelay : ent.Comp.TearDelay);
},
});
}

private void OnInsertAttempt(Entity<EnvelopeComponent> ent, ref ItemSlotInsertAttemptEvent args)
{
args.Cancelled |= ent.Comp.State != EnvelopeComponent.EnvelopeState.Open;
}

private void OnEjectAttempt(Entity<EnvelopeComponent> ent, ref ItemSlotEjectAttemptEvent args)
{
args.Cancelled |= ent.Comp.State == EnvelopeComponent.EnvelopeState.Sealed;
}

private void TryStartDoAfter(Entity<EnvelopeComponent> ent, EntityUid user, TimeSpan delay)
{
if (ent.Comp.EnvelopeDoAfter.HasValue)
return;

var doAfterEventArgs = new DoAfterArgs(EntityManager, user, delay, new EnvelopeDoAfterEvent(), ent.Owner, ent.Owner)
{
BreakOnDamage = true,
NeedHand = true,
BreakOnHandChange = true,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
};

if (_doAfterSystem.TryStartDoAfter(doAfterEventArgs, out var doAfterId))
ent.Comp.EnvelopeDoAfter = doAfterId;
}
private void OnDoAfter(Entity<EnvelopeComponent> ent, ref EnvelopeDoAfterEvent args)
{
ent.Comp.EnvelopeDoAfter = null;

if (args.Cancelled)
return;

if (ent.Comp.State == EnvelopeComponent.EnvelopeState.Open)
{
_audioSystem.PlayPredicted(ent.Comp.SealSound, ent.Owner, args.User);
ent.Comp.State = EnvelopeComponent.EnvelopeState.Sealed;
Dirty(ent.Owner, ent.Comp);
}
else if (ent.Comp.State == EnvelopeComponent.EnvelopeState.Sealed)
{
_audioSystem.PlayPredicted(ent.Comp.TearSound, ent.Owner, args.User);
ent.Comp.State = EnvelopeComponent.EnvelopeState.Torn;
Dirty(ent.Owner, ent.Comp);

if (_itemSlotsSystem.TryGetSlot(ent.Owner, ent.Comp.SlotId, out var slotComp))
_itemSlotsSystem.TryEjectToHands(ent.Owner, slotComp, args.User);
}
}
}
11 changes: 11 additions & 0 deletions Resources/Locale/en-US/paper/envelope.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
envelope-verb-seal = Seal
envelope-verb-tear = Tear

envelope-letter-slot = Letter

envelope-sealed-examine = [color=gray]{CAPITALIZE(THE($envelope))} is sealed.[/color]
envelope-torn-examine = [color=yellow]{CAPITALIZE(THE($envelope))} is torn and unusable![/color]

envelope-default-message = TO:

FROM:
Loading
Loading