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

Mail! #16

Merged
merged 7 commits into from
Oct 1, 2023
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
8 changes: 8 additions & 0 deletions Content.Client/Nyanotrasen/Mail/MailComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Content.Shared.Mail;

namespace Content.Client.Mail
{
[RegisterComponent]
public sealed partial class MailComponent : SharedMailComponent
{}
}
59 changes: 59 additions & 0 deletions Content.Client/Nyanotrasen/Mail/MailSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Robust.Client.GameObjects;
using Content.Shared.Mail;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Client.State;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Client.Mail
{
/// <summary>
/// Display a cool stamp on the parcel based on the job of the recipient.
/// </summary>
/// <remarks>
/// GenericVisualizer is not powerful enough to handle setting a string on
/// visual data then directly relaying that string to a layer's state.
/// I.e. there is nothing like a regex capture group for visual data.
///
/// Hence why this system exists.
///
/// To do this with GenericVisualizer would require a separate condition
/// for every job value, which would be extra mess to maintain.
///
/// It would look something like this, multipled a couple dozen times.
///
/// enum.MailVisuals.JobIcon:
/// enum.MailVisualLayers.JobStamp:
/// StationEngineer:
/// state: StationEngineer
/// SecurityOfficer:
/// state: SecurityOfficer
/// </remarks>
public sealed class MailJobVisualizerSystem : VisualizerSystem<MailComponent>
{
protected override void OnAppearanceChange(EntityUid uid, MailComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;

if (args.Component.TryGetData(MailVisuals.JobIcon, out string job))
{
job = job.Substring(7); // :clueless:

args.Sprite.LayerSetState(MailVisualLayers.JobStamp, job);
}

}
}

public enum MailVisualLayers : byte
{
Icon,
Lock,
FragileStamp,
JobStamp,
PriorityTape,
Breakage,
}
}
108 changes: 108 additions & 0 deletions Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Threading;
using Robust.Shared.Audio;
using Content.Shared.Storage;
using Content.Shared.Mail;

namespace Content.Server.Mail.Components
{
[RegisterComponent]
public sealed partial class MailComponent : SharedMailComponent
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("recipient")]
public string Recipient = "None";

[ViewVariables(VVAccess.ReadWrite)]
[DataField("recipientJob")]
public string RecipientJob = "None";

// Why do we not use LockComponent?
// Because this can't be locked again,
// and we have special conditions for unlocking,
// and we don't want to add a verb.
[ViewVariables(VVAccess.ReadWrite)]
[DataField("isLocked")]
public bool IsLocked = true;

/// <summary>
/// Is this parcel profitable to deliver for the station?
/// </summary>
/// <remarks>
/// The station won't receive any award on delivery if this is false.
/// This is useful for broken fragile packages and packages that were
/// not delivered in time.
/// </remarks>
[DataField("isProfitable")]
public bool IsProfitable = true;

/// <summary>
/// Is this package considered fragile?
/// </summary>
/// <remarks>
/// This can be set to true in the YAML files for a mail delivery to
/// always be Fragile, despite its contents.
/// </remarks>
[DataField("isFragile")]
public bool IsFragile = false;

/// <summary>
/// Is this package considered priority mail?
/// </summary>
/// <remarks>
/// There will be a timer set for its successful delivery. The
/// station's bank account will be penalized if it is not delivered on
/// time.
///
/// This is set to false on successful delivery.
///
/// This can be set to true in the YAML files for a mail delivery to
/// always be Priority.
/// </remarks>
[DataField("isPriority")]
public bool IsPriority = false;

/// <summary>
/// What will be packaged when the mail is spawned.
/// </summary>
[DataField("contents")]
public List<EntitySpawnEntry> Contents = new();

/// <summary>
/// The amount that cargo will be awarded for delivering this mail.
/// </summary>
[DataField("bounty")]
public int Bounty = 750;

/// <summary>
/// Penalty if the mail is destroyed.
/// </summary>
[DataField("penalty")]
public int Penalty = -250;

/// <summary>
/// The sound that's played when the mail's lock is broken.
/// </summary>
[DataField("penaltySound")]
public SoundSpecifier PenaltySound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg");

/// <summary>
/// The sound that's played when the mail's opened.
/// </summary>
[DataField("openSound")]
public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/packetrip.ogg");

/// <summary>
/// The sound that's played when the mail's lock has been emagged.
/// </summary>
[DataField("emagSound")]
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");

/// <summary>
/// Whether this component is enabled.
/// Removed when it becomes trash.
/// </summary>
public bool IsEnabled = true;

public CancellationTokenSource? priorityCancelToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Content.Server.Mail.Components
{
[RegisterComponent]
public sealed partial class MailReceiverComponent : Component
{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using Robust.Shared.Audio;

namespace Content.Server.Mail.Components
{
/// <summary>
/// This is for the mail teleporter.
/// Random mail will be teleported to this every few minutes.
/// </summary>
[RegisterComponent]
public sealed partial class MailTeleporterComponent : Component
{

// Not starting accumulator at 0 so mail carriers have some deliveries to make shortly after roundstart.
[DataField("accumulator")]
public float Accumulator = 285f;

[DataField("teleportInterval")]
public TimeSpan TeleportInterval = TimeSpan.FromMinutes(5);

/// <summary>
/// The sound that's played when new mail arrives.
/// </summary>
[DataField("teleportSound")]
public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");

/// <summary>
/// The MailDeliveryPoolPrototype that's used to select what mail this
/// teleporter can deliver.
/// </summary>
[DataField("mailPool")]
public string MailPool = "RandomMailDeliveryPool";

/// <summary>
/// How many mail candidates do we need per actual delivery sent when
/// the mail goes out? The number of candidates is divided by this number
/// to determine how many deliveries will be teleported in.
/// It does not determine unique recipients. That is random.
/// </summary>
[DataField("candidatesPerDelivery")]
public int CandidatesPerDelivery = 8;

[DataField("minimumDeliveriesPerTeleport")]
public int MinimumDeliveriesPerTeleport = 1;

/// <summary>
/// Do not teleport any more mail in, if there are at least this many
/// undelivered parcels.
/// </summary>
/// <remarks>
/// Currently this works by checking how many MailComponent entities
/// are sitting on the teleporter's tile.
///
/// It should be noted that if the number of actual deliveries to be
/// made based on the number of candidates divided by candidates per
/// delivery exceeds this number, the teleporter will spawn more mail
/// than this number.
///
/// This is just a simple check to see if anyone's been picking up the
/// mail lately to prevent entity bloat for the sake of performance.
/// </remarks>
[DataField("maximumUndeliveredParcels")]
public int MaximumUndeliveredParcels = 5;

/// <summary>
/// Any item that breaks or is destroyed in less than this amount of
/// damage is one of the types of items considered fragile.
/// </summary>
[DataField("fragileDamageThreshold")]
public int FragileDamageThreshold = 10;

/// <summary>
/// What's the bonus for delivering a fragile package intact?
/// </summary>
[DataField("fragileBonus")]
public int FragileBonus = 100;

/// <summary>
/// What's the malus for failing to deliver a fragile package?
/// </summary>
[DataField("fragileMalus")]
public int FragileMalus = -100;

/// <summary>
/// What's the chance for any one delivery to be marked as priority mail?
/// </summary>
[DataField("priorityChance")]
public float PriorityChance = 0.1f;

/// <summary>
/// How long until a priority delivery is considered as having failed
/// if not delivered?
/// </summary>
[DataField("priorityDuration")]
public TimeSpan priorityDuration = TimeSpan.FromMinutes(5);

/// <summary>
/// What's the bonus for delivering a priority package on time?
/// </summary>
[DataField("priorityBonus")]
public int PriorityBonus = 250;

/// <summary>
/// What's the malus for failing to deliver a priority package?
/// </summary>
[DataField("priorityMalus")]
public int PriorityMalus = -250;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Content.Server.Mail;

/// <summary>
/// Designates a station as a place for sending and receiving mail.
/// </summary>
[RegisterComponent]
public sealed partial class StationMailRouterComponent : Component
{
}
Loading
Loading