]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Cargo Mail System (#35429)
authorScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Fri, 7 Mar 2025 13:51:08 +0000 (14:51 +0100)
committerGitHub <noreply@github.com>
Fri, 7 Mar 2025 13:51:08 +0000 (14:51 +0100)
* shitcode init

* biocoding, SpawnTableOnUse, Moving shit to shared

* server :(

* fixes

* ok works

* Discard changes to Content.Shared/Interaction/Events/GettingUsedAttemptEvent.cs

* Discard changes to Content.Shared/Forensics/Components/FingerprintMaskComponent.cs

* Discard changes to Content.Shared/Forensics/Components/FingerprintComponent.cs

* Discard changes to Content.Server/Forensics/Systems/ForensicsSystem.cs

* Discard changes to Content.Server/StationRecords/Systems/StationRecordsSystem.cs

* Discard changes to Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs

* Discard changes to Content.Shared/Interaction/Events/GettingUsedAttemptEvent.cs

* big stuff

* preperation

* temperory spawning thing for testing

* Update CargoDeliveryDataComponent.cs

* kinda proper spawning idk god save me

* cleanup (kinda)

* preparation 2.0

* stuff i think

* entity table work

* renames

* spawn ratio based on players

* comment

* letter tables

* more spam

* package tables

* comment

* biocodedn't

* builds correctly

* cleaning

* Update deliveries_tables.yml

* labels

* package sprites

* mail teleporter

* revert testing value

* fix test

* fix other test

* i love tests

* mail teleporter enabled by default

* random cooldowns

* fixtures

* Discard changes to Content.Shared/FingerprintReader/FingerprintReaderComponent.cs

* Discard changes to Content.Shared/FingerprintReader/FingerprintReaderSystem.cs

* Discard changes to Content.Shared/Interaction/Events/GettingUsedAttemptEvent.cs

* Discard changes to Resources/Locale/en-US/fingerprint-reader/fingerprint-reader.ftl

* clean

* fuck paper scrap

* oops

* fuck SpawnTableOnUse

* mail teleporter board in QM locker + addressed review

* oops

* clean

* sound on delivery spawn

* address review

* partial review address

* partial review addressing

* addressing partial review

* pratarial revivew address

* misprediction hell

* stuff

* more stuff

* unrelated

* TODO

* link

* partial review

* DirtyField

---------

Co-authored-by: Milon <milonpl.git@proton.me>
51 files changed:
Content.Client/Delivery/DeliverySystem.cs [new file with mode: 0644]
Content.Client/Delivery/DeliveryVisualizerSystem.cs [new file with mode: 0644]
Content.Server/Delivery/CargoDeliveryDataComponent.cs [new file with mode: 0644]
Content.Server/Delivery/DeliverySystem.Spawning.cs [new file with mode: 0644]
Content.Server/Delivery/DeliverySystem.cs [new file with mode: 0644]
Content.Server/Shuttles/Systems/SpaceGarbageSystem.cs
Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs
Content.Shared/Delivery/DeliveryComponent.cs [new file with mode: 0644]
Content.Shared/Delivery/DeliverySpawnerComponent.cs [new file with mode: 0644]
Content.Shared/Delivery/DeliveryVisuals.cs [new file with mode: 0644]
Content.Shared/Delivery/SharedDeliverySystem.cs [new file with mode: 0644]
Content.Shared/FingerprintReader/FingerprintReaderSystem.cs
Content.Shared/Interaction/Events/GettingUsedAttemptEvent.cs
Content.Shared/Labels/Components/LabelComponent.cs
Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs
Content.Shared/Shuttles/Components/SpaceGarbageComponent.cs [moved from Content.Server/Shuttles/Components/SpaceGarbageComponent.cs with 53% similarity]
Resources/Locale/en-US/delivery/delivery-component.ftl [new file with mode: 0644]
Resources/Locale/en-US/delivery/delivery-spam.ftl [new file with mode: 0644]
Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml
Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Deliveries/deliveries_items.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Deliveries/deliveries_tables.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
Resources/Prototypes/Entities/Stations/base.yml
Resources/Prototypes/Entities/Stations/nanotrasen.yml
Resources/Prototypes/Entities/Structures/Machines/mail_teleporter.yml [new file with mode: 0644]
Resources/Prototypes/SoundCollections/deliveries.yml [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/broken.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/fragile.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/locked.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/postmark.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority_inactive.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail.rsi/trash.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/broken.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/fragile.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-left.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-right.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/locked.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/postmark.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority_inactive.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/trash.png [new file with mode: 0644]
Resources/Textures/Structures/mailbox.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Structures/mailbox.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/mailbox.rsi/unlit.png [new file with mode: 0644]

diff --git a/Content.Client/Delivery/DeliverySystem.cs b/Content.Client/Delivery/DeliverySystem.cs
new file mode 100644 (file)
index 0000000..c83e09e
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Delivery;
+
+namespace Content.Client.Delivery;
+
+public sealed class DeliverySystem : SharedDeliverySystem;
diff --git a/Content.Client/Delivery/DeliveryVisualizerSystem.cs b/Content.Client/Delivery/DeliveryVisualizerSystem.cs
new file mode 100644 (file)
index 0000000..8ed1534
--- /dev/null
@@ -0,0 +1,45 @@
+using Content.Shared.Delivery;
+using Content.Shared.StatusIcon;
+using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Delivery;
+
+public sealed class DeliveryVisualizerSystem : VisualizerSystem<DeliveryComponent>
+{
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly IPrototypeManager _prototype = default!;
+    [Dependency] private readonly SpriteSystem _sprite = default!;
+
+    private static readonly ProtoId<JobIconPrototype> UnknownIcon = "JobIconUnknown";
+
+    protected override void OnAppearanceChange(EntityUid uid, DeliveryComponent component, ref AppearanceChangeEvent args)
+    {
+        if (args.Sprite == null)
+            return;
+
+        _appearance.TryGetData(uid, DeliveryVisuals.JobIcon, out string job, args.Component);
+
+        if (string.IsNullOrEmpty(job))
+            job = UnknownIcon;
+
+        if (!_prototype.TryIndex<JobIconPrototype>(job, out var icon))
+        {
+            args.Sprite.LayerSetTexture(DeliveryVisualLayers.JobStamp, _sprite.Frame0(_prototype.Index("JobIconUnknown")));
+            return;
+        }
+
+        args.Sprite.LayerSetTexture(DeliveryVisualLayers.JobStamp, _sprite.Frame0(icon.Icon));
+    }
+}
+
+public enum DeliveryVisualLayers : byte
+{
+    Icon,
+    Lock,
+    FragileStamp,
+    JobStamp,
+    PriorityTape,
+    Breakage,
+    Trash,
+}
diff --git a/Content.Server/Delivery/CargoDeliveryDataComponent.cs b/Content.Server/Delivery/CargoDeliveryDataComponent.cs
new file mode 100644 (file)
index 0000000..f54da6c
--- /dev/null
@@ -0,0 +1,51 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Delivery;
+
+/// <summary>
+/// Component given to a station to indicate it can have deliveries spawn on it.
+/// </summary>
+[RegisterComponent, AutoGenerateComponentPause]
+public sealed partial class CargoDeliveryDataComponent : Component
+{
+    /// <summary>
+    /// The time at which the next delivery will spawn.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+    public TimeSpan NextDelivery;
+
+    /// <summary>
+    /// Minimum cooldown after a delivery spawns.
+    /// </summary>
+    [DataField]
+    public TimeSpan MinDeliveryCooldown = TimeSpan.FromMinutes(3);
+
+    /// <summary>
+    /// Maximum cooldown after a delivery spawns.
+    /// </summary>
+    [DataField]
+    public TimeSpan MaxDeliveryCooldown = TimeSpan.FromMinutes(7);
+
+
+    /// <summary>
+    /// The ratio at which deliveries will spawn, based on the amount of people in the crew manifest.
+    /// 1 delivery per X players.
+    /// </summary>
+    [DataField]
+    public int PlayerToDeliveryRatio = 7;
+
+    /// <summary>
+    /// The minimum amount of deliveries that will spawn.
+    /// This is not per spawner unless DistributeRandomly is false.
+    /// </summary>
+    [DataField]
+    public int MinimumDeliverySpawn = 1;
+
+    /// <summary>
+    /// Should deliveries be randomly split between spawners?
+    /// If true, the amount of deliveries will be spawned randomly across all spawners.
+    /// If false, an amount of mail based on PlayerToDeliveryRatio will be spawned on all spawners.
+    /// </summary>
+    [DataField]
+    public bool DistributeRandomly = true;
+}
diff --git a/Content.Server/Delivery/DeliverySystem.Spawning.cs b/Content.Server/Delivery/DeliverySystem.Spawning.cs
new file mode 100644 (file)
index 0000000..022e67c
--- /dev/null
@@ -0,0 +1,126 @@
+using Content.Server.Power.EntitySystems;
+using Content.Server.StationRecords;
+using Content.Shared.Delivery;
+using Content.Shared.EntityTable;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Delivery;
+
+/// <summary>
+/// System for managing deliveries spawned by the mail teleporter.
+/// This covers for spawning deliveries.
+/// </summary>
+public sealed partial class DeliverySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly EntityTableSystem _entityTable = default!;
+    [Dependency] private readonly PowerReceiverSystem _power = default!;
+
+    private void InitializeSpawning()
+    {
+        SubscribeLocalEvent<CargoDeliveryDataComponent, MapInitEvent>(OnDataMapInit);
+    }
+
+    private void OnDataMapInit(Entity<CargoDeliveryDataComponent> ent, ref MapInitEvent args)
+    {
+        ent.Comp.NextDelivery = _timing.CurTime + ent.Comp.MinDeliveryCooldown; // We want an early wave of mail so cargo doesn't have to wait
+    }
+
+    private void SpawnDelivery(Entity<DeliverySpawnerComponent?> ent, int amount)
+    {
+        if (!Resolve(ent.Owner, ref ent.Comp))
+            return;
+
+        var coords = Transform(ent).Coordinates;
+
+        _audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner);
+
+        for (int i = 0; i < amount; i++)
+        {
+            var spawns = _entityTable.GetSpawns(ent.Comp.Table);
+
+            foreach (var id in spawns)
+            {
+                Spawn(id, coords);
+            }
+        }
+    }
+
+    private void SpawnStationDeliveries(Entity<CargoDeliveryDataComponent> ent)
+    {
+        if (!TryComp<StationRecordsComponent>(ent, out var records))
+            return;
+
+        var spawners = GetValidSpawners(ent);
+
+        // Skip if theres no spawners available
+        if (spawners.Count == 0)
+            return;
+
+        // We take the amount of mail calculated based on player amount or the minimum, whichever is higher.
+        // We don't want stations with less than the player ratio to not get mail at all
+        var deliveryCount = Math.Max(records.Records.Keys.Count / ent.Comp.PlayerToDeliveryRatio, ent.Comp.MinimumDeliverySpawn);
+
+        if (!ent.Comp.DistributeRandomly)
+        {
+            foreach (var spawner in spawners)
+            {
+                SpawnDelivery(spawner, deliveryCount);
+            }
+        }
+        else
+        {
+            int[] amounts = new int[spawners.Count];
+
+            // Distribute items randomly
+            for (int i = 0; i < deliveryCount; i++)
+            {
+                var randomListIndex = _random.Next(spawners.Count);
+                amounts[randomListIndex]++;
+            }
+            for (int j = 0; j < spawners.Count; j++)
+            {
+                SpawnDelivery(spawners[j], amounts[j]);
+            }
+        }
+
+    }
+
+    private List<EntityUid> GetValidSpawners(Entity<CargoDeliveryDataComponent> ent)
+    {
+        var validSpawners = new List<EntityUid>();
+
+        var spawners = EntityQueryEnumerator<DeliverySpawnerComponent>();
+        while (spawners.MoveNext(out var spawnerUid, out _))
+        {
+            var spawnerStation = _station.GetOwningStation(spawnerUid);
+
+            if (spawnerStation != ent.Owner)
+                continue;
+
+            if (!_power.IsPowered(spawnerUid))
+                continue;
+
+            validSpawners.Add(spawnerUid);
+        }
+
+        return validSpawners;
+    }
+
+    private void UpdateSpawner(float frameTime)
+    {
+        var dataQuery = EntityQueryEnumerator<CargoDeliveryDataComponent>();
+        var curTime = _timing.CurTime;
+
+        while (dataQuery.MoveNext(out var uid, out var deliveryData))
+        {
+            if (deliveryData.NextDelivery > curTime)
+                continue;
+
+            deliveryData.NextDelivery += _random.Next(deliveryData.MinDeliveryCooldown, deliveryData.MaxDeliveryCooldown); // Random cooldown between min and max
+            SpawnStationDeliveries((uid, deliveryData));
+        }
+    }
+}
diff --git a/Content.Server/Delivery/DeliverySystem.cs b/Content.Server/Delivery/DeliverySystem.cs
new file mode 100644 (file)
index 0000000..2923c6b
--- /dev/null
@@ -0,0 +1,85 @@
+using Content.Server.Cargo.Components;
+using Content.Server.Cargo.Systems;
+using Content.Server.Station.Systems;
+using Content.Server.StationRecords.Systems;
+using Content.Shared.Delivery;
+using Content.Shared.FingerprintReader;
+using Content.Shared.Labels.EntitySystems;
+using Content.Shared.StationRecords;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+
+namespace Content.Server.Delivery;
+
+/// <summary>
+/// System for managing deliveries spawned by the mail teleporter.
+/// This covers for mail spawning, as well as granting cargo money.
+/// </summary>
+public sealed partial class DeliverySystem : SharedDeliverySystem
+{
+    [Dependency] private readonly CargoSystem _cargo = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly StationRecordsSystem _records = default!;
+    [Dependency] private readonly StationSystem _station = default!;
+    [Dependency] private readonly FingerprintReaderSystem _fingerprintReader = default!;
+    [Dependency] private readonly SharedLabelSystem _label = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DeliveryComponent, MapInitEvent>(OnMapInit);
+
+        InitializeSpawning();
+    }
+
+    private void OnMapInit(Entity<DeliveryComponent> ent, ref MapInitEvent args)
+    {
+        _container.EnsureContainer<Container>(ent, ent.Comp.Container);
+
+        var stationId = _station.GetStationInMap(Transform(ent).MapID);
+
+        if (stationId == null)
+            return;
+
+        _records.TryGetRandomRecord<GeneralStationRecord>(stationId.Value, out var entry);
+
+        if (entry == null)
+            return;
+
+        ent.Comp.RecipientName = entry.Name;
+        ent.Comp.RecipientJobTitle = entry.JobTitle;
+        ent.Comp.RecipientStation = stationId.Value;
+
+        _appearance.SetData(ent, DeliveryVisuals.JobIcon, entry.JobIcon);
+
+        _label.Label(ent, ent.Comp.RecipientName);
+
+        if (TryComp<FingerprintReaderComponent>(ent, out var reader) && entry.Fingerprint != null)
+        {
+            _fingerprintReader.AddAllowedFingerprint((ent.Owner, reader), entry.Fingerprint);
+        }
+
+        Dirty(ent);
+    }
+
+    protected override void GrantSpesoReward(Entity<DeliveryComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return;
+
+        if (!TryComp<StationBankAccountComponent>(ent.Comp.RecipientStation, out var account))
+            return;
+
+        _cargo.UpdateBankAccount(ent, account, ent.Comp.SpesoReward);
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        UpdateSpawner(frameTime);
+    }
+}
index 3dc014c40e6670a61b2fd7974dadb51e6558f1a6..e3443edfd78394ff248a4420ab75a2b893491070 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Shuttles.Components;
+using Content.Shared.Shuttles.Components;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Dynamics;
 using Robust.Shared.Physics.Events;
index d3a32f500646695af4ec848b4e1273f038a725ab..3b9b498df455495d00c895743a59ba937bd4341b 100644 (file)
@@ -1,6 +1,6 @@
 using Content.Server.Popups;
-using Content.Server.Shuttles.Components;
 using Content.Server.Singularity.Events;
+using Content.Shared.Shuttles.Components;
 using Content.Shared.Popups;
 using Content.Shared.Singularity.Components;
 using Content.Shared.Throwing;
diff --git a/Content.Shared/Delivery/DeliveryComponent.cs b/Content.Shared/Delivery/DeliveryComponent.cs
new file mode 100644 (file)
index 0000000..19effcd
--- /dev/null
@@ -0,0 +1,68 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Component given to deliveries.
+/// Means the entity is a delivery, which upon opening will grant a reward to cargo.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
+public sealed partial class DeliveryComponent : Component
+{
+    /// <summary>
+    /// Whether this delivery has been opened before.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IsOpened;
+
+    /// <summary>
+    /// Whether this delivery is still locked using the fingerprint reader.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IsLocked = true;
+
+    /// <summary>
+    /// The amount of spesos that gets added to the station bank account on unlock.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int SpesoReward = 500;
+
+    /// <summary>
+    /// The name of the recipient of this delivery.
+    /// Used for the examine text.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? RecipientName;
+
+    /// <summary>
+    /// The job of the recipient of this delivery.
+    /// Used for the examine text.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? RecipientJobTitle;
+
+    /// <summary>
+    /// The EntityUid of the station this delivery was spawned on.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? RecipientStation;
+
+    /// <summary>
+    /// The sound to play when the delivery is unlocked.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? UnlockSound = new SoundCollectionSpecifier("DeliveryUnlockSounds", AudioParams.Default.WithVolume(-10));
+
+    /// <summary>
+    /// The sound to play when the delivery is opened.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? OpenSound = new SoundCollectionSpecifier("DeliveryOpenSounds");
+
+    /// <summary>
+    /// The container with all the contents of the delivery.
+    /// </summary>
+    [DataField]
+    public string Container = "delivery";
+}
diff --git a/Content.Shared/Delivery/DeliverySpawnerComponent.cs b/Content.Shared/Delivery/DeliverySpawnerComponent.cs
new file mode 100644 (file)
index 0000000..4491509
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.EntityTable.EntitySelectors;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Used to mark entities that are valid for spawning deliveries on.
+/// If this requires power, it needs to be powered to count as a valid spawner.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class DeliverySpawnerComponent : Component
+{
+    /// <summary>
+    /// The entity table to select deliveries from.
+    /// </summary>
+    [DataField(required: true)]
+    public EntityTableSelector Table = default!;
+
+    /// <summary>
+    /// The sound to play when the spawner spawns a delivery.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? SpawnSound = new SoundCollectionSpecifier("DeliverySpawnSounds", AudioParams.Default.WithVolume(-7));
+}
diff --git a/Content.Shared/Delivery/DeliveryVisuals.cs b/Content.Shared/Delivery/DeliveryVisuals.cs
new file mode 100644 (file)
index 0000000..6da45b6
--- /dev/null
@@ -0,0 +1,15 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Delivery;
+
+[Serializable, NetSerializable]
+public enum DeliveryVisuals : byte
+{
+    IsLocked,
+    IsTrash,
+    IsBroken,
+    IsFragile,
+    IsPriority,
+    IsPriorityInactive,
+    JobIcon,
+}
diff --git a/Content.Shared/Delivery/SharedDeliverySystem.cs b/Content.Shared/Delivery/SharedDeliverySystem.cs
new file mode 100644 (file)
index 0000000..0ac78fe
--- /dev/null
@@ -0,0 +1,175 @@
+using System.Linq;
+using Content.Shared.Shuttles.Components;
+using Content.Shared.Examine;
+using Content.Shared.FingerprintReader;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction.Events;
+using Content.Shared.NameModifier.EntitySystems;
+using Content.Shared.Popups;
+using Content.Shared.Tag;
+using Content.Shared.Verbs;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Shared side of the DeliverySystem.
+/// This covers for letters/packages, as well as spawning a reward for the player upon opening.
+/// </summary>
+public abstract class SharedDeliverySystem : EntitySystem
+{
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly FingerprintReaderSystem _fingerprintReader = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly SharedHandsSystem _hands = default!;
+    [Dependency] private readonly NameModifierSystem _nameModifier = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DeliveryComponent, ExaminedEvent>(OnExamine);
+        SubscribeLocalEvent<DeliveryComponent, UseInHandEvent>(OnUseInHand);
+        SubscribeLocalEvent<DeliveryComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
+    }
+
+    private void OnExamine(Entity<DeliveryComponent> ent, ref ExaminedEvent args)
+    {
+        var jobTitle = ent.Comp.RecipientJobTitle ?? Loc.GetString("delivery-recipient-no-job");
+        var recipientName = ent.Comp.RecipientName ?? Loc.GetString("delivery-recipient-no-name");
+
+        if (ent.Comp.IsOpened)
+        {
+            args.PushText(Loc.GetString("delivery-already-opened-examine"));
+        }
+
+        args.PushText(Loc.GetString("delivery-recipient-examine", ("recipient", recipientName), ("job", jobTitle)));
+    }
+
+    private void OnUseInHand(Entity<DeliveryComponent> ent, ref UseInHandEvent args)
+    {
+        args.Handled = true;
+
+        if (ent.Comp.IsOpened)
+            return;
+
+        if (ent.Comp.IsLocked)
+            TryUnlockDelivery(ent, args.User);
+        else
+            OpenDelivery(ent, args.User);
+    }
+
+    private void OnGetVerbs(Entity<DeliveryComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.IsOpened)
+            return;
+
+        if (_hands.IsHolding(args.User, ent))
+            return;
+
+        var user = args.User;
+
+        args.Verbs.Add(new AlternativeVerb()
+        {
+            Act = () =>
+            {
+                if (ent.Comp.IsLocked)
+                    TryUnlockDelivery(ent, user);
+                else
+                    OpenDelivery(ent, user, false);
+            },
+            Text = ent.Comp.IsLocked ? Loc.GetString("delivery-unlock-verb") : Loc.GetString("delivery-open-verb"),
+        });
+    }
+
+    private bool TryUnlockDelivery(Entity<DeliveryComponent> ent, EntityUid user, bool rewardMoney = true)
+    {
+        // Check fingerprint access if there is a reader on the mail
+        if (TryComp<FingerprintReaderComponent>(ent, out var reader) && !_fingerprintReader.IsAllowed((ent, reader), user))
+            return false;
+
+        var deliveryName = _nameModifier.GetBaseName(ent.Owner);
+
+        _audio.PlayPredicted(ent.Comp.UnlockSound, user, user);
+
+        ent.Comp.IsLocked = false;
+        UpdateAntiTamperVisuals(ent, ent.Comp.IsLocked);
+
+        DirtyField(ent, ent.Comp, nameof(DeliveryComponent.IsLocked));
+
+        var ev = new DeliveryUnlockedEvent(user);
+        RaiseLocalEvent(ent, ref ev);
+
+        if (rewardMoney)
+            GrantSpesoReward(ent.AsNullable());
+
+        _popup.PopupPredicted(Loc.GetString("delivery-unlocked", ("delivery", deliveryName)), user, user);
+        return true;
+    }
+
+    private void OpenDelivery(Entity<DeliveryComponent> ent, EntityUid user, bool attemptPickup = true)
+    {
+        var deliveryName = _nameModifier.GetBaseName(ent.Owner);
+
+        _audio.PlayPredicted(ent.Comp.OpenSound, user, user);
+
+        var ev = new DeliveryOpenedEvent(user);
+        RaiseLocalEvent(ent, ref ev);
+
+        if (attemptPickup)
+            _hands.TryDrop(user, ent);
+
+        ent.Comp.IsOpened = true;
+        _appearance.SetData(ent, DeliveryVisuals.IsTrash, ent.Comp.IsOpened);
+
+        _tag.AddTags(ent, "Trash", "Recyclable");
+        EnsureComp<SpaceGarbageComponent>(ent);
+
+        DirtyField(ent.Owner, ent.Comp, nameof(DeliveryComponent.IsOpened));
+
+        _popup.PopupPredicted(Loc.GetString("delivery-opened", ("delivery", deliveryName)), user, user);
+
+        if (!_container.TryGetContainer(ent, ent.Comp.Container, out var container))
+            return;
+
+        if (attemptPickup)
+        {
+            foreach (var entity in container.ContainedEntities.ToArray())
+            {
+                _hands.PickupOrDrop(user, entity);
+            }
+        }
+        else
+        {
+            _container.EmptyContainer(container, true, Transform(ent.Owner).Coordinates);
+        }
+    }
+
+    // TODO: generic updateVisuals from component data
+    private void UpdateAntiTamperVisuals(EntityUid uid, bool isLocked)
+    {
+        _appearance.SetData(uid, DeliveryVisuals.IsLocked, isLocked);
+
+        // If we're trying to unlock, always remove the priority tape
+        if (!isLocked)
+            _appearance.SetData(uid, DeliveryVisuals.IsPriority, false);
+    }
+
+    protected virtual void GrantSpesoReward(Entity<DeliveryComponent?> ent) { }
+}
+
+/// <summary>
+/// Event raised on the delivery when it is unlocked.
+/// </summary>
+[ByRefEvent]
+public readonly record struct DeliveryUnlockedEvent(EntityUid User);
+
+/// <summary>
+/// Event raised on the delivery when it is opened.
+/// </summary>
+[ByRefEvent]
+public readonly record struct DeliveryOpenedEvent(EntityUid User);
index f3944be9cd50ff99bc5da0460a614fc69f91e405..c627abb337f972ecfc779dd9c84e363ac2a58043 100644 (file)
@@ -31,7 +31,7 @@ public sealed class FingerprintReaderSystem : EntitySystem
         if (!target.Comp.IgnoreGloves && TryGetBlockingGloves(user, out var gloves))
         {
             if (target.Comp.FailGlovesPopup != null)
-                _popup.PopupEntity(Loc.GetString(target.Comp.FailGlovesPopup, ("blocker", gloves)), target, user);
+                _popup.PopupPredicted(Loc.GetString(target.Comp.FailGlovesPopup, ("blocker", gloves)), target, user);
             return false;
         }
 
@@ -40,7 +40,7 @@ public sealed class FingerprintReaderSystem : EntitySystem
             !target.Comp.AllowedFingerprints.Contains(fingerprint.Fingerprint))
         {
             if (target.Comp.FailPopup != null)
-                _popup.PopupEntity(Loc.GetString(target.Comp.FailPopup), target, user);
+                _popup.PopupPredicted(Loc.GetString(target.Comp.FailPopup), target, user);
 
             return false;
         }
index cd5f0293467f999c4afee8491bf7452e22c3c8d4..2c88c75eabd7b0d1a08ec6328c6757fd55acac4a 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Shared.Interaction.Events;
+namespace Content.Shared.Interaction.Events;
 
 /// <summary>
 /// Event raised on an item when attempting to use it in your hands. Cancelling it stops the interaction.
index d57023c8ab152837d0f9e7664207e2cbc5a15b82..ee508797adead0191e28e7f201c713ce0723d4f8 100644 (file)
@@ -14,4 +14,10 @@ public sealed partial class LabelComponent : Component
     /// </summary>
     [DataField, AutoNetworkedField]
     public string? CurrentLabel { get; set; }
+
+    /// <summary>
+    /// Should the label show up in the examine menu?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Examinable = true;
 }
index eb3a0f185b9be7c87aed26a293658d040249d94a..8d548715632b230edba5651e3ebe690c61821bbb 100644 (file)
@@ -35,6 +35,9 @@ public abstract partial class SharedLabelSystem : EntitySystem
         if (!Resolve(uid, ref label))
             return;
 
+        if (!label.Examinable)
+            return;
+
         if (label.CurrentLabel == null)
             return;
 
similarity index 53%
rename from Content.Server/Shuttles/Components/SpaceGarbageComponent.cs
rename to Content.Shared/Shuttles/Components/SpaceGarbageComponent.cs
index 299458ab339b66a9b1c30b0f49f2da50d79c8c74..cdcb6103fe3a3b30219933e7200c1eb8926a3295 100644 (file)
@@ -1,8 +1,10 @@
-namespace Content.Server.Shuttles.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Shuttles.Components;
 
 /// <summary>
 ///     Cleanup component that deletes the entity if it has a cross-grid collision.
 ///     Useful for small, unimportant items like bullets to avoid generating many contacts.
 /// </summary>
-[RegisterComponent]
-public sealed partial class SpaceGarbageComponent : Component {}
+[RegisterComponent, NetworkedComponent]
+public sealed partial class SpaceGarbageComponent : Component;
diff --git a/Resources/Locale/en-US/delivery/delivery-component.ftl b/Resources/Locale/en-US/delivery/delivery-component.ftl
new file mode 100644 (file)
index 0000000..a26a604
--- /dev/null
@@ -0,0 +1,10 @@
+delivery-recipient-examine = This one is meant for {$recipient}, {$job}.
+delivery-already-opened-examine = It was already opened.
+delivery-recipient-no-name = Unnamed
+delivery-recipient-no-job = Unknown
+
+delivery-unlocked = You unlock the {$delivery} with your fingerprint.
+delivery-opened = You open the {$delivery}.
+
+delivery-unlock-verb = Unlock
+delivery-open-verb = Open
diff --git a/Resources/Locale/en-US/delivery/delivery-spam.ftl b/Resources/Locale/en-US/delivery/delivery-spam.ftl
new file mode 100644 (file)
index 0000000..5c81c33
--- /dev/null
@@ -0,0 +1,235 @@
+# All spelling mistakes and broken english are intentional!
+# I hate saving paper contents in ftl files
+
+delivery-spam-robust-toolboxes = [color=blue][head=1]
+                                 ░░▄▀░░
+                                 ░▄█▄▄▀ [head=3]ROBUST - TOOLBOXES AND TOOLS[/head]
+                                 ██▀░░░ [/head][/color]
+
+                                 {"[bold]BUY ONE TOOLBOX, GET ONE SET OF TOOLS FOR FREE![/bold]"}
+
+                                 AS YOU ARE ONE OF OUR VALUED CUSTOMERS, YOU GET A CUSTOMER BONUS, YOUR TOOLS COME RUST AND LEAD-FREE!!! ISN'T THAT AMAZING? THE TOOLBOX ON THE OTHER HAND, COMES WITH EXTRA LEAD! AMAZING FOR SMASHING SKULLS AND STOPPING RADIATION ALIKE!
+
+                                 {"[bold]ALL THIS AND POSSIBLY MORE FOR ONLY ONE ORGAN![/bold]"}
+
+                                 ROBUST - TOOLBOXES AND TOOLS:
+                                 -LEAD AND ASBESTOS FREE!
+                                 -OR WITH LEAD AND ASBESTOS, IF YOU PREFER!
+                                 -CHEAP! ONLY ONE ORGAN! THAT'S LESS THAN TWO ORGANS!
+                                 -DOESN'T HAVE TO BE YOUR ORGAN! WE DON'T JUDGE!
+
+delivery-spam-reasons-to-chose-nanotrasen = [color=blue]
+                                                                      ╔══════════════════╗
+                                                                      ║███░███░░░░██░░░░░║
+                                                                      ║░██░████░░░██░░░░░║
+                                                                      ║░░█░██░██░░██░█░░░║
+                                                                      ║░░░░██░░██░██░██░░║
+                                                                      ║░░░░██░░░████░███░║
+                                                                      ╚══════════════════╝[/color]
+
+                                       {"[head=2]TOP THREE REASONS WHY THE SYNDICATE IS INCOMPETENT[/head]"}
+
+                                       {"[bold]NUMBER ONE[/bold]"}
+                                       THEIR SLEEPER AGENTS ARE INCOMPETENT! THEY CAN'T EVEN KILL A PASSENGER WITH A DEATHWISH!
+
+                                       {"[bold]NUMBER TWO[/bold]"}
+                                       THEIR CIVILIANS ARE WEAK TO BULLETS! TRUST ME, WE TRIED! UNLIKE THE NANOTRASEN CIVILIANS, SYNDICATE CIVILIANS DIE FROM A BULLET TO THE SKULL! BULLETS WE HAVE!
+
+                                       {"[bold]NUMBER THREE[/bold]"}
+                                       THEIR LOGO IS HORRIBLE! THEY THINK THEY'RE COOL WITH THEIR LOGO! OOH, LOOK AT ME, I'M SO COOL! OOH, SNAKE THAT'S ALSO AN S! HOW CREATIVE! MY THREE YEAR OLD SON COULD DRAW A BETTER LOGO!
+
+delivery-spam-reasons-to-choose-syndicate = [color=#ff0000]
+                                                                          ╔══════════════════╗
+                                                                          ║░░░░░████████░░░░░║
+                                                                          ║░░░░░██░░░░░░░░░░░║
+                                                                          ║░░░░░████████░░░░░║
+                                                                          ║░░░░░░░░░░░██░░░░░║
+                                                                          ║░░░░░████████░░░░░║
+                                                                          ╚══════════════════╝[/color]
+
+                                            {"[head=2]TOP THREE REASONS WHY NANOTRASEN IS INCOMPETENT[/head]"}
+
+                                            {"[bold]NUMBER ONE[/bold]"}
+                                            THEIR GUNS SUCK! THEY DON'T EVEN HAVE SNIPER RIFLES! THEIR SECURITY FORCES CAN'T EVEN CARRY BIG GUNS IN MOST SITUATIONS!
+
+                                            {"[bold]NUMBER TWO[/bold]"}
+                                            THEIR COMMANDERS? THEY DIE FROM A SINGLE SHOT! NO COOL ARMOR! JUST BANG, DEAD! LAME! OUR COMMANDERS GET COOL HARDSUITS!
+
+                                            {"[bold]NUMBER THREE[/bold]"}
+                                            THEIR MURDER METHODS ARE UNINSPIRED! IT'S JUST GUN! THERE'S NO THROWING PEOPLE INTO DEEP SPACE, NO FEEDING PEOPLE INTO RECYCLERS WITH SAFETY MODE DISENGAGED, NO SLIPPING BOMBS INTO POCKETS! SO BORING!
+
+delivery-spam-tired-of-science = [head=3]Science will LOVE you for this!!
+
+                                 are [bold]YOU[/bold] Tired of your Station's Science Department blowing up withoutdoing any actual science?
+                                 Well Your in luck![/head]
+
+                                 Folow this simple guide, and we'll ensure your Science [italic]Never Works Again![/italic]
+
+                                 Simply do the following:
+                                 - Step One: Locate your Science Department's Research Server
+                                 - Step Two: Un-anchor the Research Server from the ground
+                                 - Step Three: Hurl the Research Server into space, preferably in the direction of the Spider Clan Super Secret Space Dojo
+                                 - Step Four: Wait appproximately 3-5 Business Shifts
+                                 - Step Five: Our Workers at Spid-ex Inc will provide your station with one (1) techdisk per week.
+
+                                 {"[color=lightgray]Note: Spider Clan is not responsible for any punishment issued by your supervisors.[/color]"}
+
+delivery-spam-free-all-access = [head=3]Have You ever wanted to have [italic][color=green]Free [bold]All Axcess!?!?[/bold][/color][/italic][/head]
+                                {"[head=2]Well NOW YOU CAN!![/head]!"}
+
+                                All you need to do is call [color=blue]555-GOUR-LECKSSS[/color] and state your Staton ID# !!!
+                                Once youve done that,  we can simply remotely query the wallet of Yourstation's Cargo department, extacting our required fees of three [italic] EASY[/italic] payments, allowing you to claim your
+                                {"[head=2][color=green] [bolditalic] FREE AA!!!!!!!!!![/bolditalic][/color][/head]"}
+
+
+                                {"[color=gray]"}
+                                {"[bullet/]Note: station ID must be stated in the format of \"NT/NX - ###\""}
+                                {"[bullet/]Note: Payments lodged to the client's station's cargo department amount to roughly $5000 spesos per transaction, not including individual processing fees"}
+                                {"[bullet/]Note: We at Gour-Lecksss LMT. are not responsible if your station's HoP forces you to fill out an ACTUAL Free AA form if they find out about this letter"}
+                                {"[/color]"}
+
+delivery-spam-centcomm-retribution = [color=red] THIS IS AN OFICAL NOTICE FROM THE HEAD OF [color=blue]NANOTRASN[/color][/color]
+
+                                     Dear Sir, Madam, or Other Insignificat station personell
+
+                                     If you do not wish for this station to be declared Unprofitable in the eyes of
+                                     {"[head=2][italic] Our Great and Glorious [color=blue]Nanotransen[[/color][/head]"}
+                                     Then you must organize for three [color=blue]Nt[/color] Standard Stacks of [color=blue]nt[/color] Standard Gold Ingots to be sent to your station's Away Trade Outpots within 5 [color=blue]nT[/color] Standard work shifts.
+
+                                     {"[head=2][color=red]IGNORE THIS ORDER AT RISK OF RETRIBUTON FROM [color=green]CENTCO[/color]!!!!![/head][/color]"}
+
+delivery-spam-alternate-timeline = [color=red]
+                                                                ╔══════════════════╗
+                                                                ║███░███░░░░██░░░░░║
+                                                                ║░██░████░░░██░░░░░║
+                                                                ║░░█░██░██░░██░█░░░║
+                                                                ║░░░░██░░██░██░██░░║
+                                                                ║░░░░██░░░████░███░║
+                                                                ╚══════════════════╝[/color]
+                                   {"[head=2]This is an official notice from the [color=red]Chief Security Officer[/color] at a Nanotrasen's Space Station 15.[/head]"}
+
+                                   To whoever receives this letter. I am Sergeant Rigel. My occupation is the CSO. We need immediate assistance.
+
+                                   Our station is currently under attack by Atomic Agents, this letter is being thrown into a destabilized bluespace anomaly created by our [color=purple]Head of Research[/color].
+
+                                   I am currently bolted in the Bridge, if you receive this message, please send aid immediately. I don't know how much longer we can last.
+
+                                   Glory to Nanotrasen.
+
+delivery-spam-narsie-cult =                         [color=#134975][head=2]The Children of Nar'Sie[/head][/color]
+                                                         The Beginning of a New Era
+                            {"[bold]══──══──══──══──══──══──══──══──══──══──══[/bold]"}
+
+                            {"[head=3]Do you feel lost in the vastness of our cosmos?[/head]"}
+                            In the modern era, it's easy for wayward souls to feel like cogs in the machine of vast corporations.
+
+                            {"[head=3]Do you feel as if you're made for a better purpose?[/head]"}
+                            Do you tire of the life of mundanity forced upon you? Mopping floors, delivering boxes, or filling out endless paperwork?
+
+                            {"[head=3]Do you want to make the galaxy a better place?[/head]"}
+
+
+                            If you answered "Yes" to any of these questions, then contact one of our representatives today! We have members across stations all over the galaxy eager to welcome new members into our flock. Be one of the blades that helps welcome the Geometer of Blood into our universe so that all may know his bliss!
+
+                            All you have to do is say [color=#FF0000][italic]"Sas'so c'arta forbici!"[/italic][/color]
+
+delivery-spam-rage-cage = [color=#aaaaaa]▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀[/color]
+                                                       {"[bold][head=1]THE RAGE CAGE[/head][/bold]"}
+                          {"[color=#aaaaaa]▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬[/color]"}
+                          {"[bold][color=#FF0000][head=3]DO YOU WANT TO FIGHT?[/head][/color][/bold]"}
+                          {"[bold][color=#FF0000][head=3]DO YOU WANT TO WIN?[/head][/color][/bold]"}
+                          {"[bold][color=#FF0000][head=3]DO YOU WANT TO DOMINATE?![/head][/color][/bold]"}
+
+
+
+                          Then come on down to... [color=#FF0000][bold][head=2]THE RAGE CAGE[/head][/bold][/color]
+
+
+
+                          Hidden in the depths of your local Nanotrasen station is the patented [color=#FF0000][bold]RAGE CAGE[/bold][/color]. An electrified fighting arena designed for only the strongest of fighters, the [color=#FF0000][bold]RAGE CAGE[/bold][/color] seperates the Wimps from the Warriors, the Scrubs from the Soldiers, and the Losers from the Winners.
+                          ──────────────────────────────────────────
+                          In the [color=#FF0000][bold]RAGE CAGE[/bold][/color] there is only one rule: [italic]Two fighters enter. One fighter leaves. [/italic]
+                          ──────────────────────────────────────────
+                          No weapons, no armor, just pure unadulterated [bold]COMBAT[/bold]. Don't lose out and be a [bold]WIMP[/bold]. Win the glory of being your station's most robust fighter in the [color=#FF0000][bold]RAGE CAGE[/bold][/color] today!
+
+delivery-spam-evil-lizard = [color=#FF0000][bold][head=2]STOP[/head][/bold][/color]
+
+                            If yore reading this letter...[color=#FF0000][head=3]YOUR ALRAEDY CURSED!!![/head][/color]
+
+                            Im sorry to do this to you but I have to warn you about:
+
+                                   {"[color=#FF0000][head=1]The Ghost of The Bloody Lizardd[/head][/color]"}
+
+                            It all started when i to got a letter in the mail: it was a scary image of a lizard plushie with BLOODY EYES staring RIGHT AT ME. the letter said I was cursed...and if I didn't send this letter to 30 people within 30 days then the Blood Lizard would come in the middle of the night...
+
+                            {"[italic]and KILL ME.[/italic]"}
+
+                            im sorry......but your one of the 30 people i have to send this too..and now yoor cursed too...
+
+                            please send this letter to 30 other people to stop the curse! you can still save yorself! theres still time! don't let the bloody lizard get you too, and take this thingie! it will keep you safe from dark spiirts...[head=3]FOR NOW[/head]
+
+
+
+
+                            {"[head=1]OH NO THERE IT IS!!!!!!!![/head]"}
+
+                            ░░░░░░░░░█░░[color=#67CC40]████████[/color]█[color=#67CC40]███[/color]░░░░░░░░░░
+                            ░░░░░░[color=#FF0000]████[/color]█[color=#6EC543]█[/color][color=#67CC40]███████[/color]█[color=#FF0000]██████[/color]░░░░░░░░
+                            ░░░░[color=#FF0000]████[/color][color=#6EC543]██[/color][color=#67CC40]████████[/color][color=#FF0000]██████[/color][color=#FFFFFF]██[/color][color=#FF0000]█[/color][color=#B53737]█[/color]░░░░░░
+                            ░░░░[color=#FF0000]██[/color][color=#6EC543]██[/color][color=#67CC40]██████████[color=#FF0000]████████[/color][color=#B53737]██[/color]░░░░░░
+                            ░░[color=#6EC543]██████[/color][color=#67CC40]██████████[/color][color=#FF0000]███████[/color][color=#B53737]███[/color]░░░░░░
+                            {"[color=#6EC543]██████[/color][color=#67CC40]██████████████[/color][color=#FF0000]███[/color][color=#B53737]███[/color]░░░░░░░░"}
+                            {"[color=#6EC543]██████[/color][color=#67CC40]██████████████[/color][color=#FF0000]█[/color][color=#6EC543]█████[/color]░░░░░░░░"}
+                            {"[color=#6EC543]██████████[/color][color=#67CC40]██████████[/color][color=#FF0000]█[/color][color=#67CC40]███[/color]░░░░░░░░░░"}
+                            ░░[color=#6EC543]██[/color][color=#FF2020]██[/color][color=#FF3D3D]██[/color][color=#6EC543]████████[/color][color=#67CC40]████[/color][color=#86E158]██[/color]░░░░░░░░░░░░
+                            ░░░░[color=#FF2020]██[/color][color=#FF3D3D]██[/color]░░░░[color=#86E158]████████[/color][color=#6EC543]██[/color][color=#61D034]██[/color]░░░░░░░░[color=#56B037]██[/color]
+                            ░░░░[color=#FF2020]██[/color]░░░░[color=#A8EB7A]██[/color][color=#B5EE85]██████[/color][color=#A8EB7A]██[/color][color=#6EC543]████[/color][color=#61D034]██[/color]░░░░[color=#56B037]██[/color][color=#48A926]██[/color]
+                            ░░░░░░░░[color=#A8EB7A]██[/color][color=#B5EE85]██████████[/color][color=#A8EB7A]██[/color][color=#6EC543]████[/color][color=#56B037]██[/color][color=#48A926]██████[/color]
+                            ░░░░[color=#6EC543]██[/color][color=#61D034]██[/color][color=#A8EB7A]██[/color][color=#B5EE85]██████████[/color][color=#A8EB7A]██[/color][color=#48A926]████████████[/color]
+                            ░░░░[color=#6EC543]████[/color][color=#86E158]██[/color][color=#A8EB7A]██[/color][color=#B5EE85]████[/color][color=#A8EB7A]██[/color][color=#86E158]██[/color][color=#61D034]████[/color][color=#6EC543]██[/color][color=#48A926]████[/color][color=#52A037]██[/color]░░
+                            ░░░░[color=#6EC543]████[/color][color=#61D034]██[/color][color=#86E158]████████[/color][color=#61D034]██[/color][color=#6EC543]██████[/color][color=#52A037]████[/color]░░░░
+                            ░░░░░░[color=#6EC543]████[/color]░░░░░░░░[color=#6EC543]████████[/color]░░░░░░░░
+                            ░░░░░░░░░░░░░░░░░░░░[color=#6EC543]████[/color]░░░░░░░░░░
+
+delivery-spam-parents-need-money = [bold]Hello Child,[/bold]
+
+                                   This is your Parents writing to you: we are in need of money! Our taxes have been audited and we owe $100,000 in spesos to governnment! please help! they only want gift cards so you will need to send us 100 $1000 spesos Bisa gift cards.
+
+                                   please mail to:
+                                   50192 Spess Lane
+                                   Station City, Ignius 40195-243
+                                   Gamma Quadrant
+                                   Guilimin System
+
+                                   if we do not get this money in 10 days then the govertment will come to take away childhood home and we will be homeless.
+
+                                   {"[bold]thank you and we love you,[/bold]"}
+                                   {"[italic]parents[/italics]"}
+
+delivery-spam-voyage-advertisement = [head=2]Join us on the maiden voyage of the...[/head]
+
+                                          ░█▀▀░█▀▀░░░█▀▄░█▀▀░█░░░█▀█░█░█░█▀█░█▀▄
+                                          ░▀▀█░▀▀█░░░█▀▄░█▀▀░█░░░█▀█░▄▀▄░█░█░█▀▄
+                                          ░▀▀▀░▀▀▀░░░▀░▀░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀░▀
+
+
+                                     {"[bold]══════════════════════════════════════════[/bold]"}
+
+                                     The latest in Comfortech™ and the most beautiful sights this side of the Iraxsi System! The [italic]SS Relaxor[/italic] is a state of the art luxury Cruiser taking you on the journey of a lifetime!
+
+                                     {"[head=3]Experience the phosphorous lakes of Galimar* from the comfort of our LuxuCabins™ with the all the modern amenities you could ever wish for![/head]"}
+
+                                     {"[head=3]Gaze in awe at the Eye of the Cosmos** while enjoying meals from our Five Star Galaxy class chefs![/head]"}
+
+                                     {"[head=3]Explore the ruins of Agathar***, now open to the public with the assistance of Nanotrasen's top Scientists. All the mysteries of the old Agatharian civilization are excavated and displayed for your viewing pleasure![/head]"}
+
+                                     For the low, low cost of $5,000 spesos a night, the six month luxury cruise could be yours for the vacation of your dreams! Call us today at [color=#00FF00]RELAX-NOW[/color] to book your cruise. Don't wait! Act now!
+
+
+
+
+
+
+                                     {"[italic]*Phosphorus lakes are not for swimming, you waive all rights to legal representations with Relaxination Destinations upon landing on Galimar.[/italic]"}
+                                     {"[italic]**Eye of the Cosmos must not be looked at for longer than five seconds at a time. You do not hear the call of the Eye.[/italic]"}
+                                     {"[italic]***Must sign safety waiver before landing, Relaxination Destinations does not guarantee the safety of the Agatharian ruins. Disappearances of tour groups are down to an acceptable margin of 0.23% of all tour groups that visit the ruins.[/italic]"}
index 0145259dacab96338d7a0040a0b05920d8c42c4e..b63d661ae9818c8d79d55a709f503e405f05f8bf 100644 (file)
@@ -18,6 +18,7 @@
     - id: RubberStampDenied
     - id: RubberStampQm
     - id: AstroNavCartridge
+    - id: MailTeleporterMachineCircuitboard
 
 - type: entity
   id: LockerQuarterMasterFilled
index 287d63824530e154e2c1e533250176bcd33bca88..e6882fa621195273458e7a0a6e2906e85b164fe7 100644 (file)
         - ReagentId: Vitamin
           Quantity: 1
 
-- type: entity         
+- type: entity
   name: grilled cheese sandwich
   parent: FoodBakedBase
   id: FoodBakedGrilledCheeseSandwich
           Quantity: 2
         - ReagentId: Vitamin
           Quantity: 3
+
+# Entity Tables
+
+- type: entityTable
+  id: FoodRandomCakeTable
+  table: !type:GroupSelector
+    children:
+    - id: FoodCakeApple
+    - id: FoodCakeBirthday
+    - id: FoodCakeBlueberry
+    - id: FoodCakePlain
+    - id: FoodCakeCarrot
+    - id: FoodCakeCheese
+    - id: FoodCakeChocolate
+    - id: FoodCakeChristmas
+    - id: FoodCakeClown
+    - id: FoodCakeLemon
+    - id: FoodCakeLime
+    - id: FoodCakeOrange
+    - id: FoodCakePumpkin
+    - id: FoodCakeSlime
+    - id: FoodCakeSpaceman
+    - id: FoodCakeVanilla
+    - id: FoodCakeLemoon
diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
new file mode 100644 (file)
index 0000000..6682874
--- /dev/null
@@ -0,0 +1,120 @@
+- type: entity
+  abstract: true
+  parent: BaseItem
+  id: BaseDelivery
+  components:
+  - type: Appearance
+  - type: GenericVisualizer
+    visuals:
+      enum.DeliveryVisuals.IsFragile:
+        enum.DeliveryVisualLayers.FragileStamp:
+          True: { visible: true }
+          False: { visible: false }
+      enum.DeliveryVisuals.IsLocked:
+        enum.DeliveryVisualLayers.Lock:
+          True: { visible: true }
+          False: { visible: false }
+      enum.DeliveryVisuals.IsPriority:
+        enum.DeliveryVisualLayers.PriorityTape:
+          True: { visible: true }
+          False: { visible: false }
+      enum.DeliveryVisuals.IsPriorityInactive:
+        enum.DeliveryVisualLayers.PriorityTape:
+          True: { shader: shaded, state: priority_inactive }
+          False: { shader: unshaded, state: priority }
+      enum.DeliveryVisuals.IsBroken:
+        enum.DeliveryVisualLayers.Breakage:
+          True: { visible: true }
+          False: { visible: false }
+      enum.DeliveryVisuals.IsTrash:
+        enum.DeliveryVisualLayers.Trash:
+          True: { visible: true }
+          False: { visible: false }
+  - type: Label
+    examinable: false
+  - type: FingerprintReader
+    failPopup: fingerprint-reader-fail
+    failGlovesPopup: fingerprint-reader-fail-gloves
+  - type: Delivery
+  - type: ContainerContainer
+    containers:
+      delivery: !type:Container
+
+- type: entity
+  parent: BaseDelivery
+  id: PackageDelivery
+  name: package
+  components:
+  - type: Sprite
+    sprite: Objects/Specific/Cargo/mail_large.rsi
+    scale: 0.85,0.85
+    layers:
+    - state: icon
+      map: [ "enum.DeliveryVisualLayers.Icon" ]
+    - state: trash
+      map: [ "enum.DeliveryVisualLayers.Trash" ]
+      visible: false
+    - state: postmark
+    - map: [ "enum.DeliveryVisualLayers.JobStamp" ]
+      scale: 0.6, 0.6
+      offset: -0.23, -0.23
+    - state: fragile
+      map: [ "enum.DeliveryVisualLayers.FragileStamp" ]
+      visible: false
+    - state: locked
+      map: [ "enum.DeliveryVisualLayers.Lock" ]
+    - state: priority
+      map: [ "enum.DeliveryVisualLayers.PriorityTape" ]
+      visible: false
+      shader: unshaded
+    - state: broken
+      map: [ "enum.DeliveryVisualLayers.Breakage" ]
+      visible: false
+  - type: MultiHandedItem
+  - type: Item
+    size: Huge
+  - type: Delivery
+    spesoReward: 1000
+  - type: EntityTableContainerFill
+    containers:
+      delivery: !type:NestedSelector
+        tableId: PackageDeliveryRewards
+
+- type: entity
+  parent: BaseDelivery
+  id: LetterDelivery
+  name: letter
+  components:
+  - type: Sprite
+    sprite: Objects/Specific/Cargo/mail.rsi
+    scale: 0.8,0.8
+    layers:
+    - state: icon
+      map: [ "enum.DeliveryVisualLayers.Icon" ]
+    - state: trash
+      map: [ "enum.DeliveryVisualLayers.Trash" ]
+      visible: false
+    - state: postmark
+    - map: [ "enum.DeliveryVisualLayers.JobStamp" ]
+      scale: 0.8, 0.8
+      offset: 0.225, 0.165
+    - state: fragile
+      map: [ "enum.DeliveryVisualLayers.FragileStamp" ]
+      visible: false
+    - state: locked
+      map: [ "enum.DeliveryVisualLayers.Lock" ]
+    - state: priority
+      map: [ "enum.DeliveryVisualLayers.PriorityTape" ]
+      visible: false
+      shader: unshaded
+    - state: broken
+      map: [ "enum.DeliveryVisualLayers.Breakage" ]
+      visible: false
+  - type: Item
+    storedRotation: 90
+  - type: Delivery
+    spesoReward: 500
+  - type: EntityTableContainerFill
+    containers:
+      delivery: !type:NestedSelector
+        tableId: LetterDeliveryRewards
diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries_items.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries_items.yml
new file mode 100644 (file)
index 0000000..e0b0e09
--- /dev/null
@@ -0,0 +1,113 @@
+### Spam Mail
+
+## TODO: They all should be a localized dataset for PaperComponent
+
+# Advertisements
+- type: entity
+  id: MailRobustToolsSpam
+  name: Robust Toolbox - Special Offer!
+  description: An advertisement for the robust toolboxes.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-robust-toolboxes
+
+- type: entity
+  id: MailNanotrasenSpam
+  name: Reasons to choose Nanotrasen!
+  description: An advertisement for the Nanotrasen.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-reasons-to-chose-nanotrasen
+
+- type: entity
+  id: MailSyndicateSpam
+  name: Reasons to choose The Syndicate!
+  description: An advertisement for the The Syndicate.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-reasons-to-choose-syndicate
+
+- type: entity
+  id: MailAlternativeDimensionSpam
+  name: Send reinforcements!
+  description: An official notice from... an alternate timeline?
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-alternate-timeline
+
+- type: entity
+  id: MailNarsieCultSpam
+  name: The Children of Nar'Sie
+  description: A local cult is looking for recruits.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-narsie-cult
+
+- type: entity
+  id: MailRageCageSpam
+  name: Do you want to fight?!
+  description: Advertisement for a local fighting club.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-rage-cage
+
+- type: entity
+  id: MailVoyageAdvertisementSpam
+  name: Join us on the maiden voyage!
+  description: Advertisement for a relaxing voyage.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-voyage-advertisement
+
+# Scam Mail
+- type: entity
+  id: MailScienceSpiderClanSpam
+  name: Tired of science blowing up?
+  description: Follow these simple steps to ensure it never happens again!
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-tired-of-science
+
+- type: entity
+  id: MailAllAccessSpam
+  name: FREE ALL AXCESS!! # Spelling mistake intentional
+  description: Did you ever want free all access?!
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-free-all-access
+
+- type: entity
+  id: MailCentcommRetributionSpam
+  name: NOTICE FROM NANOTRASN!! # Spelling mistake intentional
+  description: An official notice from the CEO of Nanotrasn?!
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-centcomm-retribution
+
+- type: entity
+  id: MailEvilLizardSpam
+  name: DO NOT OPEN THIS MAIL
+  description: You have been cursed!
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-evil-lizard
+
+- type: entity
+  id: MailParentsNeedMoneySpam
+  name: Help mom and dad!
+  description: Parents in need of financial support.
+  parent: Paper
+  components:
+  - type: Paper
+    content: delivery-spam-parents-need-money
diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries_tables.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries_tables.yml
new file mode 100644 (file)
index 0000000..f0a7fc4
--- /dev/null
@@ -0,0 +1,145 @@
+### Spawners
+- type: entityTable
+  id: RandomDeliveryBasic
+  table: !type:GroupSelector
+    children:
+    - !type:NestedSelector
+      tableId: RandomDeliveryLetterBasic
+      weight: 0.7
+    - !type:NestedSelector
+      tableId: RandomDeliveryPackageBasic
+      weight: 0.3
+
+# Letters
+
+- type: entityTable # Used to pick between all different letter types, if there will be more
+  id: RandomDeliveryLetterBasic
+  table: !type:GroupSelector
+    children:
+    - id: LetterDelivery
+
+# Packages
+
+- type: entityTable # Used to pick between all different package types, if there will be more
+  id: RandomDeliveryPackageBasic
+  table: !type:GroupSelector
+    children:
+    - id: PackageDelivery
+
+### Reward Tables
+
+- type: entityTable
+  id: LetterDeliveryRewards
+  table: !type:GroupSelector
+    children:
+    - !type:NestedSelector
+      tableId: LetterCommonEntityTable
+      weight: 0.6
+    - !type:NestedSelector
+      tableId: LetterUncommonEntityTable
+      weight: 0.3
+    - !type:NestedSelector
+      tableId: LetterRareEntityTable
+      weight: 0.1
+
+- type: entityTable
+  id: PackageDeliveryRewards
+  table: !type:GroupSelector
+    children:
+    - !type:NestedSelector
+      tableId: PackageCommonEntityTable
+      weight: 0.6
+    - !type:NestedSelector
+      tableId: PackageUncommonEntityTable
+      weight: 0.3
+    - !type:NestedSelector
+      tableId: PackageRareEntityTable
+      weight: 0.1
+
+### Loot Tables
+# Letters
+- type: entityTable
+  id: LetterCommonEntityTable # Basically trash and spam mail, maybe something barely useful here and there
+  table: !type:GroupSelector
+    children:
+    - !type:NestedSelector # Don't you love getting mailed trash?
+      tableId: GenericTrashItems
+    - !type:GroupSelector
+      weight: 7
+      children:
+      - id: MailRobustToolsSpam
+      - id: MailNanotrasenSpam
+      - id: MailSyndicateSpam
+      - id: MailScienceSpiderClanSpam
+      - id: MailAllAccessSpam
+      - id: MailCentcommRetributionSpam
+      - id: MailAlternativeDimensionSpam
+      - id: MailNarsieCultSpam
+      - id: MailRageCageSpam
+      - id: MailVoyageAdvertisementSpam
+      - id: MailEvilLizardSpam
+      - id: MailParentsNeedMoneySpam
+
+- type: entityTable # TODO: Add more variety!
+  id: LetterUncommonEntityTable # Some more varied things, should never be more expensive than 100 spesos
+  table: !type:GroupSelector
+    children:
+    - id: MobCockroach
+    - id: SpaceCash100
+    - id: StrangePill
+    - id: Joint
+
+- type: entityTable # TODO: Add more variety!
+  id: LetterRareEntityTable # Interesting things that can actually be of use, should never be more expensive than 500 spesos
+  table: !type:GroupSelector
+    children:
+    - id: ResearchDisk5000
+    - id: ClothingHeadHatHairflower
+    - id: ClothingHeadHatFlowerWreath
+    - id: JointRainbow
+
+# Packages
+# TODO: Currently mostly maints loot, should be updated in the future.
+- type: entityTable # TODO: Add more variety!
+  id: PackageCommonEntityTable
+  table: !type:GroupSelector
+    children:
+    - !type:NestedSelector
+      tableId: MaintToolsTable
+      rolls: !type:RangeNumberSelector
+        range: 1, 2
+    - !type:NestedSelector
+      tableId: FoodRandomCakeTable
+      weight: 2
+    - id: FoodBoxPizzaFilled
+
+- type: entityTable
+  id: PackageUncommonEntityTable
+  table: !type:GroupSelector
+    children:
+    # Fluff
+    - !type:NestedSelector
+      tableId: MaintFluffTable
+      weight: 0.4
+      rolls: !type:RangeNumberSelector
+        range: 1, 2
+    # Plushies
+    - !type:NestedSelector
+      tableId: AllPlushiesTable
+      weight: 0.4
+    # Weapons
+    - !type:NestedSelector
+      tableId: MaintWeaponTable
+      weight: 0.1
+    # Syndie Loot
+    - !type:NestedSelector
+      tableId: SyndieMaintLoot
+      weight: 0.1
+
+- type: entityTable # TODO: Add more variety!
+  id: PackageRareEntityTable
+  table: !type:GroupSelector
+    children:
+    - id: MedkitAdvancedFilled
+    - id: MobMothroach
+    - id: PipeBomb
index fde151f20e08d6b6530099aa484d7c63749ba443..3265e2fd32dc3ced3c2b8fbf4695949bc6ce304d 100644 (file)
         Capacitor: 2
         Steel: 5
 
+- type: entity
+  id: MailTeleporterMachineCircuitboard
+  parent: BaseMachineCircuitboard
+  name: mail teleporter machine board
+  description: A machine printed circuit board for a mail teleporter.
+  components:
+  - type: Sprite
+    state: supply
+  - type: MachineBoard
+    prototype: CargoMailTeleporter
+    stackRequirements:
+      Capacitor: 2
+      Steel: 5
+
 - type: entity
   id: SodaDispenserMachineCircuitboard
   parent: BaseMachineCircuitboard
index e026d50d29af6040f33eaea8b7f160f0b22a4fa3..5490dcc5745b540d519962a61840b1af7ba6bcdc 100644 (file)
   components:
     - type: StationNews
 
+- type: entity
+  id: BaseStationDeliveries
+  abstract: true
+  components:
+  - type: CargoDeliveryData
+
 - type: entity
   id: BaseStationAllEventsEligible
   abstract: true
index 613bb1b1f4f16518911d8cd2d03340dc965ac492..5fb40f272a2b4cbfd91dde253cff15f1145470db 100644 (file)
@@ -25,6 +25,7 @@
     - BaseStationSiliconLawCrewsimov
     - BaseStationAllEventsEligible
     - BaseStationNanotrasen
+    - BaseStationDeliveries
   categories: [ HideSpawnMenu ]
   components:
     - type: Transform
diff --git a/Resources/Prototypes/Entities/Structures/Machines/mail_teleporter.yml b/Resources/Prototypes/Entities/Structures/Machines/mail_teleporter.yml
new file mode 100644 (file)
index 0000000..4356921
--- /dev/null
@@ -0,0 +1,44 @@
+- type: entity
+  id: CargoMailTeleporter
+  parent: [ BaseMachinePowered, ConstructibleMachine ]
+  name: mail teleporter
+  description: Periodically teleports in mail to deliver across the station.
+  components:
+  - type: Fixtures
+    fixtures:
+      fix1:
+        shape:
+          !type:PhysShapeAabb
+          bounds: "-0.35,-0.35,0.35,0.35"
+        density: 190
+        mask:
+        - MachineMask
+        layer:
+        - MachineLayer
+  - type: Sprite
+    sprite: Structures/mailbox.rsi
+    snapCardinals: true
+    layers:
+    - state: icon
+    - state: unlit
+      shader: unshaded
+      visible: false
+      map: [ "enum.PowerDeviceVisualLayers.Powered" ]
+  - type: Damageable
+    damageContainer: StructuralInorganic
+    damageModifierSet: StructuralMetallicStrong
+  - type: ApcPowerReceiver
+    powerLoad: 1000
+  - type: Appearance
+  - type: GenericVisualizer
+    visuals:
+      enum.PowerDeviceVisuals.Powered:
+        enum.PowerDeviceVisualLayers.Powered:
+          True: { visible: true }
+          False: { visible: false }
+  - type: Machine
+    board: MailTeleporterMachineCircuitboard
+  - type: PowerSwitch
+  - type: DeliverySpawner
+    table: !type:NestedSelector
+      tableId: RandomDeliveryBasic
diff --git a/Resources/Prototypes/SoundCollections/deliveries.yml b/Resources/Prototypes/SoundCollections/deliveries.yml
new file mode 100644 (file)
index 0000000..79803c6
--- /dev/null
@@ -0,0 +1,14 @@
+- type: soundCollection
+  id: DeliveryOpenSounds
+  files:
+  - /Audio/Effects/unwrap.ogg
+
+- type: soundCollection
+  id: DeliveryUnlockSounds
+  files:
+  - /Audio/Effects/Cargo/ping.ogg
+
+- type: soundCollection
+  id: DeliverySpawnSounds
+  files:
+  - /Audio/Effects/Lightning/lightningbolt.ogg
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/broken.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/broken.png
new file mode 100644 (file)
index 0000000..afce598
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/broken.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/fragile.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/fragile.png
new file mode 100644 (file)
index 0000000..a17f4b5
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/fragile.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/icon.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/icon.png
new file mode 100644 (file)
index 0000000..bcfe67c
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/locked.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/locked.png
new file mode 100644 (file)
index 0000000..8292644
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/locked.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json
new file mode 100644 (file)
index 0000000..9d5374b
--- /dev/null
@@ -0,0 +1,35 @@
+{
+    "version": 1,
+    "license": "CC-BY-SA-3.0",
+    "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a",
+    "size": {
+        "x": 32,
+        "y": 32
+    },
+    "states": [
+        {
+            "name": "icon"
+        },
+        {
+            "name": "locked"
+        },
+        {
+            "name": "trash"
+        },
+        {
+            "name": "fragile"
+        },
+        {
+            "name": "priority"
+        },
+        {
+            "name": "priority_inactive"
+        },
+        {
+            "name": "broken"
+        },
+        {
+            "name": "postmark"
+        }
+    ]
+}
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/postmark.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/postmark.png
new file mode 100644 (file)
index 0000000..5d55ab0
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/postmark.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority.png
new file mode 100644 (file)
index 0000000..f2c0106
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority_inactive.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority_inactive.png
new file mode 100644 (file)
index 0000000..034991a
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/priority_inactive.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/trash.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/trash.png
new file mode 100644 (file)
index 0000000..1faadff
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/trash.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/broken.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/broken.png
new file mode 100644 (file)
index 0000000..1c798c4
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/broken.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/fragile.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/fragile.png
new file mode 100644 (file)
index 0000000..0917000
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/fragile.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/icon.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/icon.png
new file mode 100644 (file)
index 0000000..f3974ab
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-left.png
new file mode 100644 (file)
index 0000000..ccbc87c
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-right.png
new file mode 100644 (file)
index 0000000..ccbc87c
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/locked.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/locked.png
new file mode 100644 (file)
index 0000000..1cacaf1
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/locked.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json
new file mode 100644 (file)
index 0000000..cf7609b
--- /dev/null
@@ -0,0 +1,43 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Taken from tgstation (obj/storage/closet.dmi, obj/service/bureaucracy.dmi), modified by Whatstone (Discord).  broken, inhand-left, inhand-right by Whatstone.",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "broken"
+    },
+    {
+      "name": "fragile"
+    },
+    {
+      "name": "icon"
+    },
+    {
+      "name": "inhand-left",
+      "directions": 4
+    },
+    {
+      "name": "inhand-right",
+      "directions": 4
+    },
+    {
+      "name": "locked"
+    },
+    {
+      "name": "priority"
+    },
+    {
+      "name": "priority_inactive"
+    },
+    {
+      "name": "trash"
+    },
+    {
+      "name": "postmark"
+    }
+  ]
+}
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/postmark.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/postmark.png
new file mode 100644 (file)
index 0000000..3267a6e
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/postmark.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority.png
new file mode 100644 (file)
index 0000000..9c5a74a
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority_inactive.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority_inactive.png
new file mode 100644 (file)
index 0000000..fc03165
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/priority_inactive.png differ
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/trash.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/trash.png
new file mode 100644 (file)
index 0000000..2ef4ee7
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/trash.png differ
diff --git a/Resources/Textures/Structures/mailbox.rsi/icon.png b/Resources/Textures/Structures/mailbox.rsi/icon.png
new file mode 100644 (file)
index 0000000..d2ac9ec
Binary files /dev/null and b/Resources/Textures/Structures/mailbox.rsi/icon.png differ
diff --git a/Resources/Textures/Structures/mailbox.rsi/meta.json b/Resources/Textures/Structures/mailbox.rsi/meta.json
new file mode 100644 (file)
index 0000000..ce3494c
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Created by meijunkie on Discord, funkystation.org at commit https://github.com/funky-station/funky-station/commit/430be9247ac220367f338c2c1a716a7503b1446d",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "icon"
+    },
+    {
+      "name": "unlit"
+    }
+  ]
+}
diff --git a/Resources/Textures/Structures/mailbox.rsi/unlit.png b/Resources/Textures/Structures/mailbox.rsi/unlit.png
new file mode 100644 (file)
index 0000000..31ca062
Binary files /dev/null and b/Resources/Textures/Structures/mailbox.rsi/unlit.png differ