Cargo Mail System (#35429)

* 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>
This commit is contained in:
ScarKy0
2025-03-07 14:51:08 +01:00
committed by GitHub
parent 5c12c1bf08
commit 3281f408eb
51 changed files with 1438 additions and 9 deletions

View File

@@ -0,0 +1,5 @@
using Content.Shared.Delivery;
namespace Content.Client.Delivery;
public sealed class DeliverySystem : SharedDeliverySystem;

View File

@@ -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,
}

View File

@@ -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;
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View 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;

View 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;

View File

@@ -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";
}

View File

@@ -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));
}

View File

@@ -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,
}

View File

@@ -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);

View 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;
}

View 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.

View 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;
}

View 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;

View 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;

View File

@@ -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

View File

@@ -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]"}

View File

@@ -18,6 +18,7 @@
- id: RubberStampDenied
- id: RubberStampQm
- id: AstroNavCartridge
- id: MailTeleporterMachineCircuitboard
- type: entity
id: LockerQuarterMasterFilled

View File

@@ -861,3 +861,27 @@
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1192,6 +1192,20 @@
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

View File

@@ -128,6 +128,12 @@
components:
- type: StationNews
- type: entity
id: BaseStationDeliveries
abstract: true
components:
- type: CargoDeliveryData
- type: entity
id: BaseStationAllEventsEligible
abstract: true

View File

@@ -25,6 +25,7 @@
- BaseStationSiliconLawCrewsimov
- BaseStationAllEventsEligible
- BaseStationNanotrasen
- BaseStationDeliveries
categories: [ HideSpawnMenu ]
components:
- type: Transform

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

View File

@@ -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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

View File

@@ -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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B