diff --git a/Content.Client/CartridgeLoader/Cartridges/MailMetricUi.cs b/Content.Client/CartridgeLoader/Cartridges/MailMetricUi.cs new file mode 100644 index 00000000000..504dda7c56e --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/MailMetricUi.cs @@ -0,0 +1,28 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.UserInterface; + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed partial class MailMetricUi : UIFragment +{ + private MailMetricUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new MailMetricUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is MailMetricUiState cast) + { + _fragment?.UpdateState(cast); + } + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/MailMetricUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/MailMetricUiFragment.xaml new file mode 100644 index 00000000000..39639fe8c97 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/MailMetricUiFragment.xaml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/MailMetricUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/MailMetricUiFragment.xaml.cs new file mode 100644 index 00000000000..553e3a57931 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/MailMetricUiFragment.xaml.cs @@ -0,0 +1,104 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class MailMetricUiFragment : BoxContainer +{ + + private OpenedMailPercentGrade? _successGrade; + + public MailMetricUiFragment() + { + RobustXamlLoader.Load(this); + + // This my way of adding multiple classes to a XAML control. + // Haha Batman I'm going to blow up Gotham City + OpenedMailCount.StyleClasses.Add("Good"); + OpenedMailSpesos.StyleClasses.Add("Good"); + TamperedMailCount.StyleClasses.Add("Danger"); + TamperedMailSpesos.StyleClasses.Add("Danger"); + ExpiredMailCount.StyleClasses.Add("Danger"); + ExpiredMailSpesos.StyleClasses.Add("Danger"); + DamagedMailCount.StyleClasses.Add("Danger"); + DamagedMailSpesos.StyleClasses.Add("Danger"); + UnopenedMailCount.StyleClasses.Add("Caution"); + } + + public void UpdateState(MailMetricUiState state) + { + UpdateTextLabels(state); + UpdateSuccessGrade(state); + } + + public void UpdateTextLabels(MailMetricUiState state) + { + var stats = state.Metrics; + + OpenedMailCount.Text = stats.OpenedCount.ToString(); + OpenedMailSpesos.Text = stats.Earnings.ToString(); + TamperedMailCount.Text = stats.TamperedCount.ToString(); + TamperedMailSpesos.Text = stats.TamperedLosses.ToString(); + ExpiredMailCount.Text = stats.ExpiredCount.ToString(); + ExpiredMailSpesos.Text = stats.ExpiredLosses.ToString(); + DamagedMailCount.Text = stats.DamagedCount.ToString(); + DamagedMailSpesos.Text = stats.DamagedLosses.ToString(); + UnopenedMailCount.Text = state.UnopenedMailCount.ToString(); + TotalMailCount.Text = state.TotalMail.ToString(); + TotalMailSpesos.Text = stats.TotalIncome.ToString(); + SuccessRateCounts.Text = Loc.GetString("mail-metrics-progress", + ("opened", stats.OpenedCount), + ("total", state.TotalMail)); + SuccessRatePercent.Text = Loc.GetString("mail-metrics-progress-percent", + ("successRate", state.SuccessRate)); + } + + public void UpdateSuccessGrade(MailMetricUiState state) + { + var previousGrade = _successGrade; + _successGrade = GetSuccessRateGrade(state.SuccessRate); + + // No need to update if they're the same + if (previousGrade == _successGrade) + return; + + var previousGradeClass = GetClassForGrade(previousGrade); + if (previousGradeClass != string.Empty) + { + SuccessRatePercent.StyleClasses.Remove(previousGradeClass); + } + + SuccessRatePercent.StyleClasses.Add(GetClassForGrade(_successGrade)); + } + + private static OpenedMailPercentGrade GetSuccessRateGrade(double successRate) + { + return successRate switch + { + > 75 => OpenedMailPercentGrade.Good, + > 50 => OpenedMailPercentGrade.Average, + _ => OpenedMailPercentGrade.Bad, + }; + } + + private string GetClassForGrade(OpenedMailPercentGrade? grade) + { + return grade switch + { + OpenedMailPercentGrade.Good => "Good", + OpenedMailPercentGrade.Average => "Caution", + OpenedMailPercentGrade.Bad => "Danger", + _ => string.Empty, + }; + } +} + +enum OpenedMailPercentGrade +{ + Good, + Average, + Bad +} diff --git a/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs b/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs new file mode 100644 index 00000000000..ca1fbeaad08 --- /dev/null +++ b/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Cargo; +using Content.Shared.CartridgeLoader.Cartridges; + +namespace Content.Server.Cargo.Components; + +/// +/// Added to the abstract representation of a station to track stats related to mail delivery and income +/// +[RegisterComponent, Access(typeof(SharedCargoSystem))] +public sealed partial class StationLogisticStatsComponent : Component +{ + [DataField] + public MailStats Metrics { get; set; } +} diff --git a/Content.Server/Cargo/Systems/LogisticStatsSystem.cs b/Content.Server/Cargo/Systems/LogisticStatsSystem.cs new file mode 100644 index 00000000000..6abf4eb5a47 --- /dev/null +++ b/Content.Server/Cargo/Systems/LogisticStatsSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.Cargo; +using Content.Server.Cargo.Components; +using JetBrains.Annotations; + +namespace Content.Server.Cargo.Systems; + +public sealed partial class LogisticStatsSystem : SharedCargoSystem +{ + [PublicAPI] + public void AddOpenedMailEarnings(EntityUid uid, StationLogisticStatsComponent component, int earnedMoney) + { + component.Metrics = component.Metrics with + { + Earnings = component.Metrics.Earnings + earnedMoney, + OpenedCount = component.Metrics.OpenedCount + 1 + }; + UpdateLogisticsStats(uid); + } + + [PublicAPI] + public void AddExpiredMailLosses(EntityUid uid, StationLogisticStatsComponent component, int lostMoney) + { + component.Metrics = component.Metrics with + { + ExpiredLosses = component.Metrics.ExpiredLosses + lostMoney, + ExpiredCount = component.Metrics.ExpiredCount + 1 + }; + UpdateLogisticsStats(uid); + } + + [PublicAPI] + public void AddDamagedMailLosses(EntityUid uid, StationLogisticStatsComponent component, int lostMoney) + { + component.Metrics = component.Metrics with + { + DamagedLosses = component.Metrics.DamagedLosses + lostMoney, + DamagedCount = component.Metrics.DamagedCount + 1 + }; + UpdateLogisticsStats(uid); + } + + [PublicAPI] + public void AddTamperedMailLosses(EntityUid uid, StationLogisticStatsComponent component, int lostMoney) + { + component.Metrics = component.Metrics with + { + TamperedLosses = component.Metrics.TamperedLosses + lostMoney, + TamperedCount = component.Metrics.TamperedCount + 1 + }; + UpdateLogisticsStats(uid); + } + + private void UpdateLogisticsStats(EntityUid uid) => RaiseLocalEvent(new LogisticStatsUpdatedEvent(uid)); +} + +public sealed class LogisticStatsUpdatedEvent : EntityEventArgs +{ + public EntityUid Station; + public LogisticStatsUpdatedEvent(EntityUid station) + { + Station = station; + } +} diff --git a/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeComponent.cs new file mode 100644 index 00000000000..380a4d90c04 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent, Access(typeof(MailMetricsCartridgeSystem))] +public sealed partial class MailMetricsCartridgeComponent : Component +{ + /// + /// Station entity keeping track of logistics stats + /// + [DataField] + public EntityUid? Station; +} diff --git a/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeSystem.cs new file mode 100644 index 00000000000..00b6d0a16e8 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeSystem.cs @@ -0,0 +1,82 @@ +using Content.Server.Cargo.Components; +using Content.Server.Cargo.Systems; +using Content.Server.Mail.Components; +using Content.Server.Station.Systems; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; + +namespace Content.Server.CartridgeLoader.Cartridges; + +public sealed class MailMetricsCartridgeSystem : EntitySystem +{ + [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!; + [Dependency] private readonly StationSystem _station = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUiReady); + SubscribeLocalEvent(OnLogisticsStatsUpdated); + SubscribeLocalEvent(OnMapInit); + } + + private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args) + { + UpdateUI(ent, args.Loader); + } + + private void OnLogisticsStatsUpdated(LogisticStatsUpdatedEvent args) + { + UpdateAllCartridges(args.Station); + } + + private void OnMapInit(EntityUid uid, MailComponent mail, MapInitEvent args) + { + if (_station.GetOwningStation(uid) is { } station) + UpdateAllCartridges(station); + } + + private void UpdateAllCartridges(EntityUid station) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var cartridge)) + { + if (cartridge.LoaderUid is not { } loader || comp.Station != station) + continue; + UpdateUI((uid, comp), loader); + } + } + + private void UpdateUI(Entity ent, EntityUid loader) + { + if (_station.GetOwningStation(loader) is { } station) + ent.Comp.Station = station; + + if (!TryComp(ent.Comp.Station, out var logiStats)) + return; + + // Get station's logistic stats + var unopenedMailCount = GetUnopenedMailCount(ent.Comp.Station); + + // Send logistic stats to cartridge client + var state = new MailMetricUiState(logiStats.Metrics, unopenedMailCount); + _cartridgeLoader.UpdateCartridgeUiState(loader, state); + } + + + private int GetUnopenedMailCount(EntityUid? station) + { + var unopenedMail = 0; + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.IsLocked && _station.GetOwningStation(uid) == station) + unopenedMail++; + } + + return unopenedMail; + } +} diff --git a/Content.Server/Mail/Components/DelayedItemComponent.cs b/Content.Server/Mail/Components/DelayedItemComponent.cs new file mode 100644 index 00000000000..f73aef85b06 --- /dev/null +++ b/Content.Server/Mail/Components/DelayedItemComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Server.Mail.Components; + +/// +/// A placeholder for another entity, spawned when dropped or placed in someone's hands. +/// Useful for storing instant effect entities, e.g. smoke, in the mail. +/// +[RegisterComponent] +public sealed partial class DelayedItemComponent : Component +{ + /// + /// The entity to replace this when opened or dropped. + /// + [DataField] + public string Item = "None"; +} diff --git a/Content.Server/Mail/Components/MailComponent.cs b/Content.Server/Mail/Components/MailComponent.cs new file mode 100644 index 00000000000..af0bdaa87ff --- /dev/null +++ b/Content.Server/Mail/Components/MailComponent.cs @@ -0,0 +1,111 @@ +using System.Threading; +using Robust.Shared.Audio; +using Content.Shared.Storage; +using Content.Shared.Mail; + +namespace Content.Server.Mail.Components; + +[RegisterComponent] +public sealed partial class MailComponent : SharedMailComponent +{ + [DataField] + public string Recipient = "None"; + + [DataField] + public string RecipientJob = "None"; + + /// + /// Why do we not use LockComponent? + /// Because this can't be locked again, + /// and we have special conditions for unlocking, + /// and we don't want to add a verb. + /// + [DataField] + public bool IsLocked = true; + + /// + /// Is this parcel profitable to deliver for the station? + /// + /// + /// The station won't receive any award on delivery if this is false. + /// This is useful for broken fragile packages and packages that were + /// not delivered in time. + /// + [DataField] + public bool IsProfitable = true; + + /// + /// Is this package considered fragile? + /// + /// + /// This can be set to true in the YAML files for a mail delivery to + /// always be Fragile, despite its contents. + /// + [DataField] + public bool IsFragile = false; + + /// + /// Is this package considered priority mail? + /// + /// + /// There will be a timer set for its successful delivery. The + /// station's bank account will be penalized if it is not delivered on + /// time.
+ ///
+ /// This is set to false on successful delivery.
+ ///
+ /// This can be set to true in the YAML files for a mail delivery to always be Priority. + ///
+ [DataField] + public bool IsPriority = false; + + /// + /// Whether this parcel is large. + /// + [DataField] + public bool IsLarge = false; + + /// + /// What will be packaged when the mail is spawned. + /// + [DataField] + public List Contents = new(); + + /// + /// The amount that cargo will be awarded for delivering this mail. + /// + [DataField] + public int Bounty = 750; + + /// + /// Penalty if the mail is destroyed. + /// + [DataField] + public int Penalty = -250; + + /// + /// The sound that's played when the mail's lock is broken. + /// + [DataField] + public SoundSpecifier PenaltySound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg"); + + /// + /// The sound that's played when the mail's opened. + /// + [DataField] + public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/packetrip.ogg"); + + /// + /// The sound that's played when the mail's lock has been emagged. + /// + [DataField] + public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks"); + + /// + /// Whether this component is enabled. + /// Removed when it becomes trash. + /// + public bool IsEnabled = true; + + public CancellationTokenSource? PriorityCancelToken; +} diff --git a/Content.Server/Mail/Components/MailReceiverComponent.cs b/Content.Server/Mail/Components/MailReceiverComponent.cs new file mode 100644 index 00000000000..281a27ea79e --- /dev/null +++ b/Content.Server/Mail/Components/MailReceiverComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Server.Mail.Components; + +[RegisterComponent] +public sealed partial class MailReceiverComponent : Component {} diff --git a/Content.Server/Mail/Components/MailTeleporterComponent.cs b/Content.Server/Mail/Components/MailTeleporterComponent.cs new file mode 100644 index 00000000000..81f58bc7a59 --- /dev/null +++ b/Content.Server/Mail/Components/MailTeleporterComponent.cs @@ -0,0 +1,118 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Mail.Components; + +/// +/// This is for the mail teleporter. +/// Random mail will be teleported to this every few minutes. +/// +[RegisterComponent] +public sealed partial class MailTeleporterComponent : Component +{ + // Not starting accumulator at 0 so mail carriers have some deliveries to make shortly after roundstart. + [DataField] + public float Accumulator = 285f; + + [DataField] + public TimeSpan TeleportInterval = TimeSpan.FromMinutes(5); + + /// + /// The sound that's played when new mail arrives. + /// + [DataField] + public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); + + /// + /// The MailDeliveryPoolPrototype that's used to select what mail this + /// teleporter can deliver. + /// + [DataField] + public string MailPool = "RandomDeltaVMailDeliveryPool"; + + /// + /// How many mail candidates do we need per actual delivery sent when + /// the mail goes out? The number of candidates is divided by this number + /// to determine how many deliveries will be teleported in. + /// It does not determine unique recipients. That is random. + /// + [DataField] + public int CandidatesPerDelivery = 8; + + [DataField] + public int MinimumDeliveriesPerTeleport = 1; + + /// + /// Do not teleport any more mail in, if there are at least this many + /// undelivered parcels. + /// + /// + /// Currently this works by checking how many MailComponent entities + /// are sitting on the teleporter's tile.

+ /// + /// It should be noted that if the number of actual deliveries to be + /// made based on the number of candidates divided by candidates per + /// delivery exceeds this number, the teleporter will spawn more mail + /// than this number.

+ /// + /// This is just a simple check to see if anyone's been picking up the + /// mail lately to prevent entity bloat for the sake of performance. + ///
+ [DataField] + public int MaximumUndeliveredParcels = 5; + + /// + /// Any item that breaks or is destroyed in less than this amount of + /// damage is one of the types of items considered fragile. + /// + [DataField] + public int FragileDamageThreshold = 10; + + /// + /// What's the bonus for delivering a fragile package intact? + /// + [DataField] + public int FragileBonus = 100; + + /// + /// What's the malus for failing to deliver a fragile package? + /// + [DataField] + public int FragileMalus = -100; + + /// + /// What's the chance for any one delivery to be marked as priority mail? + /// + [DataField] + public float PriorityChance = 0.1f; + + /// + /// How long until a priority delivery is considered as having failed + /// if not delivered? + /// + [DataField] + public TimeSpan PriorityDuration = TimeSpan.FromMinutes(5); + + /// + /// What's the bonus for delivering a priority package on time? + /// + [DataField] + public int PriorityBonus = 250; + + /// + /// What's the malus for failing to deliver a priority package? + /// + [DataField] + public int PriorityMalus = -250; + + /// + /// What's the bonus for delivering a large package intact? + /// + [DataField] + public int LargeBonus = 1500; + + /// + /// What's the malus for failing to deliver a large package? + /// + [DataField] + public int LargeMalus = -500; +} diff --git a/Content.Server/Nyanotrasen/Mail/Components/StationMailRouterComponent.cs b/Content.Server/Mail/Components/StationMailRouterComponent.cs similarity index 51% rename from Content.Server/Nyanotrasen/Mail/Components/StationMailRouterComponent.cs rename to Content.Server/Mail/Components/StationMailRouterComponent.cs index ce87eb131fc..6f6df1e10bb 100644 --- a/Content.Server/Nyanotrasen/Mail/Components/StationMailRouterComponent.cs +++ b/Content.Server/Mail/Components/StationMailRouterComponent.cs @@ -1,7 +1,7 @@ -namespace Content.Server.Mail; +namespace Content.Server.Mail.Components; /// -/// Designates a station as a place for sending and receiving mail. +/// Designates a station as a place for sending and receiving mail. /// [RegisterComponent] public sealed partial class StationMailRouterComponent : Component diff --git a/Content.Server/Nyanotrasen/Mail/MailCommands.cs b/Content.Server/Mail/MailCommands.cs similarity index 69% rename from Content.Server/Nyanotrasen/Mail/MailCommands.cs rename to Content.Server/Mail/MailCommands.cs index 5af873f9e80..390de93fb3a 100644 --- a/Content.Server/Nyanotrasen/Mail/MailCommands.cs +++ b/Content.Server/Mail/MailCommands.cs @@ -5,6 +5,7 @@ using Content.Shared.Administration; using Content.Server.Administration; using Content.Server.Mail.Components; +using Content.Server.Mail.Systems; namespace Content.Server.Mail; @@ -20,6 +21,7 @@ public sealed class MailToCommand : IConsoleCommand [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; private readonly string _blankMailPrototype = "MailAdminFun"; + private readonly string _blankLargeMailPrototype = "MailLargeAdminFun"; // Frontier: large mail private readonly string _container = "storagebase"; private readonly string _mailContainer = "contents"; @@ -44,21 +46,23 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) return; } - if (!Boolean.TryParse(args[2], out var isFragile)) + if (!bool.TryParse(args[2], out var isFragile) || !bool.TryParse(args[3], out var isPriority)) { shell.WriteError(Loc.GetString("shell-invalid-bool")); return; } - if (!Boolean.TryParse(args[3], out var isPriority)) + var isLarge = false; + if (args.Length > 4 && !bool.TryParse(args[4], out isLarge)) { shell.WriteError(Loc.GetString("shell-invalid-bool")); return; } + var mailPrototype = isLarge ? _blankLargeMailPrototype : _blankMailPrototype; - var _mailSystem = _entitySystemManager.GetEntitySystem(); - var _containerSystem = _entitySystemManager.GetEntitySystem(); + var mailSystem = _entitySystemManager.GetEntitySystem(); + var containerSystem = _entitySystemManager.GetEntitySystem(); if (!_entityManager.TryGetComponent(recipientUid, out MailReceiverComponent? mailReceiver)) { @@ -66,49 +70,50 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) return; } - if (!_prototypeManager.HasIndex(_blankMailPrototype)) + if (!_prototypeManager.HasIndex(mailPrototype)) { - shell.WriteLine(Loc.GetString("command-mailto-no-blankmail", ("blankMail", _blankMailPrototype))); + shell.WriteLine(Loc.GetString("command-mailto-no-blankmail", ("blankMail", mailPrototype))); return; } - if (!_containerSystem.TryGetContainer(containerUid, _container, out var targetContainer)) + if (!containerSystem.TryGetContainer(containerUid, _container, out var targetContainer)) { shell.WriteLine(Loc.GetString("command-mailto-invalid-container", ("requiredContainer", _container))); return; } - if (!_mailSystem.TryGetMailRecipientForReceiver(mailReceiver, out MailRecipient? recipient)) + if (!mailSystem.TryGetMailRecipientForReceiver(mailReceiver, out MailRecipient? recipient)) { shell.WriteLine(Loc.GetString("command-mailto-unable-to-receive")); return; } - if (!_mailSystem.TryGetMailTeleporterForReceiver(mailReceiver, out MailTeleporterComponent? teleporterComponent)) + if (!mailSystem.TryGetMailTeleporterForReceiver(mailReceiver, out MailTeleporterComponent? teleporterComponent)) { shell.WriteLine(Loc.GetString("command-mailto-no-teleporter-found")); return; } - var mailUid = _entityManager.SpawnEntity(_blankMailPrototype, _entityManager.GetComponent(containerUid).Coordinates); - var mailContents = _containerSystem.EnsureContainer(mailUid, _mailContainer); + var mailUid = _entityManager.SpawnEntity(mailPrototype, _entityManager.GetComponent(containerUid).Coordinates); + var mailContents = containerSystem.EnsureContainer(mailUid, _mailContainer); if (!_entityManager.TryGetComponent(mailUid, out MailComponent? mailComponent)) { - shell.WriteLine(Loc.GetString("command-mailto-bogus-mail", ("blankMail", _blankMailPrototype), ("requiredMailComponent", nameof(MailComponent)))); + shell.WriteLine(Loc.GetString("command-mailto-bogus-mail", ("blankMail", mailPrototype), ("requiredMailComponent", nameof(MailComponent)))); return; } foreach (var entity in targetContainer.ContainedEntities.ToArray()) - _containerSystem.Insert(entity, mailContents); + containerSystem.Insert(entity, mailContents); mailComponent.IsFragile = isFragile; mailComponent.IsPriority = isPriority; + mailComponent.IsLarge = isLarge; - _mailSystem.SetupMail(mailUid, teleporterComponent, recipient.Value); + mailSystem.SetupMail(mailUid, teleporterComponent, recipient.Value); - var teleporterQueue = _containerSystem.EnsureContainer(teleporterComponent.Owner, "queued"); - _containerSystem.Insert(mailUid, teleporterQueue); + var teleporterQueue = containerSystem.EnsureContainer(teleporterComponent.Owner, "queued"); + containerSystem.Insert(mailUid, teleporterQueue); shell.WriteLine(Loc.GetString("command-mailto-success", ("timeToTeleport", teleporterComponent.TeleportInterval.TotalSeconds - teleporterComponent.Accumulator))); } } @@ -125,7 +130,7 @@ public sealed class MailNowCommand : IConsoleCommand public async void Execute(IConsoleShell shell, string argStr, string[] args) { - var _mailSystem = _entitySystemManager.GetEntitySystem(); + var entitySystem = _entitySystemManager.GetEntitySystem(); foreach (var mailTeleporter in _entityManager.EntityQuery()) { diff --git a/Content.Server/Mail/MailConstants.cs b/Content.Server/Mail/MailConstants.cs new file mode 100644 index 00000000000..38d37a13269 --- /dev/null +++ b/Content.Server/Mail/MailConstants.cs @@ -0,0 +1,37 @@ +namespace Content.Server.Mail; + +/// +/// A set of localized strings related to mail entities +/// +public struct MailEntityStrings +{ + public string NameAddressed; + public string DescClose; + public string DescFar; +} + +/// +/// Constants related to mail. +/// +public static class MailConstants +{ + /// + /// Locale strings related to small parcels. + /// + public static readonly MailEntityStrings Mail = new() + { + NameAddressed = "mail-item-name-addressed", + DescClose = "mail-desc-close", + DescFar = "mail-desc-far" + }; + + /// + /// Locale strings related to large packages. + /// + public static readonly MailEntityStrings MailLarge = new() + { + NameAddressed = "mail-large-item-name-addressed", + DescClose = "mail-large-desc-close", + DescFar = "mail-large-desc-far" + }; +} diff --git a/Content.Server/Mail/Systems/DelayedItemSystem.cs b/Content.Server/Mail/Systems/DelayedItemSystem.cs new file mode 100644 index 00000000000..59aaa3aff6f --- /dev/null +++ b/Content.Server/Mail/Systems/DelayedItemSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared.Damage; +using Content.Shared.Hands; +using Robust.Shared.Containers; + +namespace Content.Server.Mail.Systems; + +/// +/// A placeholder for another entity, spawned when taken out of a container, with the placeholder deleted shortly after. +/// Useful for storing instant effect entities, e.g. smoke, in the mail. +/// +public sealed class DelayedItemSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDropAttempt); + SubscribeLocalEvent(OnHandEquipped); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnRemovedFromContainer); + } + + private void OnRemovedFromContainer(EntityUid uid, Components.DelayedItemComponent component, ContainerModifiedMessage args) + { + Spawn(component.Item, Transform(uid).Coordinates); + } + + private void OnHandEquipped(EntityUid uid, Components.DelayedItemComponent component, EquippedHandEvent args) + { + EntityManager.DeleteEntity(uid); + } + + private void OnDropAttempt(EntityUid uid, Components.DelayedItemComponent component, DropAttemptEvent args) + { + EntityManager.DeleteEntity(uid); + } + + private void OnDamageChanged(EntityUid uid, Components.DelayedItemComponent component, DamageChangedEvent args) + { + Spawn(component.Item, Transform(uid).Coordinates); + EntityManager.DeleteEntity(uid); + } +} diff --git a/Content.Server/Mail/Systems/MailSystem.cs b/Content.Server/Mail/Systems/MailSystem.cs new file mode 100644 index 00000000000..e80febd2308 --- /dev/null +++ b/Content.Server/Mail/Systems/MailSystem.cs @@ -0,0 +1,756 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using Content.Server.Access.Systems; +using Content.Server.Cargo.Components; +using Content.Server.Cargo.Systems; +using Content.Server.Chat.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Damage.Components; +using Content.Server.Destructible; +using Content.Server.Destructible.Thresholds; +using Content.Server.Destructible.Thresholds.Behaviors; +using Content.Server.Destructible.Thresholds.Triggers; +using Content.Server.Item; +using Content.Server.Mail.Components; +using Content.Server.Mind; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.Spawners.EntitySystems; +using Content.Server.Station.Systems; +using Content.Shared.Access; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.Chat; +using Content.Shared.Damage; +using Content.Shared.Destructible; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Examine; +using Content.Shared.Fluids.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Mail; +using Content.Shared.Maps; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.PDA; +using Content.Shared.Roles; +using Content.Shared.Storage; +using Content.Shared.Tag; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Timer = Robust.Shared.Timing.Timer; + +namespace Content.Server.Mail.Systems; + +public sealed class MailSystem : EntitySystem +{ + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly CargoSystem _cargoSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + + // DeltaV - system that keeps track of mail and cargo stats + [Dependency] private readonly LogisticStatsSystem _logisticsStatsSystem = default!; + + private ISawmill _sawmill = default!; + + public override void Initialize() + { + base.Initialize(); + + _sawmill = Logger.GetSawmill("mail"); + + SubscribeLocalEvent(OnSpawnPlayer, after: new[] { typeof(SpawnPointSystem) }); + + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnAfterInteractUsing); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnDestruction); + SubscribeLocalEvent(OnDamage); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent(OnMailEmagged); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + foreach (var mailTeleporter in EntityQuery()) + { + if (TryComp(mailTeleporter.Owner, out var power) && !power.Powered) + continue; + + mailTeleporter.Accumulator += frameTime; + if (mailTeleporter.Accumulator < mailTeleporter.TeleportInterval.TotalSeconds) + continue; + + mailTeleporter.Accumulator -= (float) mailTeleporter.TeleportInterval.TotalSeconds; + + SpawnMail(mailTeleporter.Owner, mailTeleporter); + } + } + + /// + /// Dynamically add the MailReceiver component to appropriate entities. + /// + private void OnSpawnPlayer(PlayerSpawningEvent args) + { + if (args.SpawnResult == null + || args.Job == null + || args.Station is not {} station + || !HasComp(station)) + return; + + AddComp(args.SpawnResult.Value); + } + + private void OnRemove(EntityUid uid, MailComponent component, ComponentRemove args) + { + // Make sure the priority timer doesn't run. + if (component.PriorityCancelToken != null) + component.PriorityCancelToken.Cancel(); + } + + /// + /// Try to open the mail. + /// + private void OnUseInHand(EntityUid uid, MailComponent component, UseInHandEvent args) + { + if (!component.IsEnabled) + return; + if (component.IsLocked) + { + _popupSystem.PopupEntity(Loc.GetString("mail-locked"), uid, args.User); + return; + } + OpenMail(uid, component, args.User); + } + + /// + /// Handle logic similar between a normal mail unlock and an emag + /// frying out the lock. + /// + private void UnlockMail(EntityUid uid, MailComponent component) + { + component.IsLocked = false; + UpdateAntiTamperVisuals(uid, false); + + if (component.IsPriority) + { + // This is a successful delivery. Keep the failure timer from triggering. + if (component.PriorityCancelToken != null) + component.PriorityCancelToken.Cancel(); + + // The priority tape is visually considered to be a part of the + // anti-tamper lock, so remove that too. + _appearanceSystem.SetData(uid, MailVisuals.IsPriority, false); + + // The examination code depends on this being false to not show + // the priority tape description anymore. + component.IsPriority = false; + } + } + + /// + /// Check the ID against the mail's lock + /// + private void OnAfterInteractUsing(EntityUid uid, MailComponent component, AfterInteractUsingEvent args) + { + if (!args.CanReach || !component.IsLocked || !TryComp(uid, out var access)) + return; + + IdCardComponent? idCard = null; // We need an ID card. + if (HasComp(args.Used)) // Can we find it in a PDA if the user is using that? + { + _idCardSystem.TryGetIdCard(args.Used, out var pdaID); + idCard = pdaID; + } + + if (HasComp(args.Used)) // Or are they using an id card directly? + idCard = Comp(args.Used); + + if (idCard == null) // Return if we still haven't found an id card. + return; + + if (!HasComp(uid)) + { + if (idCard.FullName != component.Recipient || idCard.JobTitle != component.RecipientJob) + { + _popupSystem.PopupEntity(Loc.GetString("mail-recipient-mismatch"), uid, args.User); + return; + } + + if (!_accessSystem.IsAllowed(uid, args.User)) + { + _popupSystem.PopupEntity(Loc.GetString("mail-invalid-access"), uid, args.User); + return; + } + } + + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddOpenedMailEarnings(station, + logisticStats, + component.IsProfitable ? component.Bounty : 0); + }); + UnlockMail(uid, component); + + if (!component.IsProfitable) + { + _popupSystem.PopupEntity(Loc.GetString("mail-unlocked"), uid, args.User); + return; + } + + _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-reward", ("bounty", component.Bounty)), uid, args.User); + + component.IsProfitable = false; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var account)) + { + if (_stationSystem.GetOwningStation(uid) != station) + continue; + + _cargoSystem.UpdateBankAccount(station, account, component.Bounty); + } + } + + private void OnExamined(EntityUid uid, MailComponent component, ExaminedEvent args) + { + MailEntityStrings mailEntityStrings = component.IsLarge ? MailConstants.MailLarge : MailConstants.Mail; //Frontier: mail types stored per type (large mail) + if (!args.IsInDetailsRange) + { + args.PushMarkup(Loc.GetString(mailEntityStrings.DescFar)); // Frontier: mail constants struct + return; + } + + args.PushMarkup(Loc.GetString(mailEntityStrings.DescClose, ("name", component.Recipient), ("job", component.RecipientJob))); // Frontier: mail constants struct + + if (component.IsFragile) + args.PushMarkup(Loc.GetString("mail-desc-fragile")); + + if (component.IsPriority) + { + if (component.IsProfitable) + args.PushMarkup(Loc.GetString("mail-desc-priority")); + else + args.PushMarkup(Loc.GetString("mail-desc-priority-inactive")); + } + } + + /// + /// Penalize a station for a failed delivery. + /// + /// + /// This will mark a parcel as no longer being profitable, which will + /// prevent multiple failures on different conditions for the same + /// delivery.

+ /// + /// The standard penalization is breaking the anti-tamper lock, + /// but this allows a delivery to fail for other reasons too + /// while having a generic function to handle different messages. + ///
+ public void PenalizeStationFailedDelivery(EntityUid uid, MailComponent component, string localizationString) + { + if (!component.IsProfitable) + return; + + _chatSystem.TrySendInGameICMessage(uid, Loc.GetString(localizationString, ("credits", component.Penalty)), InGameICChatType.Speak, false); + _audioSystem.PlayPvs(component.PenaltySound, uid); + + component.IsProfitable = false; + + if (component.IsPriority) + _appearanceSystem.SetData(uid, MailVisuals.IsPriorityInactive, true); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var account)) + { + if (_stationSystem.GetOwningStation(uid) != station) + continue; + + _cargoSystem.UpdateBankAccount(station, account, component.Penalty); + return; + } + } + + private void OnDestruction(EntityUid uid, MailComponent component, DestructionEventArgs args) + { + if (component.IsLocked) + { + // DeltaV - Tampered mail recorded to logistic stats + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddTamperedMailLosses(station, + logisticStats, + component.IsProfitable ? component.Penalty : 0); + }); + + PenalizeStationFailedDelivery(uid, component, "mail-penalty-lock"); + } + + if (component.IsEnabled) + OpenMail(uid, component); + + UpdateAntiTamperVisuals(uid, false); + } + + private void OnDamage(EntityUid uid, MailComponent component, DamageChangedEvent args) + { + if (args.DamageDelta == null || !_containerSystem.TryGetContainer(uid, "contents", out var contents)) + return; + + // Transfer damage to the contents. + // This should be a general-purpose feature for all containers in the future. + foreach (var entity in contents.ContainedEntities.ToArray()) + _damageableSystem.TryChangeDamage(entity, args.DamageDelta); + } + + private void OnBreak(EntityUid uid, MailComponent component, BreakageEventArgs args) + { + _appearanceSystem.SetData(uid, MailVisuals.IsBroken, true); + + if (component.IsFragile) + { + // DeltaV - Broken mail recorded to logistic stats + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddDamagedMailLosses(station, + logisticStats, + component.IsProfitable ? component.Penalty : 0); + }); + + PenalizeStationFailedDelivery(uid, component, "mail-penalty-fragile"); + } + } + + private void OnMailEmagged(EntityUid uid, MailComponent component, ref GotEmaggedEvent args) + { + if (!component.IsLocked) + return; + + UnlockMail(uid, component); + + _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-by-emag"), uid, args.UserUid); + + _audioSystem.PlayPvs(component.EmagSound, uid, AudioParams.Default.WithVolume(4)); + component.IsProfitable = false; + args.Handled = true; + } + + /// + /// Returns true if the given entity is considered fragile for delivery. + /// + public bool IsEntityFragile(EntityUid uid, int fragileDamageThreshold) + { + // It takes damage on falling. + if (HasComp(uid)) + return true; + + // It can be spilled easily and has something to spill. + if (HasComp(uid) + && TryComp(uid, out var openable) + && !_openable.IsClosed(uid, null, openable) + && _solutionContainerSystem.PercentFull(uid) > 0) + return true; + + // It might be made of non-reinforced glass. + if (TryComp(uid, out DamageableComponent? damageableComponent) + && damageableComponent.DamageModifierSetId == "Glass") + return true; + + if (!TryComp(uid, out DestructibleComponent? destructibleComp)) + return false; + + // Fallback: It breaks or is destroyed in less than a damage + // threshold dictated by the teleporter. + foreach (var threshold in destructibleComp.Thresholds) + { + if (threshold.Trigger is not DamageTrigger trigger || trigger.Damage >= fragileDamageThreshold) + continue; + + foreach (var behavior in threshold.Behaviors) + { + if (behavior is not DoActsBehavior doActs) + continue; + + if (doActs.Acts.HasFlag(ThresholdActs.Breakage) || doActs.Acts.HasFlag(ThresholdActs.Destruction)) + return true; + } + } + + return false; + } + + public bool TryMatchJobTitleToDepartment(string jobTitle, [NotNullWhen(true)] out string? jobDepartment) + { + foreach (var department in _prototypeManager.EnumeratePrototypes()) + { + foreach (var role in department.Roles) + { + if (!_prototypeManager.TryIndex(role, out JobPrototype? jobPrototype) || jobPrototype.LocalizedName != jobTitle) + continue; + + jobDepartment = department.ID; + return true; + } + } + + jobDepartment = null; + return false; + } + + public bool TryMatchJobTitleToPrototype(string jobTitle, [NotNullWhen(true)] out JobPrototype? jobPrototype) + { + foreach (var job in _prototypeManager.EnumeratePrototypes()) + { + if (job.LocalizedName == jobTitle) + { + jobPrototype = job; + return true; + } + } + + jobPrototype = null; + return false; + } + + /// + /// Handle all the gritty details particular to a new mail entity. + /// + /// + /// This is separate mostly so the unit tests can get to it. + /// + public void SetupMail(EntityUid uid, MailTeleporterComponent component, MailRecipient recipient) + { + var mailComp = EnsureComp(uid); + + var container = _containerSystem.EnsureContainer(uid, "contents"); + foreach (var item in EntitySpawnCollection.GetSpawns(mailComp.Contents, _random)) + { + var entity = EntityManager.SpawnEntity(item, Transform(uid).Coordinates); + if (!_containerSystem.Insert(entity, container)) + { + _sawmill.Error($"Can't insert {ToPrettyString(entity)} into new mail delivery {ToPrettyString(uid)}! Deleting it."); + QueueDel(entity); + } + else if (!mailComp.IsFragile && IsEntityFragile(entity, component.FragileDamageThreshold)) + { + mailComp.IsFragile = true; + } + } + + mailComp.IsPriority = recipient.MayReceivePriorityMail && _random.Prob(component.PriorityChance); + mailComp.RecipientJob = recipient.Job; + mailComp.Recipient = recipient.Name; + + var mailEntityStrings = mailComp.IsLarge ? MailConstants.MailLarge : MailConstants.Mail; + if (mailComp.IsLarge) + { + mailComp.Bounty += component.LargeBonus; + mailComp.Penalty += component.LargeMalus; + } + + if (mailComp.IsFragile) + { + mailComp.Bounty += component.FragileBonus; + mailComp.Penalty += component.FragileMalus; + _appearanceSystem.SetData(uid, MailVisuals.IsFragile, true); + } + + if (mailComp.IsPriority) + { + mailComp.Bounty += component.PriorityBonus; + mailComp.Penalty += component.PriorityMalus; + _appearanceSystem.SetData(uid, MailVisuals.IsPriority, true); + mailComp.PriorityCancelToken = new CancellationTokenSource(); + + Timer.Spawn((int) component.PriorityDuration.TotalMilliseconds, () => + { + // DeltaV - Expired mail recorded to logistic stats + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddExpiredMailLosses(station, + logisticStats, + mailComp.IsProfitable ? mailComp.Penalty : 0); + }); + + PenalizeStationFailedDelivery(uid, mailComp, "mail-penalty-expired"); + }, mailComp.PriorityCancelToken.Token); + } + + _appearanceSystem.SetData(uid, MailVisuals.JobIcon, recipient.JobIcon); + + _metaDataSystem.SetEntityName(uid, Loc.GetString(mailEntityStrings.NameAddressed, // Frontier: move constant to MailEntityString + ("recipient", recipient.Name))); + + var accessReader = EnsureComp(uid); + accessReader.AccessLists.Add(recipient.AccessTags); + } + + /// + /// Return the parcels waiting for delivery. + /// + /// The mail teleporter to check. + public List GetUndeliveredParcels(EntityUid uid) + { + // An alternative solution would be to keep a list of the unopened + // parcels spawned by the teleporter and see if they're not carried + // by someone, but this is simple, and simple is good. + List undeliveredParcels = new(); + foreach (var entityInTile in TurfHelpers.GetEntitiesInTile(Transform(uid).Coordinates, LookupFlags.Dynamic | LookupFlags.Sundries)) + { + if (HasComp(entityInTile)) + undeliveredParcels.Add(entityInTile); + } + return undeliveredParcels; + } + + /// + /// Return how many parcels are waiting for delivery. + /// + /// The mail teleporter to check. + public uint GetUndeliveredParcelCount(EntityUid uid) + { + return (uint) GetUndeliveredParcels(uid).Count(); + } + + /// + /// Try to match a mail receiver to a mail teleporter. + /// + public bool TryGetMailTeleporterForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailTeleporterComponent? teleporterComponent) + { + foreach (var mailTeleporter in EntityQuery()) + { + if (_stationSystem.GetOwningStation(receiver.Owner) != _stationSystem.GetOwningStation(mailTeleporter.Owner)) + continue; + + teleporterComponent = mailTeleporter; + return true; + } + + teleporterComponent = null; + return false; + } + + /// + /// Try to construct a recipient struct for a mail parcel based on a receiver. + /// + public bool TryGetMailRecipientForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailRecipient? recipient) + { + // Because of the way this works, people are not considered + // candidates for mail if there is no valid PDA or ID in their slot + // or active hand. A better future solution might be checking the + // station records, possibly cross-referenced with the medical crew + // scanner to look for living recipients. TODO + + if (_idCardSystem.TryFindIdCard(receiver.Owner, out var idCard) + && TryComp(idCard.Owner, out var access) + && idCard.Comp.FullName != null + && idCard.Comp.JobTitle != null) + { + var accessTags = access.Tags; + + var mayReceivePriorityMail = !(_mindSystem.GetMind(receiver.Owner) == null); + + recipient = new MailRecipient(idCard.Comp.FullName, + idCard.Comp.JobTitle, + idCard.Comp.JobIcon, + accessTags, + mayReceivePriorityMail); + + return true; + } + + recipient = null; + return false; + } + + /// + /// Get the list of valid mail recipients for a mail teleporter. + /// + public List GetMailRecipientCandidates(EntityUid uid) + { + List candidateList = new(); + + foreach (var receiver in EntityQuery()) + { + if (_stationSystem.GetOwningStation(receiver.Owner) != _stationSystem.GetOwningStation(uid)) + continue; + + if (TryGetMailRecipientForReceiver(receiver, out MailRecipient? recipient)) + candidateList.Add(recipient.Value); + } + + return candidateList; + } + + /// + /// Handle the spawning of all the mail for a mail teleporter. + /// + public void SpawnMail(EntityUid uid, MailTeleporterComponent? component = null) + { + if (!Resolve(uid, ref component)) + { + _sawmill.Error($"Tried to SpawnMail on {ToPrettyString(uid)} without a valid MailTeleporterComponent!"); + return; + } + + if (GetUndeliveredParcelCount(uid) >= component.MaximumUndeliveredParcels) + return; + + var candidateList = GetMailRecipientCandidates(uid); + + if (candidateList.Count <= 0) + { + _sawmill.Error("List of mail candidates was empty!"); + return; + } + + if (!_prototypeManager.TryIndex(component.MailPool, out var pool)) + { + _sawmill.Error($"Can't index {ToPrettyString(uid)}'s MailPool {component.MailPool}!"); + return; + } + + for (int i = 0; + i < component.MinimumDeliveriesPerTeleport + candidateList.Count / component.CandidatesPerDelivery; + i++) + { + var candidate = _random.Pick(candidateList); + var possibleParcels = new Dictionary(pool.Everyone); + + if (TryMatchJobTitleToPrototype(candidate.Job, out JobPrototype? jobPrototype) + && pool.Jobs.TryGetValue(jobPrototype.ID, out Dictionary? jobParcels)) + { + possibleParcels = possibleParcels.Union(jobParcels) + .GroupBy(g => g.Key) + .ToDictionary(pair => pair.Key, pair => pair.First().Value); + } + + if (TryMatchJobTitleToDepartment(candidate.Job, out string? department) + && pool.Departments.TryGetValue(department, out Dictionary? departmentParcels)) + { + possibleParcels = possibleParcels.Union(departmentParcels) + .GroupBy(g => g.Key) + .ToDictionary(pair => pair.Key, pair => pair.First().Value); + } + + var accumulated = 0f; + var randomPoint = _random.NextFloat(possibleParcels.Values.Sum()); + string? chosenParcel = null; + foreach (var (key, weight) in possibleParcels) + { + accumulated += weight; + if (accumulated >= randomPoint) + { + chosenParcel = key; + break; + } + } + + if (chosenParcel == null) + { + _sawmill.Error($"MailSystem wasn't able to find a deliverable parcel for {candidate.Name}, {candidate.Job}!"); + return; + } + + var mail = EntityManager.SpawnEntity(chosenParcel, Transform(uid).Coordinates); + SetupMail(mail, component, candidate); + + _tagSystem.AddTag(mail, "Mail"); // Frontier + } + + if (_containerSystem.TryGetContainer(uid, "queued", out var queued)) + _containerSystem.EmptyContainer(queued); + + _audioSystem.PlayPvs(component.TeleportSound, uid); + } + + public void OpenMail(EntityUid uid, MailComponent? component = null, EntityUid? user = null) + { + if (!Resolve(uid, ref component)) + return; + + _audioSystem.PlayPvs(component.OpenSound, uid); + + if (user != null) + _handsSystem.TryDrop((EntityUid) user); + + if (!_containerSystem.TryGetContainer(uid, "contents", out var contents)) + { + // I silenced this error because it fails non deterministically in tests and doesn't seem to effect anything else. + // _sawmill.Error($"Mail {ToPrettyString(uid)} was missing contents container!"); + return; + } + + foreach (var entity in contents.ContainedEntities.ToArray()) + { + _handsSystem.PickupOrDrop(user, entity); + } + + _tagSystem.AddTag(uid, "Trash"); + _tagSystem.AddTag(uid, "Recyclable"); + component.IsEnabled = false; + UpdateMailTrashState(uid, true); + } + + private void UpdateAntiTamperVisuals(EntityUid uid, bool isLocked) + { + _appearanceSystem.SetData(uid, MailVisuals.IsLocked, isLocked); + } + + private void UpdateMailTrashState(EntityUid uid, bool isTrash) + { + _appearanceSystem.SetData(uid, MailVisuals.IsTrash, isTrash); + } + + // DeltaV - Helper function that executes for each StationLogisticsStatsComponent + // For updating MailMetrics stats + private void ExecuteForEachLogisticsStats(EntityUid uid, + Action action) + { + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var logisticStats)) + { + if (_stationSystem.GetOwningStation(uid) != station) + continue; + action(station, logisticStats); + } + } +} + +public struct MailRecipient( + string name, + string job, + string jobIcon, + HashSet> accessTags, + bool mayReceivePriorityMail) +{ + public string Name = name; + public string Job = job; + public string JobIcon = jobIcon; + public HashSet> AccessTags = accessTags; + public bool MayReceivePriorityMail = mayReceivePriorityMail; +} diff --git a/Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs deleted file mode 100644 index 61d94bbad31..00000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Threading; -using Robust.Shared.Audio; -using Content.Shared.Storage; -using Content.Shared.Mail; - -namespace Content.Server.Mail.Components -{ - [RegisterComponent] - public sealed partial class MailComponent : SharedMailComponent - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("recipient")] - public string Recipient = "None"; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("recipientJob")] - public string RecipientJob = "None"; - - // Why do we not use LockComponent? - // Because this can't be locked again, - // and we have special conditions for unlocking, - // and we don't want to add a verb. - [ViewVariables(VVAccess.ReadWrite)] - [DataField("isLocked")] - public bool IsLocked = true; - - /// - /// Is this parcel profitable to deliver for the station? - /// - /// - /// The station won't receive any award on delivery if this is false. - /// This is useful for broken fragile packages and packages that were - /// not delivered in time. - /// - [DataField("isProfitable")] - public bool IsProfitable = true; - - /// - /// Is this package considered fragile? - /// - /// - /// This can be set to true in the YAML files for a mail delivery to - /// always be Fragile, despite its contents. - /// - [DataField("isFragile")] - public bool IsFragile = false; - - /// - /// Is this package considered priority mail? - /// - /// - /// There will be a timer set for its successful delivery. The - /// station's bank account will be penalized if it is not delivered on - /// time. - /// - /// This is set to false on successful delivery. - /// - /// This can be set to true in the YAML files for a mail delivery to - /// always be Priority. - /// - [DataField("isPriority")] - public bool IsPriority = false; - - /// - /// What will be packaged when the mail is spawned. - /// - [DataField("contents")] - public List Contents = new(); - - /// - /// The amount that cargo will be awarded for delivering this mail. - /// - [DataField("bounty")] - public int Bounty = 750; - - /// - /// Penalty if the mail is destroyed. - /// - [DataField("penalty")] - public int Penalty = -250; - - /// - /// The sound that's played when the mail's lock is broken. - /// - [DataField("penaltySound")] - public SoundSpecifier PenaltySound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg"); - - /// - /// The sound that's played when the mail's opened. - /// - [DataField("openSound")] - public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/packetrip.ogg"); - - /// - /// The sound that's played when the mail's lock has been emagged. - /// - [DataField("emagSound")] - public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks"); - - /// - /// Whether this component is enabled. - /// Removed when it becomes trash. - /// - public bool IsEnabled = true; - - public CancellationTokenSource? priorityCancelToken; - } -} diff --git a/Content.Server/Nyanotrasen/Mail/Components/MailReceiverComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/MailReceiverComponent.cs deleted file mode 100644 index 4224de5de45..00000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/MailReceiverComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Server.Mail.Components -{ - [RegisterComponent] - public sealed partial class MailReceiverComponent : Component - {} -} diff --git a/Content.Server/Nyanotrasen/Mail/Components/MailTeleporterComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/MailTeleporterComponent.cs deleted file mode 100644 index bc05d7307d9..00000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/MailTeleporterComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Robust.Shared.Audio; - -namespace Content.Server.Mail.Components -{ - /// - /// This is for the mail teleporter. - /// Random mail will be teleported to this every few minutes. - /// - [RegisterComponent] - public sealed partial class MailTeleporterComponent : Component - { - - // Not starting accumulator at 0 so mail carriers have some deliveries to make shortly after roundstart. - [DataField("accumulator")] - public float Accumulator = 285f; - - [DataField("teleportInterval")] - public TimeSpan TeleportInterval = TimeSpan.FromMinutes(5); - - /// - /// The sound that's played when new mail arrives. - /// - [DataField("teleportSound")] - public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); - - /// - /// The MailDeliveryPoolPrototype that's used to select what mail this - /// teleporter can deliver. - /// - [DataField("mailPool")] - public string MailPool = "RandomMailDeliveryPool"; - - /// - /// How many mail candidates do we need per actual delivery sent when - /// the mail goes out? The number of candidates is divided by this number - /// to determine how many deliveries will be teleported in. - /// It does not determine unique recipients. That is random. - /// - [DataField("candidatesPerDelivery")] - public int CandidatesPerDelivery = 8; - - [DataField("minimumDeliveriesPerTeleport")] - public int MinimumDeliveriesPerTeleport = 1; - - /// - /// Do not teleport any more mail in, if there are at least this many - /// undelivered parcels. - /// - /// - /// Currently this works by checking how many MailComponent entities - /// are sitting on the teleporter's tile. - /// - /// It should be noted that if the number of actual deliveries to be - /// made based on the number of candidates divided by candidates per - /// delivery exceeds this number, the teleporter will spawn more mail - /// than this number. - /// - /// This is just a simple check to see if anyone's been picking up the - /// mail lately to prevent entity bloat for the sake of performance. - /// - [DataField("maximumUndeliveredParcels")] - public int MaximumUndeliveredParcels = 5; - - /// - /// Any item that breaks or is destroyed in less than this amount of - /// damage is one of the types of items considered fragile. - /// - [DataField("fragileDamageThreshold")] - public int FragileDamageThreshold = 10; - - /// - /// What's the bonus for delivering a fragile package intact? - /// - [DataField("fragileBonus")] - public int FragileBonus = 100; - - /// - /// What's the malus for failing to deliver a fragile package? - /// - [DataField("fragileMalus")] - public int FragileMalus = -100; - - /// - /// What's the chance for any one delivery to be marked as priority mail? - /// - [DataField("priorityChance")] - public float PriorityChance = 0.1f; - - /// - /// How long until a priority delivery is considered as having failed - /// if not delivered? - /// - [DataField("priorityDuration")] - public TimeSpan priorityDuration = TimeSpan.FromMinutes(5); - - /// - /// What's the bonus for delivering a priority package on time? - /// - [DataField("priorityBonus")] - public int PriorityBonus = 250; - - /// - /// What's the malus for failing to deliver a priority package? - /// - [DataField("priorityMalus")] - public int PriorityMalus = -250; - } -} diff --git a/Content.Server/Nyanotrasen/Mail/MailSystem.cs b/Content.Server/Nyanotrasen/Mail/MailSystem.cs deleted file mode 100644 index 05cd0c88a72..00000000000 --- a/Content.Server/Nyanotrasen/Mail/MailSystem.cs +++ /dev/null @@ -1,731 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Content.Server.Access.Systems; -using Content.Server.Cargo.Components; -using Content.Server.Cargo.Systems; -using Content.Server.Chat.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Damage.Components; -using Content.Server.Destructible; -using Content.Server.Destructible.Thresholds; -using Content.Server.Destructible.Thresholds.Behaviors; -using Content.Server.Destructible.Thresholds.Triggers; -using Content.Server.Fluids.Components; -using Content.Server.Item; -using Content.Server.Mail.Components; -using Content.Server.Mind; -using Content.Server.Nutrition.Components; -using Content.Server.Nutrition.EntitySystems; -using Content.Server.Popups; -using Content.Server.Power.Components; -using Content.Server.Station.Systems; -using Content.Server.Spawners.EntitySystems; -using Content.Shared.Access; -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Chat; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Damage; -using Content.Shared.Emag.Components; -using Content.Shared.Destructible; -using Content.Shared.Emag.Systems; -using Content.Shared.Examine; -using Content.Shared.Fluids.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Item; -using Content.Shared.Mail; -using Content.Shared.Maps; -using Content.Shared.Nutrition.Components; -using Content.Shared.Nutrition.EntitySystems; -using Content.Shared.PDA; -using Content.Shared.Random.Helpers; -using Content.Shared.Roles; -using Content.Shared.StatusIcon; -using Content.Shared.Storage; -using Content.Shared.Tag; -using Robust.Shared.Audio.Systems; -using Timer = Robust.Shared.Timing.Timer; - -namespace Content.Server.Mail -{ - public sealed class MailSystem : EntitySystem - { - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly AccessReaderSystem _accessSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly IdCardSystem _idCardSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly CargoSystem _cargoSystem = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly OpenableSystem _openable = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly ItemSystem _itemSystem = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; - - private ISawmill _sawmill = default!; - - public override void Initialize() - { - base.Initialize(); - - _sawmill = Logger.GetSawmill("mail"); - - SubscribeLocalEvent(OnSpawnPlayer, after: new[] { typeof(SpawnPointSystem) }); - - SubscribeLocalEvent(OnRemove); - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnAfterInteractUsing); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnDestruction); - SubscribeLocalEvent(OnDamage); - SubscribeLocalEvent(OnBreak); - SubscribeLocalEvent(OnMailEmagged); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - foreach (var mailTeleporter in EntityQuery()) - { - if (TryComp(mailTeleporter.Owner, out var power) && !power.Powered) - return; - - mailTeleporter.Accumulator += frameTime; - - if (mailTeleporter.Accumulator < mailTeleporter.TeleportInterval.TotalSeconds) - continue; - - mailTeleporter.Accumulator -= (float) mailTeleporter.TeleportInterval.TotalSeconds; - - SpawnMail(mailTeleporter.Owner, mailTeleporter); - } - } - - /// - /// Dynamically add the MailReceiver component to appropriate entities. - /// - private void OnSpawnPlayer(PlayerSpawningEvent args) - { - if (args.SpawnResult == null || - args.Job == null || - args.Station is not {} station) - { - return; - } - - if (!HasComp(station)) - return; - - AddComp(args.SpawnResult.Value); - } - - private void OnRemove(EntityUid uid, MailComponent component, ComponentRemove args) - { - // Make sure the priority timer doesn't run. - if (component.priorityCancelToken != null) - component.priorityCancelToken.Cancel(); - } - - /// - /// Try to open the mail. - /// - private void OnUseInHand(EntityUid uid, MailComponent component, UseInHandEvent args) - { - if (!component.IsEnabled) - return; - if (component.IsLocked) - { - _popupSystem.PopupEntity(Loc.GetString("mail-locked"), uid, args.User); - return; - } - OpenMail(uid, component, args.User); - } - - /// - /// Handle logic similar between a normal mail unlock and an emag - /// frying out the lock. - /// - private void UnlockMail(EntityUid uid, MailComponent component) - { - component.IsLocked = false; - UpdateAntiTamperVisuals(uid, false); - - if (component.IsPriority) - { - // This is a successful delivery. Keep the failure timer from triggering. - if (component.priorityCancelToken != null) - component.priorityCancelToken.Cancel(); - - // The priority tape is visually considered to be a part of the - // anti-tamper lock, so remove that too. - _appearanceSystem.SetData(uid, MailVisuals.IsPriority, false); - - // The examination code depends on this being false to not show - // the priority tape description anymore. - component.IsPriority = false; - } - } - - /// - /// Check the ID against the mail's lock - /// - private void OnAfterInteractUsing(EntityUid uid, MailComponent component, AfterInteractUsingEvent args) - { - if (!args.CanReach || !component.IsLocked) - return; - - if (!TryComp(uid, out var access)) - return; - - IdCardComponent? idCard = null; // We need an ID card. - - if (HasComp(args.Used)) /// Can we find it in a PDA if the user is using that? - { - _idCardSystem.TryGetIdCard(args.Used, out var pdaID); - idCard = pdaID; - } - - if (HasComp(args.Used)) /// Or are they using an id card directly? - idCard = Comp(args.Used); - - if (idCard == null) /// Return if we still haven't found an id card. - return; - - if (!HasComp(uid)) - { - if (idCard.FullName != component.Recipient || idCard.JobTitle != component.RecipientJob) - { - _popupSystem.PopupEntity(Loc.GetString("mail-recipient-mismatch"), uid, args.User); - return; - } - - if (!_accessSystem.IsAllowed(uid, args.User)) - { - _popupSystem.PopupEntity(Loc.GetString("mail-invalid-access"), uid, args.User); - return; - } - } - - UnlockMail(uid, component); - - if (!component.IsProfitable) - { - _popupSystem.PopupEntity(Loc.GetString("mail-unlocked"), uid, args.User); - return; - } - - _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-reward", ("bounty", component.Bounty)), uid, args.User); - - component.IsProfitable = false; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var station, out var account)) - { - if (_stationSystem.GetOwningStation(uid) != station) - continue; - - _cargoSystem.UpdateBankAccount(station, account, component.Bounty); - return; - } - } - - private void OnExamined(EntityUid uid, MailComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange) - { - args.PushMarkup(Loc.GetString("mail-desc-far")); - return; - } - - args.PushMarkup(Loc.GetString("mail-desc-close", ("name", component.Recipient), ("job", component.RecipientJob))); - - if (component.IsFragile) - args.PushMarkup(Loc.GetString("mail-desc-fragile")); - - if (component.IsPriority) - { - if (component.IsProfitable) - args.PushMarkup(Loc.GetString("mail-desc-priority")); - else - args.PushMarkup(Loc.GetString("mail-desc-priority-inactive")); - } - } - - /// - /// Penalize a station for a failed delivery. - /// - /// - /// This will mark a parcel as no longer being profitable, which will - /// prevent multiple failures on different conditions for the same - /// delivery. - /// - /// The standard penalization is breaking the anti-tamper lock, - /// but this allows a delivery to fail for other reasons too - /// while having a generic function to handle different messages. - /// - public void PenalizeStationFailedDelivery(EntityUid uid, MailComponent component, string localizationString) - { - if (!component.IsProfitable) - return; - - _chatSystem.TrySendInGameICMessage(uid, Loc.GetString(localizationString, ("credits", component.Penalty)), InGameICChatType.Speak, false); - _audioSystem.PlayPvs(component.PenaltySound, uid); - - component.IsProfitable = false; - - if (component.IsPriority) - _appearanceSystem.SetData(uid, MailVisuals.IsPriorityInactive, true); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var station, out var account)) - { - if (_stationSystem.GetOwningStation(uid) != station) - continue; - - _cargoSystem.UpdateBankAccount(station, account, component.Penalty); - return; - } - } - - private void OnDestruction(EntityUid uid, MailComponent component, DestructionEventArgs args) - { - if (component.IsLocked) - PenalizeStationFailedDelivery(uid, component, "mail-penalty-lock"); - - if (component.IsEnabled) - OpenMail(uid, component); - - UpdateAntiTamperVisuals(uid, false); - } - - private void OnDamage(EntityUid uid, MailComponent component, DamageChangedEvent args) - { - if (args.DamageDelta == null) - return; - - if (!_containerSystem.TryGetContainer(uid, "contents", out var contents)) - return; - - // Transfer damage to the contents. - // This should be a general-purpose feature for all containers in the future. - foreach (var entity in contents.ContainedEntities.ToArray()) - { - _damageableSystem.TryChangeDamage(entity, args.DamageDelta); - } - } - - private void OnBreak(EntityUid uid, MailComponent component, BreakageEventArgs args) - { - _appearanceSystem.SetData(uid, MailVisuals.IsBroken, true); - - if (component.IsFragile) - PenalizeStationFailedDelivery(uid, component, "mail-penalty-fragile"); - } - - private void OnMailEmagged(EntityUid uid, MailComponent component, ref GotEmaggedEvent args) - { - if (!component.IsLocked) - return; - - UnlockMail(uid, component); - - _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-by-emag"), uid, args.UserUid); - - _audioSystem.PlayPvs(component.EmagSound, uid, AudioParams.Default.WithVolume(4)); - component.IsProfitable = false; - args.Handled = true; - } - - /// - /// Returns true if the given entity is considered fragile for delivery. - /// - public bool IsEntityFragile(EntityUid uid, int fragileDamageThreshold) - { - // It takes damage on falling. - if (HasComp(uid)) - return true; - - // It can be spilled easily and has something to spill. - if (HasComp(uid) - && TryComp(uid, out var openable) - && !_openable.IsClosed(uid, null, openable) - && _solutionContainerSystem.PercentFull(uid) > 0) - return true; - - // It might be made of non-reinforced glass. - if (TryComp(uid, out DamageableComponent? damageableComponent) - && damageableComponent.DamageModifierSetId == "Glass") - return true; - - // Fallback: It breaks or is destroyed in less than a damage - // threshold dictated by the teleporter. - if (TryComp(uid, out DestructibleComponent? destructibleComp)) - { - foreach (var threshold in destructibleComp.Thresholds) - { - if (threshold.Trigger is DamageTrigger trigger - && trigger.Damage < fragileDamageThreshold) - { - foreach (var behavior in threshold.Behaviors) - { - if (behavior is DoActsBehavior doActs) - { - if (doActs.Acts.HasFlag(ThresholdActs.Breakage) - || doActs.Acts.HasFlag(ThresholdActs.Destruction)) - { - return true; - } - } - } - } - } - } - - return false; - } - - public bool TryMatchJobTitleToDepartment(string jobTitle, [NotNullWhen(true)] out string? jobDepartment) - { - foreach (var department in _prototypeManager.EnumeratePrototypes()) - { - foreach (var role in department.Roles) - { - if (_prototypeManager.TryIndex(role, out JobPrototype? _jobPrototype) - && _jobPrototype.LocalizedName == jobTitle) - { - jobDepartment = department.ID; - return true; - } - } - } - - jobDepartment = null; - return false; - } - - public bool TryMatchJobTitleToPrototype(string jobTitle, [NotNullWhen(true)] out JobPrototype? jobPrototype) - { - foreach (var job in _prototypeManager.EnumeratePrototypes()) - { - if (job.LocalizedName == jobTitle) - { - jobPrototype = job; - return true; - } - } - - jobPrototype = null; - return false; - } - - /// - /// Handle all the gritty details particular to a new mail entity. - /// - /// - /// This is separate mostly so the unit tests can get to it. - /// - public void SetupMail(EntityUid uid, MailTeleporterComponent component, MailRecipient recipient) - { - var mailComp = EnsureComp(uid); - - var container = _containerSystem.EnsureContainer(uid, "contents"); - foreach (var item in EntitySpawnCollection.GetSpawns(mailComp.Contents, _random)) - { - var entity = EntityManager.SpawnEntity(item, Transform(uid).Coordinates); - if (!_containerSystem.Insert(entity, container)) - { - _sawmill.Error($"Can't insert {ToPrettyString(entity)} into new mail delivery {ToPrettyString(uid)}! Deleting it."); - QueueDel(entity); - } - else if (!mailComp.IsFragile && IsEntityFragile(entity, component.FragileDamageThreshold)) - { - mailComp.IsFragile = true; - } - } - - if (_random.Prob(component.PriorityChance)) - mailComp.IsPriority = true; - - // This needs to override both the random probability and the - // entity prototype, so this is fine. - if (!recipient.MayReceivePriorityMail) - mailComp.IsPriority = false; - - mailComp.RecipientJob = recipient.Job; - mailComp.Recipient = recipient.Name; - - if (mailComp.IsFragile) - { - mailComp.Bounty += component.FragileBonus; - mailComp.Penalty += component.FragileMalus; - _appearanceSystem.SetData(uid, MailVisuals.IsFragile, true); - } - - if (mailComp.IsPriority) - { - mailComp.Bounty += component.PriorityBonus; - mailComp.Penalty += component.PriorityMalus; - _appearanceSystem.SetData(uid, MailVisuals.IsPriority, true); - - mailComp.priorityCancelToken = new CancellationTokenSource(); - - Timer.Spawn((int) component.priorityDuration.TotalMilliseconds, - () => PenalizeStationFailedDelivery(uid, mailComp, "mail-penalty-expired"), - mailComp.priorityCancelToken.Token); - } - - _appearanceSystem.SetData(uid, MailVisuals.JobIcon, recipient.JobIcon); - - _metaDataSystem.SetEntityName(uid, Loc.GetString("mail-item-name-addressed", - ("recipient", recipient.Name))); - - var accessReader = EnsureComp(uid); - accessReader.AccessLists.Add(recipient.AccessTags); - } - - /// - /// Return the parcels waiting for delivery. - /// - /// The mail teleporter to check. - public List GetUndeliveredParcels(EntityUid uid) - { - // An alternative solution would be to keep a list of the unopened - // parcels spawned by the teleporter and see if they're not carried - // by someone, but this is simple, and simple is good. - List undeliveredParcels = new(); - foreach (var entityInTile in TurfHelpers.GetEntitiesInTile(Transform(uid).Coordinates, LookupFlags.Dynamic | LookupFlags.Sundries)) - { - if (HasComp(entityInTile)) - undeliveredParcels.Add(entityInTile); - } - return undeliveredParcels; - } - - /// - /// Return how many parcels are waiting for delivery. - /// - /// The mail teleporter to check. - public uint GetUndeliveredParcelCount(EntityUid uid) - { - return (uint) GetUndeliveredParcels(uid).Count(); - } - - /// - /// Try to match a mail receiver to a mail teleporter. - /// - public bool TryGetMailTeleporterForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailTeleporterComponent? teleporterComponent) - { - foreach (var mailTeleporter in EntityQuery()) - { - if (_stationSystem.GetOwningStation(receiver.Owner) == _stationSystem.GetOwningStation(mailTeleporter.Owner)) - { - teleporterComponent = mailTeleporter; - return true; - } - } - - teleporterComponent = null; - return false; - } - - /// - /// Try to construct a recipient struct for a mail parcel based on a receiver. - /// - public bool TryGetMailRecipientForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailRecipient? recipient) - { - // Because of the way this works, people are not considered - // candidates for mail if there is no valid PDA or ID in their slot - // or active hand. A better future solution might be checking the - // station records, possibly cross-referenced with the medical crew - // scanner to look for living recipients. TODO - - if (_idCardSystem.TryFindIdCard(receiver.Owner, out var idCard) - && TryComp(idCard.Owner, out var access) - && idCard.Comp.FullName != null - && idCard.Comp.JobTitle != null) - { - var accessTags = access.Tags; - - var mayReceivePriorityMail = !(_mindSystem.GetMind(receiver.Owner) == null); - - recipient = new MailRecipient(idCard.Comp.FullName, - idCard.Comp.JobTitle, - idCard.Comp.JobIcon, - accessTags, - mayReceivePriorityMail); - - return true; - } - - recipient = null; - return false; - } - - /// - /// Get the list of valid mail recipients for a mail teleporter. - /// - public List GetMailRecipientCandidates(EntityUid uid) - { - List candidateList = new(); - - foreach (var receiver in EntityQuery()) - { - if (_stationSystem.GetOwningStation(receiver.Owner) != _stationSystem.GetOwningStation(uid)) - continue; - - if (TryGetMailRecipientForReceiver(receiver, out MailRecipient? recipient)) - candidateList.Add(recipient.Value); - } - - return candidateList; - } - - /// - /// Handle the spawning of all the mail for a mail teleporter. - /// - public void SpawnMail(EntityUid uid, MailTeleporterComponent? component = null) - { - if (!Resolve(uid, ref component)) - { - _sawmill.Error($"Tried to SpawnMail on {ToPrettyString(uid)} without a valid MailTeleporterComponent!"); - return; - } - - if (GetUndeliveredParcelCount(uid) >= component.MaximumUndeliveredParcels) - return; - - var candidateList = GetMailRecipientCandidates(uid); - - if (candidateList.Count <= 0) - { - _sawmill.Error("List of mail candidates was empty!"); - return; - } - - if (!_prototypeManager.TryIndex(component.MailPool, out var pool)) - { - _sawmill.Error($"Can't index {ToPrettyString(uid)}'s MailPool {component.MailPool}!"); - return; - } - - for (int i = 0; - i < component.MinimumDeliveriesPerTeleport + candidateList.Count / component.CandidatesPerDelivery; - i++) - { - var candidate = _random.Pick(candidateList); - var possibleParcels = new Dictionary(pool.Everyone); - - if (TryMatchJobTitleToPrototype(candidate.Job, out JobPrototype? jobPrototype) - && pool.Jobs.TryGetValue(jobPrototype.ID, out Dictionary? jobParcels)) - { - possibleParcels = possibleParcels.Union(jobParcels) - .GroupBy(g => g.Key) - .ToDictionary(pair => pair.Key, pair => pair.First().Value); - } - - if (TryMatchJobTitleToDepartment(candidate.Job, out string? department) - && pool.Departments.TryGetValue(department, out Dictionary? departmentParcels)) - { - possibleParcels = possibleParcels.Union(departmentParcels) - .GroupBy(g => g.Key) - .ToDictionary(pair => pair.Key, pair => pair.First().Value); - } - - var accumulated = 0f; - var randomPoint = _random.NextFloat(possibleParcels.Values.Sum()); - string? chosenParcel = null; - foreach (var (key, weight) in possibleParcels) - { - accumulated += weight; - if (accumulated >= randomPoint) - { - chosenParcel = key; - break; - } - } - - if (chosenParcel == null) - { - _sawmill.Error($"MailSystem wasn't able to find a deliverable parcel for {candidate.Name}, {candidate.Job}!"); - return; - } - - var mail = EntityManager.SpawnEntity(chosenParcel, Transform(uid).Coordinates); - SetupMail(mail, component, candidate); - } - - if (_containerSystem.TryGetContainer(uid, "queued", out var queued)) - _containerSystem.EmptyContainer(queued); - - _audioSystem.PlayPvs(component.TeleportSound, uid); - } - - public void OpenMail(EntityUid uid, MailComponent? component = null, EntityUid? user = null) - { - if (!Resolve(uid, ref component)) - return; - - _audioSystem.PlayPvs(component.OpenSound, uid); - - if (user != null) - _handsSystem.TryDrop((EntityUid) user); - - if (!_containerSystem.TryGetContainer(uid, "contents", out var contents)) - { - // I silenced this error because it fails non deterministically in tests and doesn't seem to effect anything else. - // _sawmill.Error($"Mail {ToPrettyString(uid)} was missing contents container!"); - return; - } - - foreach (var entity in contents.ContainedEntities.ToArray()) - { - _handsSystem.PickupOrDrop(user, entity); - } - - _tagSystem.AddTag(uid, "Trash"); - _tagSystem.AddTag(uid, "Recyclable"); - component.IsEnabled = false; - UpdateMailTrashState(uid, true); - } - - private void UpdateAntiTamperVisuals(EntityUid uid, bool isLocked) - { - _appearanceSystem.SetData(uid, MailVisuals.IsLocked, isLocked); - } - - private void UpdateMailTrashState(EntityUid uid, bool isTrash) - { - _appearanceSystem.SetData(uid, MailVisuals.IsTrash, isTrash); - } - } - - public struct MailRecipient( - string name, - string job, - string jobIcon, - HashSet> accessTags, - bool mayReceivePriorityMail) - { - public string Name = name; - public string Job = job; - public string JobIcon = jobIcon; - public HashSet> AccessTags = accessTags; - public bool MayReceivePriorityMail = mayReceivePriorityMail; - } -} diff --git a/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs b/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs new file mode 100644 index 00000000000..69506f5d0c2 --- /dev/null +++ b/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs @@ -0,0 +1,50 @@ +using Content.Shared.Security; +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class MailMetricUiState : BoundUserInterfaceState +{ + public readonly MailStats Metrics; + public int UnopenedMailCount { get; } + public int TotalMail { get; } + public double SuccessRate { get; } + + public MailMetricUiState(MailStats metrics, int unopenedMailCount) + { + Metrics = metrics; + UnopenedMailCount = unopenedMailCount; + TotalMail = metrics.TotalMail(unopenedMailCount); + SuccessRate = metrics.SuccessRate(unopenedMailCount); + } +} + +[DataDefinition] +[Serializable, NetSerializable] +public partial record struct MailStats +{ + public int Earnings { get; init; } + public int DamagedLosses { get; init; } + public int ExpiredLosses { get; init; } + public int TamperedLosses { get; init; } + public int OpenedCount { get; init; } + public int DamagedCount { get; init; } + public int ExpiredCount { get; init; } + public int TamperedCount { get; init; } + + public readonly int TotalMail(int unopenedCount) + { + return OpenedCount + unopenedCount; + } + + public readonly int TotalIncome => Earnings + DamagedLosses + ExpiredLosses + TamperedLosses; + + public readonly double SuccessRate(int unopenedCount) + { + var totalMail = TotalMail(unopenedCount); + return (totalMail > 0) + ? Math.Round((double)OpenedCount / totalMail * 100, 2) + : 0; + } +} \ No newline at end of file diff --git a/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl index 9b4c59d001d..ede1a36b8ee 100644 --- a/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl @@ -118,3 +118,16 @@ crime-assist-sophont-explanation = A sophont is described as any entity with the • [bold]Sentience[/bold]: the entity has the capacity to process an emotion or lack thereof, or at a minimum the ability to recognise its own pain. • [bold]Self-awareness[/bold]: the entity is capable of altering its behaviour in a reasonable fashion as a result of stimuli, or at a minimum is capable of recognising its own sapience and sentience. Any sophont is considered a legal person, regardless of origin or prior cognitive status. Much like any other intelligent organic, a sophont may press charges against crew and be tried for crimes. + +mail-metrics-program-name = MailMetrics +mail-metrics-header = Income from Mail Deliveries +mail-metrics-opened = Earnings (Opened) +mail-metrics-expired = Losses (Expired) +mail-metrics-damaged = Losses (Damaged) +mail-metrics-tampered = Losses (Tampered) +mail-metrics-unopened = Unopened +mail-metrics-count-header = Packages +mail-metrics-money-header = Spesos +mail-metrics-total = Total +mail-metrics-progress = {$opened} out of {$total} packages opened! +mail-metrics-progress-percent = Success rate: {$successRate}% diff --git a/Resources/Locale/en-US/Mail/mail.ftl b/Resources/Locale/en-US/mail/commands.ftl similarity index 52% rename from Resources/Locale/en-US/Mail/mail.ftl rename to Resources/Locale/en-US/mail/commands.ftl index 72cd3879b32..1f471bb7a59 100644 --- a/Resources/Locale/en-US/Mail/mail.ftl +++ b/Resources/Locale/en-US/mail/commands.ftl @@ -1,22 +1,6 @@ -mail-recipient-mismatch = Recipient name or job does not match. -mail-invalid-access = Recipient name and job match, but access isn't as expected. -mail-locked = The anti-tamper lock hasn't been removed. Tap the recipient's ID. -mail-desc-far = A parcel of mail. You can't make out who it's addressed to from this distance. -mail-desc-close = A parcel of mail addressed to {CAPITALIZE($name)}, {$job}. -mail-desc-fragile = It has a [color=red]red fragile label[/color]. -mail-desc-priority = The anti-tamper lock's [color=yellow]yellow priority tape[/color] is active. Better deliver it on time! -mail-desc-priority-inactive = The anti-tamper lock's [color=#886600]yellow priority tape[/color] is inactive. -mail-unlocked = Anti-tamper system unlocked. -mail-unlocked-by-emag = Anti-tamper system *BZZT*. -mail-unlocked-reward = Anti-tamper system unlocked. {$bounty} spesos have been added to logistics's account. -mail-penalty-lock = ANTI-TAMPER LOCK BROKEN. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} CREDITS. -mail-penalty-fragile = INTEGRITY COMPROMISED. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} CREDITS. -mail-penalty-expired = DELIVERY PAST DUE. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} CREDITS. -mail-item-name-unaddressed = mail -mail-item-name-addressed = mail ({$recipient}) - +# Mailto command-mailto-description = Queue a parcel to be delivered to an entity. Example usage: `mailto 1234 5678 false false`. The target container's contents will be transferred to an actual mail parcel. -command-mailto-help = Usage: {$command} [is-fragile: true or false] [is-priority: true or false] +command-mailto-help = Usage: {$command} [is-fragile: true or false] [is-priority: true or false] [is-large: true or false, optional] command-mailto-no-mailreceiver = Target recipient entity does not have a {$requiredComponent}. command-mailto-no-blankmail = The {$blankMail} prototype doesn't exist. Something is very wrong. Contact a programmer. command-mailto-bogus-mail = {$blankMail} did not have {$requiredMailComponent}. Something is very wrong. Contact a programmer. @@ -25,6 +9,12 @@ command-mailto-unable-to-receive = Target recipient entity was unable to be setu command-mailto-no-teleporter-found = Target recipient entity was unable to be matched to any station's mail teleporter. Recipient may be off-station. command-mailto-success = Success! Mail parcel has been queued for next teleport in {$timeToTeleport} seconds. +# Mailnow command-mailnow = Force all mail teleporters to deliver another round of mail as soon as possible. This will not bypass the undelivered mail limit. command-mailnow-help = Usage: {$command} command-mailnow-success = Success! All mail teleporters will be delivering another round of mail soon. + +# Mailtestbulk +command-mailtestbulk = Sends one of each type of parcel to a given mail teleporter. Implicitly calls mailnow. +command-mailtestbulk-help = Usage: {$command} +command-mailtestbulk-success = Success! All mail teleporters will be delivering another round of mail soon. diff --git a/Resources/Locale/en-US/mail/mail.ftl b/Resources/Locale/en-US/mail/mail.ftl new file mode 100644 index 00000000000..5a277377324 --- /dev/null +++ b/Resources/Locale/en-US/mail/mail.ftl @@ -0,0 +1,23 @@ +mail-recipient-mismatch = Recipient name or job does not match. +mail-invalid-access = Recipient name and job match, but access isn't as expected. +mail-locked = The anti-tamper lock hasn't been removed. Tap the recipient's ID. +mail-desc-far = A parcel of mail. You can't make out who it's addressed to from this distance. +mail-desc-close = A parcel of mail addressed to {CAPITALIZE($name)}, {$job}. +mail-desc-fragile = It has a [color=red]red fragile label[/color]. +mail-desc-priority = The anti-tamper lock's [color=yellow]yellow priority tape[/color] is active. Better deliver it on time! +mail-desc-priority-inactive = The anti-tamper lock's [color=#886600]yellow priority tape[/color] is inactive. +mail-unlocked = Anti-tamper system unlocked. +mail-unlocked-by-emag = Anti-tamper system *BZZT*. +mail-unlocked-reward = Anti-tamper system unlocked. {$bounty} spesos have been added to logistics' account. +mail-penalty-lock = ANTI-TAMPER LOCK BROKEN. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} SPESOS. +mail-penalty-fragile = INTEGRITY COMPROMISED. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} SPESOS. +mail-penalty-expired = DELIVERY PAST DUE. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} SPESOS. +mail-item-name-unaddressed = mail +mail-item-name-addressed = mail ({$recipient}) + +mail-large-item-name-unaddressed = package +mail-large-item-name-addressed = package ({$recipient}) +mail-large-desc-far = A large package. +mail-large-desc-close = A large package addressed to {CAPITALIZE($name)}, {$job}. + + diff --git a/Resources/Migrations/frontierMigrations.yml b/Resources/Migrations/frontierMigrations.yml new file mode 100644 index 00000000000..e8d607494ed --- /dev/null +++ b/Resources/Migrations/frontierMigrations.yml @@ -0,0 +1,2 @@ +# 2024-08-22 - Frontier Mail - Add more to these when they come up as mapped. Part of the Frontier Mail port, blame Tortuga. +MailPAI: MailNFPAI diff --git a/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml index 88f691ba9b7..778b3725858 100644 --- a/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml +++ b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml @@ -7,8 +7,10 @@ CourierBag: 2 ClothingHeadsetCargo: 2 ClothingOuterWinterCargo: 2 - ClothingUniformMailCarrier: 2 # Nyanotrasen - Mail Carrier old gear - ClothingUniformSkirtMailCarrier: 2 # Nyanotrasen - Mail Carrier old gear - ClothingHeadMailCarrier: 2 # Nyanotrasen - Mail Carrier old gear - MailBag: 2 # Nyanotrasen - Mail Carrier old gear - ClothingOuterWinterCoatMail: 2 # Nyanotrasen - Mail Carrier old gear + ClothingUniformMailCarrier: 2 + ClothingUniformSkirtMailCarrier: 2 + ClothingHeadMailCarrier: 2 + MailBag: 2 + ClothingOuterWinterCoatMail: 2 + WeaponMailLake: 2 + BoxMailCapsulePrimed: 4 diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml index def215cee43..81e11d9d084 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml @@ -38,3 +38,24 @@ sprite: Objects/Weapons/Melee/stunbaton.rsi state: stunbaton_on - type: SecWatchCartridge + +- type: entity + parent: BaseItem + id: MailMetricsCartridge + name: mail metrics cartridge + description: A cartridge that tracks statistics related to mail deliveries. + components: + - type: Sprite + sprite: DeltaV/Objects/Devices/cartridge.rsi + state: cart-mail + - type: Icon + sprite: DeltaV/Objects/Devices/cartridge.rsi + state: cart-mail + - type: UIFragment + ui: !type:MailMetricUi + - type: MailMetricsCartridge + - type: Cartridge + programName: mail-metrics-program-name + icon: + sprite: Objects/Specific/Mail/mail.rsi + state: icon diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml index 7de554f5ff5..6f96930078b 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml @@ -1,3 +1,4 @@ +# DeltaV Mail - type: entity noSpawn: true parent: BaseMail @@ -175,3 +176,1643 @@ - type: Mail contents: - id: FoodPiePumpkin + +- type: entity + noSpawn: true + parent: BaseMail + id: MailDVCosplayFakeWizard + suffix: cosplay-wizard, fake as fuck + components: + - type: Mail + contents: + - id: ClothingOuterWizardFake + - id: ClothingHeadHatWizardFake + - id: ClothingShoesWizardFake + - id: FoodBurgerSpell + orGroup: FakeWizard + prob: 0.3 + - id: FoodKebabSkewer + orGroup: FakeWizard + prob: 0.1 + +- type: entity + parent: BaseMail + id: MailDVScarves + suffix: scarves + components: + - type: Mail + contents: + - id: ClothingNeckScarfStripedRed + orGroup: Scarf + - id: ClothingNeckScarfStripedBlue + orGroup: Scarf + - id: ClothingNeckScarfStripedGreen + orGroup: Scarf + - id: ClothingNeckScarfStripedBlack + orGroup: Scarf + - id: ClothingNeckScarfStripedBrown + orGroup: Scarf + - id: ClothingNeckScarfStripedLightBlue + orGroup: Scarf + - id: ClothingNeckScarfStripedOrange + orGroup: Scarf + - id: ClothingNeckScarfStripedPurple + orGroup: Scarf + - id: ClothingNeckScarfStripedZebra + orGroup: Scarf + - id: ClothingNeckScarfStripedSyndieGreen + orGroup: Scarf + prob: 0.25 + - id: ClothingNeckScarfStripedSyndieRed + orGroup: Scarf + prob: 0.25 + - id: ClothingNeckScarfStripedCentcom + orGroup: Scarf + prob: 0.001 + +- type: entity + parent: BaseMailLarge + id: MailDVBoxes + suffix: boxes + components: + - type: Mail + contents: +# - id: BoxEnvelope # TODO: we do not have this on EE? +# orGroup: Box + - id: BoxCardboard + orGroup: Box + - id: BoxMRE + orGroup: Box + - id: BoxPerformer + orGroup: Box + - id: BoxFlare + orGroup: Box + - id: BoxDarts + orGroup: Box + - id: BoxMousetrap + orGroup: Box + + + +# Frontier Mail, including Extended Nyano Mail. (Pony Express?) +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFAlcohol + suffix: alcohol, extended + components: + - type: Mail + isFragile: true + contents: + # 12.5 overall weight, 8% base chance + - id: DrinkAbsintheBottleFull + orGroup: Drink + - id: DrinkBlueCuracaoBottleFull + orGroup: Drink + - id: DrinkCoffeeLiqueurBottleFull + orGroup: Drink + - id: DrinkGinBottleFull + orGroup: Drink + - id: DrinkMelonLiquorBottleFull + orGroup: Drink + - id: DrinkRumBottleFull + orGroup: Drink + - id: DrinkTequilaBottleFull + orGroup: Drink + - id: DrinkVermouthBottleFull + orGroup: Drink + - id: DrinkVodkaBottleFull + orGroup: Drink + - id: DrinkWhiskeyBottleFull + orGroup: Drink + - id: DrinkWineBottleFull + orGroup: Drink + - id: DrinkChampagneBottleFull + orGroup: Drink + prob: 0.5 + - id: DrinkGildlagerBottleFull + orGroup: Drink + prob: 0.5 + - id: DrinkPatronBottleFull + orGroup: Drink + prob: 0.5 + - id: DrinkGlass + amount: 2 + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFBible + suffix: bible, extended + components: + - type: Mail + contents: + - id: Bible + - id: ClothingHeadHatWitch1 #DeltaV - Compensates for items that don't exist here + - id: ClothingOuterCoatMNKBlackTopCoat #DeltaV - Compensates for items that don't exist here + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFBikeHorn + suffix: bike horn, random + components: + - type: Mail + contents: + - id: BikeHorn + orGroup: Horn + prob: 0.95 + - id: CluwneHorn + orGroup: Horn + prob: 0.05 + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFBuildABuddy + suffix: Build-a-Buddy + components: + - type: Mail + isFragile: true + contents: +# - id: BoxBuildABuddyGoblin # DeltaV - Goblins Aren't Real +# orGroup: Box + - id: BoxBuildABuddyHuman + orGroup: Box + - id: BoxBuildABuddyReptilian + orGroup: Box + - id: BoxBuildABuddySlime + orGroup: Box + - id: BoxBuildABuddyVulpkanin + orGroup: Box + - id: DrinkSpaceGlue + - id: PaperMailNFBuildABuddy + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFCake + suffix: cake, extended + components: + - type: Mail + isFragile: true + isPriority: true + contents: + # 14.8 total weight, ~6.8% base chance + - id: FoodCakeApple + orGroup: Cake + - id: FoodCakeBirthday + orGroup: Cake + - id: FoodCakeBlueberry + orGroup: Cake + - id: FoodCakeCarrot + orGroup: Cake + - id: FoodCakeCheese + orGroup: Cake + - id: FoodCakeChocolate + orGroup: Cake + - id: FoodCakeChristmas + orGroup: Cake + - id: FoodCakeClown + orGroup: Cake + - id: FoodCakeLemon + orGroup: Cake + - id: FoodCakeLime + orGroup: Cake + - id: FoodCakeOrange + orGroup: Cake + - id: FoodCakePumpkin + orGroup: Cake + - id: FoodCakeVanilla + orGroup: Cake + # Uncommon + - id: FoodMothMothmallow + orGroup: Cake + prob: 0.5 + - id: FoodCakeSlime + orGroup: Cake + prob: 0.5 + # Rare + - id: FoodCakeBrain + orGroup: Cake + prob: 0.25 + - id: FoodCakeLemoon + orGroup: Cake + prob: 0.25 + - id: FoodCakeSuppermatter + orGroup: Cake + prob: 0.25 + # Ultra rare + - id: FoodCakeSpaceman + orGroup: Cake + prob: 0.05 + - id: KnifePlastic + - id: ForkPlastic + amount: 2 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCosplayWizard + suffix: cosplay-wizard, extended + components: + - type: Mail + contents: + - id: ClothingOuterWizard + - id: ClothingHeadHatWizard + - id: ClothingShoesWizard + - id: PonderingOrb + orGroup: Staff + prob: 0.3 + - id: RGBStaff + orGroup: Staff + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCosplayMaid + suffix: cosplay-maid, extended + components: + - type: Mail + contents: + - id: UniformMaid + orGroup: Uniform + - id: ClothingUniformJumpskirtJanimaid + orGroup: Uniform + - id: ClothingUniformJumpskirtJanimaidmini + orGroup: Uniform + - id: MegaSprayBottle + - id: ClothingHandsGlovesColorWhite + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCosplayNurse + suffix: cosplay-nurse, extended + components: + - type: Mail + contents: + - id: ClothingUniformJumpskirtNurse + - id: ClothingHeadNurseHat + - id: Syringe + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCheese + suffix: cheese, extended + components: + - type: Mail + isFragile: true + isPriority: true + contents: + - id: FoodCheese + orGroup: Cheese + prob: 0.5 + - id: FoodChevre + orGroup: Cheese + prob: 0.5 + - id: KnifePlastic + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCigarettes + suffix: cigs, random + components: + - type: Mail + contents: + - id: CigPackBlack + orGroup: Cigs + prob: 0.19 + - id: CigPackBlue + orGroup: Cigs + prob: 0.19 + - id: CigPackGreen + orGroup: Cigs + prob: 0.19 + - id: CigPackRed + orGroup: Cigs + prob: 0.19 + - id: CigPackMixed + orGroup: Cigs + prob: 0.09 # Pool shared with CigPackMixedMedical, CigPackMixedNasty + - id: CigPackMixedMedical + orGroup: Cigs + prob: 0.05 + - id: CigPackMixedNasty + orGroup: Cigs + prob: 0.05 + # Rare + - id: CigPackSyndicate + orGroup: Cigs + prob: 0.05 + - id: CheapLighter + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFEMP + suffix: emp + components: + - type: Mail + contents: + - id: DelayedEMP + - id: PaperMailNFEMPPreparedness + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSmoke + suffix: smoke + components: + - type: Mail + contents: + - id: DelayedSmoke + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFGoldCigars + suffix: cigars, premium + components: + - type: Mail + contents: + - id: CigarGoldCase + orGroup: Cigars + - id: FlippoLighter + orGroup: Lighter + prob: 0.95 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCookies + suffix: cookies, random + components: + - type: Mail + isPriority: true + contents: + # Cookie 1 + - id: FoodBakedCookie + orGroup: Cookie1 + - id: FoodBakedCookieOatmeal + orGroup: Cookie1 + - id: FoodBakedCookieRaisin + orGroup: Cookie1 + - id: FoodBakedCookieSugar + orGroup: Cookie1 + # Cookie 2 + - id: FoodBakedCookie + orGroup: Cookie2 + - id: FoodBakedCookieOatmeal + orGroup: Cookie2 + - id: FoodBakedCookieRaisin + orGroup: Cookie2 + - id: FoodBakedCookieSugar + orGroup: Cookie2 + # Cookie 3 + - id: FoodBakedCookie + orGroup: Cookie3 + - id: FoodBakedCookieOatmeal + orGroup: Cookie3 + - id: FoodBakedCookieRaisin + orGroup: Cookie3 + - id: FoodBakedCookieSugar + orGroup: Cookie3 + # Cookie 4 + - id: FoodBakedCookie + orGroup: Cookie4 + - id: FoodBakedCookieOatmeal + orGroup: Cookie4 + - id: FoodBakedCookieRaisin + orGroup: Cookie4 + - id: FoodBakedCookieSugar + orGroup: Cookie4 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFKnife + suffix: knife, extended + components: + - type: Mail + contents: + - id: KukriKnife + orGroup: Knife + - id: Machete # A little large for an envelope but "we'll live" + orGroup: Knife + - id: CombatKnife + orGroup: Knife + - id: SurvivalKnife + orGroup: Knife + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFSword + suffix: sword + components: + - type: Mail + contents: + - id: KatanaDulled + orGroup: Sword + prob: 0.95 + - id: ClaymoreDulled + orGroup: Sword + prob: 0.95 + - id: FoamCutlass + orGroup: Sword + prob: 0.95 + - id: Katana + orGroup: Sword + prob: 0.5 + - id: Claymore + orGroup: Sword + prob: 0.5 +# - id: CaneSheathFilled # TODO: don't have this yet +# orGroup: Sword +# prob: 0.3 + - id: Cutlass + orGroup: Sword + prob: 0.1 + - id: Kanabou # ah yes, swords + orGroup: Sword + prob: 0.1 + - id: ClothingBeltSheathFilled # Little dangerous between the reflect and the damage + orGroup: Sword + prob: 0.001 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFMuffins + suffix: muffins, random + components: + - type: Mail + isPriority: true + contents: + # Muffin 1 + - id: FoodBakedMuffinBerry + orGroup: Muffin1 + - id: FoodBakedMuffinCherry + orGroup: Muffin1 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin1 + - id: FoodBakedMuffin + orGroup: Muffin1 + - id: FoodMothMoffin + orGroup: Muffin1 + prob: 0.5 + # Muffin 2 + - id: FoodBakedMuffinBerry + orGroup: Muffin2 + - id: FoodBakedMuffinCherry + orGroup: Muffin2 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin2 + - id: FoodBakedMuffin + orGroup: Muffin2 + - id: FoodMothMoffin + orGroup: Muffin2 + prob: 0.5 + # Muffin 3 + - id: FoodBakedMuffinBerry + orGroup: Muffin3 + - id: FoodBakedMuffinCherry + orGroup: Muffin3 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin3 + - id: FoodBakedMuffin + orGroup: Muffin3 + - id: FoodMothMoffin + orGroup: Muffin3 + prob: 0.5 + # Muffin 4 + - id: FoodBakedMuffinBerry + orGroup: Muffin4 + - id: FoodBakedMuffinCherry + orGroup: Muffin4 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin4 + - id: FoodBakedMuffin + orGroup: Muffin4 + - id: FoodMothMoffin + orGroup: Muffin4 + prob: 0.5 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFPAI + suffix: PAI, extended + components: + - type: Mail + contents: + - id: PersonalAI + orGroup: PAI + prob: 0.99 + # Ultra rare + - id: SyndicatePersonalAI + orGroup: PAI + prob: 0.01 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFPlushie + suffix: plushie, extended + components: + - type: Mail + contents: + # These are all grouped up now to guarantee at least one item received. + # The downside is you're not going to get half a dozen plushies anymore. + - id: PlushieBee + orGroup: Plushie + - id: PlushieRGBee + prob: 0.5 + orGroup: Plushie + - id: PlushieNuke + orGroup: Plushie + - id: PlushieArachind #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieAtmosian #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieXeno #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushiePenguin #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieGhost #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieDiona #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: ToyMouse #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieRouny + orGroup: Plushie + - id: PlushieLizard + orGroup: Plushie + - id: PlushieSpaceLizard + orGroup: Plushie + - id: PlushieRatvar + orGroup: Plushie + - id: PlushieNar + orGroup: Plushie + - id: PlushieCarp + orGroup: Plushie + - id: PlushieHolocarp #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieRainbowCarp #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieSlime + orGroup: Plushie + - id: PlushieSnake + orGroup: Plushie + - id: PlushieMothRandom + orGroup: Plushie + - id: PlushieMoth + prob: 0.5 + orGroup: Plushie + - id: PlushieMothMusician + prob: 0.5 + orGroup: Plushie + - id: PlushieMothBartender + prob: 0.5 + orGroup: Plushie + +# Random snacks, replaces MailChocolate (lousy animal organs) +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSnacks + suffix: snacks, random + components: + - type: Mail + contents: + # Snack 1 + - id: FoodSnackChocolate + orGroup: Snack1 + - id: FoodSnackPopcorn + orGroup: Snack1 + - id: FoodSnackChips + orGroup: Snack1 + - id: FoodSnackBoritos + orGroup: Snack1 + - id: FoodSnackSus + orGroup: Snack1 + - id: FoodSnackPistachios + orGroup: Snack1 + - id: FoodSnackRaisins + orGroup: Snack1 + - id: FoodSnackCheesie + orGroup: Snack1 + - id: FoodSnackEnergy + orGroup: Snack1 + - id: FoodSnackCnDs + orGroup: Snack1 + - id: FoodSnackSemki + orGroup: Snack1 + - id: FoodSnackSyndi + orGroup: Snack1 + prob: 0.5 + # Snack 2 + - id: FoodSnackChocolate + orGroup: Snack2 + - id: FoodSnackPopcorn + orGroup: Snack2 + - id: FoodSnackChips + orGroup: Snack2 + - id: FoodSnackBoritos + orGroup: Snack2 + - id: FoodSnackSus + orGroup: Snack2 + - id: FoodSnackPistachios + orGroup: Snack2 + - id: FoodSnackRaisins + orGroup: Snack2 + - id: FoodSnackCheesie + orGroup: Snack2 + - id: FoodSnackEnergy + orGroup: Snack2 + - id: FoodSnackCnDs + orGroup: Snack2 + - id: FoodSnackSemki + orGroup: Snack2 + - id: FoodSnackSyndi + orGroup: Snack2 + prob: 0.5 + # Snack 3 + - id: FoodSnackChocolate + orGroup: Snack3 + - id: FoodSnackPopcorn + orGroup: Snack3 + - id: FoodSnackChips + orGroup: Snack3 + - id: FoodSnackBoritos + orGroup: Snack3 + - id: FoodSnackSus + orGroup: Snack3 + - id: FoodSnackPistachios + orGroup: Snack3 + - id: FoodSnackRaisins + orGroup: Snack3 + - id: FoodSnackCheesie + orGroup: Snack3 + - id: FoodSnackEnergy + orGroup: Snack3 + - id: FoodSnackCnDs + orGroup: Snack3 + - id: FoodSnackSemki + orGroup: Snack3 + - id: FoodSnackSyndi + orGroup: Snack3 + prob: 0.5 + # Snack 4 + - id: FoodSnackChocolate + orGroup: Snack4 + - id: FoodSnackPopcorn + orGroup: Snack4 + - id: FoodSnackChips + orGroup: Snack4 + - id: FoodSnackBoritos + orGroup: Snack4 + - id: FoodSnackSus + orGroup: Snack4 + - id: FoodSnackPistachios + orGroup: Snack4 + - id: FoodSnackRaisins + orGroup: Snack4 + - id: FoodSnackCheesie + orGroup: Snack4 + - id: FoodSnackEnergy + orGroup: Snack4 + - id: FoodSnackCnDs + orGroup: Snack4 + - id: FoodSnackSemki + orGroup: Snack4 + - id: FoodSnackSyndi + orGroup: Snack4 + prob: 0.5 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFVagueThreat + suffix: vague-threat + components: + - type: Mail + contents: + - id: PaperMailNFVagueThreat1 + orGroup: Paper + - id: PaperMailNFVagueThreat2 + orGroup: Paper + - id: PaperMailNFVagueThreat3 + orGroup: Paper + - id: PaperMailNFVagueThreat4 + orGroup: Paper + - id: PaperMailNFVagueThreat5 + orGroup: Paper + - id: PaperMailNFVagueThreat6 + orGroup: Paper + - id: PaperMailNFVagueThreat7 + orGroup: Paper + - id: PaperMailNFVagueThreat8 + orGroup: Paper + - id: PaperMailNFVagueThreat9 + orGroup: Paper + - id: PaperMailNFVagueThreat10 + orGroup: Paper + - id: PaperMailNFVagueThreat11 + orGroup: Paper + - id: PaperMailNFVagueThreat12 + orGroup: Paper + - id: KitchenKnife + orGroup: ThreateningObject + - id: ButchCleaver + orGroup: ThreateningObject + - id: CombatKnife + orGroup: ThreateningObject + - id: SurvivalKnife + orGroup: ThreateningObject + - id: SoapHomemade + orGroup: ThreateningObject + - id: FoodMeat + orGroup: ThreateningObject + - id: OrganHumanHeart + orGroup: ThreateningObject + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFDonkPockets + suffix: donk pockets, random + components: + - type: Mail + contents: + - id: FoodBoxDonkpocket + orGroup: Donk + prob: 0.4 # Higher probability, useful for chefs. + - id: FoodBoxDonkpocketSpicy + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketTeriyaki + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketPizza + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketStonk + orGroup: Donk + prob: 0.05 + - id: FoodBoxDonkpocketCarp + orGroup: Donk + prob: 0.05 + - id: FoodBoxDonkpocketBerry + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketHonk + orGroup: Donk + prob: 0.05 + - id: FoodBoxDonkpocketDink + orGroup: Donk + prob: 0.05 # Bad luck. + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaPwrGame + suffix: Pwrgame + components: + - type: Mail + contents: + - id: DrinkPwrGameCan + amount: 3 + - id: PaperMailNFPwrGameAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaRedBool + suffix: Red Bool + components: + - type: Mail + contents: + - id: DrinkEnergyDrinkCan + amount: 3 + - id: PaperMailNFRedBoolAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaSpaceCola + suffix: Space Cola + components: + - type: Mail + contents: + - id: DrinkColaBottleFull + - id: PaperMailNFSpaceColaAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaSpaceMountainWind + suffix: Space Mountain Wind + components: + - type: Mail + contents: + - id: DrinkSpaceMountainWindBottleFull + - id: PaperMailNFSpaceMountainWindAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaSpaceUp + suffix: Space Up + components: + - type: Mail + contents: + - id: DrinkSpaceUpBottleFull + - id: PaperMailNFSpaceUpAd + +#TODO: we don't have rainbow joints or blunts (yet?). Uncomment when (if?) they are added. +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFJoints + suffix: joints + components: + - type: Mail + contents: + # Smokeable 1 + - id: Joint + orGroup: Smokeable1 + prob: 0.35 +# - id: JointRainbow +# orGroup: Smokeable1 +# prob: 0.15 +# - id: Blunt +# orGroup: Smokeable1 +# prob: 0.35 +# - id: BluntRainbow +# orGroup: Smokeable1 +# prob: 0.15 + # Smokeable 2 + - id: Joint + orGroup: Smokeable2 + prob: 0.35 +# - id: JointRainbow +# orGroup: Smokeable2 +# prob: 0.15 +# - id: Blunt +# orGroup: Smokeable2 +# prob: 0.35 +# - id: BluntRainbow +# orGroup: Smokeable2 +# prob: 0.15 + # Smokeable 3 + - id: Joint + orGroup: Smokeable3 + prob: 0.35 +# - id: JointRainbow +# orGroup: Smokeable3 +# prob: 0.15 +# - id: Blunt +# orGroup: Smokeable3 +# prob: 0.35 +# - id: BluntRainbow +# orGroup: Smokeable3 +# prob: 0.15 + +# Catchalls for food that only exist in random spawners +# Mmm, mail food +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFUnusualFood + suffix: unusual food + components: + - type: Mail + isPriority: true + isFragile: true + contents: + - id: FoodMealNachos + orGroup: Food + - id: FoodMealNachosCheesy + orGroup: Food + - id: FoodMealNachosCuban + orGroup: Food + - id: FoodMealEggplantParm + orGroup: Food + - id: FoodMealPotatoYaki + orGroup: Food + - id: FoodMealCornedbeef + orGroup: Food + - id: FoodMealBearsteak + orGroup: Food + - id: FoodMealPigblanket + orGroup: Food + - id: FoodMealEggsbenedict + orGroup: Food + - id: FoodMealOmelette + orGroup: Food + - id: FoodMealFriedegg + orGroup: Food + - id: FoodMealMilkape + orGroup: Food + - id: FoodMealMemoryleek + orGroup: Food + - id: DisgustingSweptSoup + orGroup: Food + - id: FoodBreadVolcanic + orGroup: Food + - id: FoodBakedWaffleSoylent + orGroup: Food + - id: FoodBakedWaffleRoffle + orGroup: Food + - id: FoodPieCherry + orGroup: Food + - id: FoodPieFrosty + orGroup: Food + prob: 0.05 + - id: FoodMeatGoliathCooked + amount: 3 + orGroup: Food + - id: FoodMeatRounyCooked + amount: 3 + orGroup: Food + - id: FoodMeatLizardCooked + amount: 3 + orGroup: Food + - id: FoodMeatSpiderlegCooked + amount: 3 + orGroup: Food + - id: FoodMeatMeatballCooked + amount: 4 + orGroup: Food + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFBakedGoods + suffix: baked goods + components: + - type: Mail + isPriority: true + isFragile: true + contents: + - id: FoodBakedBunHoney + amount: 2 + orGroup: Food + - id: FoodBakedBunHotX + amount: 2 + orGroup: Food + - id: FoodBakedBunMeat + amount: 2 + orGroup: Food + - id: FoodBakedPretzel + amount: 2 + orGroup: Food + - id: FoodBakedCannoli + amount: 2 + orGroup: Food + - id: FoodDonutPlain + amount: 2 + orGroup: Food + - id: FoodDonutJellyPlain + amount: 2 + orGroup: Food + - id: FoodDonutHomer + amount: 2 + orGroup: Food + - id: FoodDonutChaos + amount: 2 + orGroup: Food + - id: FoodDonutMeat + amount: 2 + orGroup: Food + - id: FoodDonutPink + amount: 2 + orGroup: Food + - id: FoodDonutSpaceman + amount: 2 + orGroup: Food + - id: FoodDonutApple + amount: 2 + orGroup: Food + - id: FoodDonutCaramel + amount: 2 + orGroup: Food + - id: FoodDonutChocolate + amount: 2 + orGroup: Food +# - id: FoodDonutBluePumpkin # Don't have that yet +# amount: 2 +# orGroup: Food + - id: FoodDonutBungo + amount: 2 + orGroup: Food + - id: FoodDonut + amount: 2 + orGroup: Food + - id: FoodDonutSweetpea + amount: 2 + orGroup: Food + - id: FoodDonutJellyHomer + amount: 2 + orGroup: Food + - id: FoodDonutJellyPink + amount: 2 + orGroup: Food + - id: FoodDonutJellySpaceman + amount: 2 + orGroup: Food + - id: FoodDonutJellyApple + amount: 2 + orGroup: Food + - id: FoodDonutJellyCaramel + amount: 2 + orGroup: Food + - id: FoodDonutJellyChocolate + amount: 2 + orGroup: Food +# - id: FoodDonutJellyBluePumpkin # Don't have that yet +# amount: 2 +# orGroup: Food + - id: FoodDonutJellyBungo + amount: 2 + orGroup: Food + - id: FoodDonutJelly + amount: 2 + orGroup: Food + - id: FoodDonutJellySweetpea + amount: 2 + orGroup: Food + - id: FoodDonutJellySlugcat + amount: 2 + orGroup: Food + - id: FoodFrozenSandwich # ah yes, baked goods + amount: 2 + orGroup: Food + - id: FoodFrozenFreezy + amount: 2 + orGroup: Food + - id: FoodFrozenSundae + amount: 2 + orGroup: Food + - id: FoodFrozenCornuto + amount: 2 + orGroup: Food + - id: FoodFrozenPopsicleOrange + amount: 2 + orGroup: Food + - id: FoodFrozenPopsicleBerry + amount: 2 + orGroup: Food + - id: FoodFrozenPopsicleJumbo + amount: 2 + orGroup: Food + - id: FoodFrozenSnowcone + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeBerry + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeFruit + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeClown + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeMime + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeRainbow + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeMime + amount: 2 + orGroup: Food + - id: FoodMealMint # unlucky + amount: 2 + orGroup: Food + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFUnusualProduce + suffix: unusual produce + components: + - type: Mail + isPriority: true + isFragile: true + contents: + - id: FoodLaughinPeaPod + orGroup: Produce + amount: 5 + - id: FoodMimana + orGroup: Produce + amount: 5 + - id: FoodLemoon + orGroup: Produce + amount: 5 + - id: FoodBlueTomato + orGroup: Produce + amount: 5 + - id: FoodBloodTomato + orGroup: Produce + amount: 5 + - id: FoodKoibean + orGroup: Produce + amount: 5 +# EE does not have those yet. Uncomment if any of those get ported. +# - id: FoodGhostPepper #DeltaV +# orGroup: Produce +# amount: 5 +# - id: FoodCosmicRevenant #DeltaV +# orGroup: Produce +# amount: 5 +# - id: FoodCrystalThistle #DeltaV +# orGroup: Produce +# amount: 5 + - id: FoodLily + orGroup: Produce + amount: 5 + prob: 0.5 + - id: FoodAmbrosiaDeus + orGroup: Produce + amount: 5 + prob: 0.5 + - id: FoodSpacemansTrumpet + orGroup: Produce + amount: 5 + prob: 0.25 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSoaps + suffix: soap sampler + components: + - type: Mail + contents: + - id: BoxSoapsAssorted + - id: PaperMailNTSoapAd1 + orGroup: Ad + - id: PaperMailNTSoapAd2 + orGroup: Ad + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSoapsOmega + suffix: soap sampler, omega + components: + - type: Mail + contents: + - id: BoxSoapsAssortedOmega + - id: PaperMailNTSoapAd1 + orGroup: Ad + - id: PaperMailNTSoapAd2 + orGroup: Ad + +# Could add spessman battle rules here +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFFigurineBulk # DeltaV - No longer Bulk + suffix: figurine, bulk #DeltaV - Spams 3 boxes instead of using the bulk figurine prototype that Frontier uses + components: + - type: Mail + contents: + - id: MysteryFigureBox + - id: MysteryFigureBox + - id: MysteryFigureBox + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFPen + suffix: fancy pen + components: + - type: Mail + contents: + - id: LuxuryPen + orGroup: Pen + prob: 0.50 + # DeltaV: Commenting these two out because I dunno if crew should have nice things + # - id: PenHop + # orGroup: Pen + # prob: 0.25 + # - id: PenCap + # orGroup: Pen + # prob: 0.25 + # TODO: come up with a slightly less powerful version of these + # Ultra-rare + # - id: CyberPen + # orGroup: Pen + # prob: 0.005 + # - id: PenCentcom + # orGroup: Pen + # prob: 0.005 + - id: PaperMailNFPaperPusherAd + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFThrongler + suffix: throngler + components: + - type: Mail + contents: + - id: ThronglerToy + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFInstrumentSmall + suffix: instrument, expanded + components: + - type: Mail + contents: + - id: TrumpetInstrument + orGroup: Instrument + - id: RecorderInstrument + orGroup: Instrument + - id: ClarinetInstrument + orGroup: Instrument + - id: FluteInstrument + orGroup: Instrument + - id: HarmonicaInstrument + orGroup: Instrument + - id: OcarinaInstrument + orGroup: Instrument + - id: PanFluteInstrument + orGroup: Instrument + - id: KalimbaInstrument + orGroup: Instrument + - id: WoodblockInstrument + orGroup: Instrument + - id: BikeHornInstrument + orGroup: Instrument + - id: MusicBoxInstrument + orGroup: Instrument + - id: MicrophoneInstrument + orGroup: Instrument + - id: MusicalLungInstrument + orGroup: Instrument + # Uncommon + - id: PhoneInstrument + orGroup: Instrument + prob: 0.1 + # Rare + - id: BananaPhoneInstrument + orGroup: Instrument + prob: 0.05 + # Ultra-rare + - id: PhoneInstrumentSyndicate + orGroup: Instrument + prob: 0.01 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFUnusualClothing + suffix: unusual clothing + components: + - type: Mail + contents: + - id: ClothingKimonoPink + orGroup: Clothes + - id: ClothingKimonoBlue + orGroup: Clothes + - id: ClothingKimonoPurple + orGroup: Clothes + - id: ClothingKimonoSky + orGroup: Clothes + - id: ClothingKimonoGreen + orGroup: Clothes + - id: ClothingUniformMartialGi + orGroup: Clothes + - id: ClothingNeckBling + orGroup: Clothes + - id: ClothingShoesBling + orGroup: Clothes + - id: ClothingNeckCloakAdmin + orGroup: Clothes + - id: ClothingHeadHatFancyCrown + orGroup: Clothes + - id: ClothingHeadHatCake + orGroup: Clothes + - id: ClothingHeadHatCone + orGroup: Clothes + - id: ClothingMaskOniRed + orGroup: Clothes + - id: ClothingMaskOniBlue + orGroup: Clothes + - id: ClothingHeadHatRichard + orGroup: Clothes + - id: ClothingHeadHatAnimalHeadslime + orGroup: Clothes + - id: ClothingHeadHatDogEars + orGroup: Clothes + - id: ClothingHeadHatCatEars + orGroup: Clothes + - id: ClothingEyesGlassesOutlawGlasses + orGroup: Clothes + - id: ClothingUniformJumpsuitGalaxyBlue + orGroup: Clothes + prob: 0.25 + - id: ClothingUniformJumpsuitGalaxyRed + orGroup: Clothes + prob: 0.25 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCritter + suffix: critter + components: + - type: Mail + isFragile: true + contents: + # Bugs (weight: 2) + - id: MobCockroach + orGroup: Critter + prob: 0.36 + - id: MobSlug + orGroup: Critter + prob: 0.36 + - id: MobArgocyteSlurva # honorary bug? + orGroup: Critter + prob: 0.36 + - id: MobBee + orGroup: Critter + prob: 0.36 + - id: MobButterfly + orGroup: Critter + prob: 0.36 + # Uncommon + - id: MobMothroach + orGroup: Critter + prob: 0.2 + # Small reptiles (weight: 1) + - id: MobLizard + orGroup: Critter + prob: 0.34 + - id: MobSnake + orGroup: Critter + prob: 0.33 + - id: MobFrog + orGroup: Critter + prob: 0.33 + # Small mammals (weight: 1) + - id: MobMouse + orGroup: Critter + prob: 0.33 + - id: MobMouse1 + orGroup: Critter + prob: 0.33 + - id: MobMouse2 + orGroup: Critter + prob: 0.33 + - id: MobMouseCancer + orGroup: Critter + prob: 0.01 # Rare + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFTacticalMaid + suffix: tactical maid + components: + - type: Mail + contents: + - id: ClothingUniformJumpskirtTacticalMaid + - id: ClothingHeadHatTacticalMaidHeadband + - id: MegaSprayBottle + - id: ClothingHandsTacticalMaidGloves + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFKendoKit + suffix: kendo kit + components: + - type: Mail + contents: # A lot of stuff here, seems spammy. + - id: ClothingUniformKendoHakama + amount: 2 + - id: ClothingOuterArmorKendoBogu + amount: 2 + - id: ClothingHeadHelmetKendoMen + amount: 2 + - id: Shinai + amount: 2 + +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailSake + suffix: osake + components: + - type: Mail + contents: + - id: DrinkSakeCup + amount: 2 + - id: DrinkTokkuri + +- type: entity + noSpawn: true + parent: BaseMail + id: MailBlockGameDIY + suffix: blockgamediy + components: + - type: Mail + contents: + - id: BlockGameArcadeComputerCircuitboard + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCigars + suffix: Cigars + components: + - type: Mail + contents: + - id: CigarCase + - id: Lighter + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCrayon + suffix: Crayon + components: + - type: Mail + contents: + - id: CrayonBox + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCosplayArc + suffix: cosplay-arc + components: + - type: Mail + openSound: /Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg + contents: + - id: ClothingCostumeArcDress + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCosplayGeisha + suffix: cosplay-geisha + components: + - type: Mail + contents: + - id: UniformGeisha + - id: DrinkTeapot + - id: DrinkTeacup + amount: 3 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCosplaySchoolgirl + suffix: cosplay-schoolgirl + components: + - type: Mail + contents: + - id: UniformSchoolgirlBlack + orGroup: Color + - id: UniformSchoolgirlBlue + orGroup: Color + - id: UniformSchoolgirlCyan + orGroup: Color + - id: UniformSchoolgirlGreen + orGroup: Color + - id: UniformSchoolgirlOrange + orGroup: Color + - id: UniformSchoolgirlPink + orGroup: Color + - id: UniformSchoolgirlPurple + orGroup: Color + - id: UniformSchoolgirlRed + orGroup: Color + +- type: entity + noSpawn: true + parent: BaseMail + id: MailFlowers + suffix: flowers + components: + - type: Mail + contents: + # TODO actual flowers + - id: ClothingHeadHatFlowerWreath + orGroup: Flowers + - id: FoodPoppy + orGroup: Flowers + - id: FoodLily + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSignallerKit + suffix: signallerkit + components: + - type: Mail + contents: + - id: Multitool + - id: RemoteSignaller + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNoir + suffix: noir + components: + - type: Mail + contents: + - id: ClothingUniformJumpsuitDetectiveGrey + - id: ClothingUniformJumpskirtDetectiveGrey + - id: ClothingHeadHatBowlerHat + - id: ClothingOuterCoatGentle + +- type: entity + noSpawn: true + parent: BaseMail + id: MailRestraints + suffix: restraints + components: + - type: Mail + contents: + - id: Handcuffs + - id: ClothingMaskMuzzle + - id: ClothingEyesBlindfold + +- type: entity + noSpawn: true + parent: BaseMail + id: MailFishingCap + suffix: fishingcap + components: + - type: Mail + contents: + - id: ClothingHeadFishCap + +- type: entity + noSpawn: true + parent: BaseMail + id: MailFlashlight + suffix: Flashlight + components: + - type: Mail + contents: + - id: FlashlightLantern + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSpaceVillainDIY + suffix: spacevilliandiy + components: + - type: Mail + contents: + - id: SpaceVillainArcadeComputerCircuitboard + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSunglasses + suffix: Sunglasses + components: + - type: Mail + contents: + - id: ClothingEyesGlassesSunglasses + +- type: entity + noSpawn: true + parent: BaseMail + id: MailWinterCoat + suffix: wintercoat + components: + - type: Mail + contents: + - id: ClothingOuterWinterCoat + orGroup: Coat + - id: ClothingOuterWinterCoatLong + orGroup: Coat + - id: ClothingOuterWinterCoatPlaid + orGroup: Coat diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_civilian.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_civilian.yml similarity index 71% rename from Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_civilian.yml rename to Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_civilian.yml index a41fac14ffa..19a1ee3c536 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_civilian.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_civilian.yml @@ -1,8 +1,62 @@ +# Frontier Mail +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFInstrumentLarge + suffix: instrument, large + components: + - type: Mail + contents: + - id: TromboneInstrument + orGroup: Instrument + - id: FrenchHornInstrument + orGroup: Instrument + - id: SaxophoneInstrument + orGroup: Instrument + - id: EuphoniumInstrument + orGroup: Instrument + - id: AcousticGuitarInstrument + orGroup: Instrument + - id: ElectricGuitarInstrument + orGroup: Instrument + - id: BassGuitarInstrument + orGroup: Instrument + - id: RockGuitarInstrument + orGroup: Instrument + - id: BanjoInstrument + orGroup: Instrument + - id: ViolinInstrument + orGroup: Instrument + - id: CelloInstrument + orGroup: Instrument + - id: ViolaInstrument + orGroup: Instrument + - id: BagpipeInstrument # Fury. + orGroup: Instrument + - id: SynthesizerInstrument + orGroup: Instrument + - id: AccordionInstrument + orGroup: Instrument + - id: GlockenspielInstrument + orGroup: Instrument + - id: XylophoneInstrument + orGroup: Instrument + # Uncommon + - id: Rickenbacker4003Instrument + orGroup: Instrument + prob: 0.25 + # Rare + - id: Rickenbacker4001Instrument + orGroup: Instrument + prob: 0.1 + +# Base Nyano Mail + - type: entity noSpawn: true parent: BaseMail id: MailBotanistChemicalBottles - suffix: botanistchemicals + suffix: botanist chemicals components: - type: Mail contents: @@ -102,7 +156,7 @@ noSpawn: true parent: BaseMail id: MailHoPBureaucracy - suffix: hoppaper + suffix: hop paper components: - type: Mail contents: @@ -113,7 +167,7 @@ noSpawn: true parent: BaseMail id: MailHoPSupplement - suffix: hopsupplement + suffix: hop supplement components: - type: Mail contents: @@ -125,7 +179,7 @@ noSpawn: true parent: BaseMail id: MailMimeArtsCrafts - suffix: artscrafts + suffix: arts and crafts components: - type: Mail contents: @@ -137,7 +191,7 @@ noSpawn: true parent: BaseMail id: MailMimeBlankBook - suffix: blankbook + suffix: blank book components: - type: Mail contents: @@ -147,37 +201,17 @@ noSpawn: true parent: BaseMail id: MailMimeBottleOfNothing - suffix: bottleofnothing + suffix: bottle of nothing components: - type: Mail contents: - id: DrinkBottleOfNothingFull -- type: entity - noSpawn: true - parent: BaseMail - id: MailMusicianInstrumentSmall - suffix: instrument-small - components: - - type: Mail - isFragile: true - contents: - - id: FluteInstrument - orGroup: Instrument - - id: HarmonicaInstrument - orGroup: Instrument - - id: OcarinaInstrument - orGroup: Instrument - - id: PanFluteInstrument - orGroup: Instrument - - id: RecorderInstrument - orGroup: Instrument - - type: entity noSpawn: true parent: BaseMail id: MailPassengerMoney - suffix: passengermoney + suffix: passenger money components: - type: Mail contents: diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_command.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_command.yml new file mode 100644 index 00000000000..86dec46a654 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_command.yml @@ -0,0 +1,35 @@ +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailCommandPinpointerNuclear + suffix: pinpointer mail ops + components: + - type: Mail + contents: + - id: PinpointerNuclear + +- type: entity + noSpawn: true + parent: BaseMail + id: MailStationRepNFNukeDisk + suffix: nuke disk + components: + - type: Mail + isFragile: true + contents: + - id: NukeDiskFake + - id: PaperMailNFAntivirus + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCommandNFPipebombIntern + suffix: pipe and bomb + components: + - type: Mail + isFragile: true + contents: + - id: SmokingPipeFilledTobacco + - id: DrinkAtomicBombGlass + - id: PaperMailNFPipebombIntern diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_engineering.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_engineering.yml new file mode 100644 index 00000000000..af70fc621cb --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_engineering.yml @@ -0,0 +1,182 @@ +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringCables + suffix: cables + components: + - type: Mail + contents: + - id: CableHVStack + orGroup: Cables + - id: CableMVStack + orGroup: Cables + - id: CableApcStack + orGroup: Cables + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringKudzuDeterrent + suffix: antikudzu + components: + - type: Mail + contents: + - id: PlantBGoneSpray + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringSheetGlass + suffix: sheetglass + components: + - type: Mail + contents: + - id: SheetGlass + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringWelderReplacement + suffix: welder + components: + - type: Mail + contents: + - id: Welder + +# Frontier Mail + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCircuitboardIndustrial + suffix: industrial circuitboard + components: + - type: Mail + contents: + - id: ThermomachineFreezerMachineCircuitBoard + orGroup: Board + prob: 0.5 + - id: ThermomachineHeaterMachineCircuitBoard + orGroup: Board + prob: 0.5 + - id: HellfireFreezerMachineCircuitBoard + orGroup: Board + prob: 0.25 + - id: HellfireHeaterMachineCircuitBoard + orGroup: Board + prob: 0.25 + - id: CryoPodMachineCircuitboard # Medical as well + orGroup: Board + prob: 0.5 + - id: ChemMasterMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: ChemDispenserMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: StasisBedMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: BiomassReclaimerMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: BiofabricatorMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: TurboItemRechargerCircuitboard + orGroup: Board + prob: 0.5 + - id: AutolatheHyperConvectionMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: ProtolatheHyperConvectionMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: HotplateMachineCircuitboard + orGroup: Board + prob: 0.5 +# - id: CircuitImprinterHyperConvectionMachineCircuitboard +# orGroup: Board +# prob: 0.25 + - id: SheetifierMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: RadarConsoleCircuitboard + orGroup: Board + prob: 0.25 + - id: OreProcessorIndustrialMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: GasRecyclerMachineCircuitboard + orGroup: Board + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCircuitboardService + suffix: service circuitboard + components: + - type: Mail + contents: + - id: ComputerTelevisionCircuitboard + orGroup: Board + - id: ReagentGrinderMachineCircuitboard + orGroup: Board + - id: ReagentGrinderIndustrialMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: SurveillanceWirelessCameraMovableCircuitboard + orGroup: Board + prob: 0.5 + - id: MicrowaveMachineCircuitboard + orGroup: Board + - id: ElectricGrillMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: FatExtractorMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: SeedExtractorMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: BoozeDispenserMachineCircuitboard + orGroup: Board + - id: SodaDispenserMachineCircuitboard + orGroup: Board + - id: JukeboxCircuitBoard + orGroup: Board + - id: TelecomServerCircuitboard + orGroup: Board + prob: 0.25 + - id: ComputerMassMediaCircuitboard + orGroup: Board + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFPowerTool + suffix: power tool + components: + - type: Mail + contents: + - id: PaperMailNFPowerTool + orGroup: Paper + - id: JawsOfLife + orGroup: Gift + prob: 0.33 + - id: PowerDrill + orGroup: Gift + prob: 0.33 + - id: WelderIndustrial + orGroup: Gift + prob: 0.20 + # Rare + - id: WelderIndustrialAdvanced + orGroup: Gift + prob: 0.10 + - id: WelderExperimental + orGroup: Gift + prob: 0.04 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_epistemology.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_epistemology.yml similarity index 74% rename from Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_epistemology.yml rename to Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_epistemology.yml index 38526966b80..be6f9818e22 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_epistemology.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_epistemology.yml @@ -46,13 +46,3 @@ - type: Mail contents: - id: ClothingHeadTinfoil - -- type: entity - noSpawn: true - parent: BaseMail - id: MailDetectiveForensicSupplement # Deltav - Detective is in charge of investigating crimes. - suffix: detectivesupplement # Deltav - Detective is in charge of investigating crimes. - components: - - type: Mail - contents: - - id: BoxForensicPad diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_medical.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_medical.yml similarity index 84% rename from Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_medical.yml rename to Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_medical.yml index 4e797272e5e..735f840a201 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_medical.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_medical.yml @@ -1,3 +1,4 @@ +# Base Nyano Mail - type: entity noSpawn: true parent: BaseMail @@ -91,3 +92,19 @@ maxAmount: 2 - id: SyringeTranexamicAcid maxAmount: 2 + +# Frontier Mail +- type: entity + parent: BaseMailLarge + id: MailNFMedkit + suffix: medkit + components: + - type: Mail + contents: + - id: MedkitAdvancedFilled + orGroup: Medkit + prob: 0.75 + - id: MedkitCombatFilled + orGroup: Medkit + prob: 0.25 + diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_security.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_security.yml similarity index 50% rename from Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_security.yml rename to Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_security.yml index b47d5af56e3..eed846a0909 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_security.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_security.yml @@ -1,3 +1,4 @@ +# Base Nyano Mail - type: entity noSpawn: true parent: BaseMail @@ -34,7 +35,6 @@ maxAmount: 2 #- type: entity -# noSpawn: true # parent: BaseMail # id: MailSecuritySpaceLaw # suffix: spacelaw @@ -54,3 +54,42 @@ contents: - id: BoxBeanbag +- type: entity + noSpawn: true + parent: BaseMail + id: MailDetectiveForensicSupplement # Deltav - Detective is in charge of investigating crimes. + suffix: detectivesupplement # Deltav - Detective is in charge of investigating crimes. + components: + - type: Mail + contents: + - id: BoxForensicPad + +# Frontier Mail + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailSecurityNFMusket + suffix: musket + components: + - type: Mail + contents: + - id: ClothingHeadHatPwig + - id: Musket + - id: CartridgeAntiMateriel + amount: 2 + - id: PaperMailNTMusket + +# Delta Mail + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSecurityDVSpaceLaw + suffix: spacelaw, extended + components: + - type: Mail + contents: + - id: BookSecurity +# - id: BookSOP #This is where I'd put my Delta SOP book... IF I HAD ONE!!! + - id: PaperMailNFSpaceLaw # Uses the NF space law paper, which is edited to mention the Delta Sector diff --git a/Resources/Prototypes/DeltaV/Mail/mailDeliveries.yml b/Resources/Prototypes/DeltaV/Mail/mailDeliveries.yml new file mode 100644 index 00000000000..c0d6df9ec21 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Mail/mailDeliveries.yml @@ -0,0 +1,147 @@ +- type: mailDeliveryPool + id: RandomDeltaVMailDeliveryPool #This entire table is messy as fuck but the weights add up to 35. TODO: ORGANIZE THIS SHIT AAAA + everyone: + MailNFAlcohol: 1 + MailNFBakedGoods: 1 + MailNFBible: 1 + MailNFBikeHorn: 0.5 + MailBooksAll: 1 + MailBlockGameDIY: 0.5 + MailNFBuildABuddy: 0.3 + MailDVBoxes: 0.3 + MailCrayon: 1 + MailNFCake: 1 + MailNFCheese: 1 + MailNFCookies: 1.1 + MailNFCritter: 1 + #Cigarettes - Adds up to 1 in weight + MailNFCigarettes: 0.5 + MailCigars: 0.2 + MailNFJoints: 0.2 + MailNFGoldCigars: 0.1 + #Cosplay - Adds up to 3.4 in weight + MailCosplayArc: 0.5 + MailDVCosplayFakeWizard: 0.5 + MailNFCosplayWizard: 0.5 + MailNFCosplayMaid: 0.5 + MailCosplayGeisha: 0.5 + MailCosplaySchoolgirl: 0.5 + MailNFCosplayNurse: 0.4 + MailNFDonkPockets: 0.5 + MailNFEMP: 0.3 + MailNFFigurineBulk: 1 + MailFishingCap: 0.5 + MailFlashlight: 1 + MailFlowers: 1 + MailNFKendoKit: 0.3 + MailNFKnife: 0.7 + MailNFMuffins: 1 + MailNoir: 0.5 + MailNFPAI: 1.2 + MailNFPlushie: 1 + MailPumpkinPie: 0.3 + MailNFPen: 0.7 + MailRestraints: 0.8 + MailSake: 0.4 + MailDVScarves: 0.15 + MailNFSnacks: 1 + #Soda - Adds up to 1 in weight + MailNFSodaPwrGame: 0.2 + MailNFSodaRedBool: 0.2 + MailNFSodaSpaceCola: 0.2 + MailNFSodaSpaceMountainWind: 0.2 + MailNFSodaSpaceUp: 0.2 + #End Soda + MailNFSmoke: 0.4 + MailSpaceVillainDIY: 0.5 + MailSignallerKit: 0.5 + MailSunglasses: 1 + MailNFSoaps: 0.5 + MailNFSoapsOmega: 0.5 + MailNFSword: 0.5 + MailNFTacticalMaid: 0.5 + MailNFThrongler: 0.05 + MailNFUnusualClothing: 0.5 + MailNFUnusualFood: 1 + MailNFUnusualProduce: 1 + MailNFVagueThreat: 0.5 + # Mainly for Glacier + MailWinterCoat: 1.5 + + # Department and job-specific mail can have slightly higher weights, + # since they'll be merged with the everyone pool. + departments: + Medical: + MailMedicalBasicSupplies: 2 + MailMedicalChemistrySupplement: 2 + MailMedicalEmergencyPens: 3 + MailMedicalMedicinePills: 2 + MailMedicalSheetPlasma: 1 + # MailMedicalSpaceacillin: 1 + MailMedicalStabilizers: 2 + MailNFMedkit: 2 + Engineering: + MailAMEGuide: 1 + MailEngineeringCables: 2 + MailEngineeringKudzuDeterrent: 2 + MailEngineeringSheetGlass: 2 + MailEngineeringWelderReplacement: 2 + MailNFCircuitboardIndustrial: 2 + MailNFCircuitboardService: 1 + MailNFPowerTool: 1 + Security: + MailSecurityDonuts: 3 + MailSecurityFlashlight: 2 + MailSecurityNonlethalsKit: 2 + MailSecurityDVSpaceLaw: 1 + MailSecurityNFMusket: 1 + Epistemics: +# MailBooks: 1 + MailEpistemologyBluespace: 1 + MailEpistemologyIngotGold: 2 + MailEpistemologyResearchDisk: 1 + MailEpistemologyTinfoilHat: 1 + MailSignallerKit: 1 + # All heads of staff are in Command and not their departments, technically. + # So any items from the departments above that should also be sent to the + # respective department heads should be duplicated below. + Command: + MailCommandPinpointerNuclear: 0.5 + MailStationRepNFNukeDisk: 0.3 + MailCommandNFPipebombIntern: 0.1 + + jobs: + Botanist: + MailBotanistChemicalBottles: 2 + MailBotanistMutagen: 1.5 + MailBotanistSeeds: 1 + ChiefEngineer: + MailEngineeringKudzuDeterrent: 2 + ChiefMedicalOfficer: + MailMedicalEmergencyPens: 2 + MailMedicalMedicinePills: 3 + MailMedicalSheetPlasma: 2 + Clown: + MailClownGildedBikeHorn: 0.5 + MailClownHonkSupplement: 3 + Detective: # Deltav - Detective is in charge of investigating crimes. + MailDetectiveForensicSupplement: 2 # Deltav - Detective is in charge of investigating crimes. + HeadOfPersonnel: + MailHoPBureaucracy: 2 + MailHoPSupplement: 3 + HeadOfSecurity: + MailSecurityNonlethalsKit: 2 + Lawyer: + MailSecurityDVSpaceLaw: 2 + Mime: + MailMimeArtsCrafts: 3 + MailMimeBlankBook: 2 + MailMimeBottleOfNothing: 1 + ResearchDirector: # DeltaV - Epistemics Department replacing Science but keeping their IDs + MailEpistemologyIngotGold: 2 + Musician: + MailMusicianInstrumentSmall: 1 + Passenger: + MailPassengerMoney: 3 + Warden: + MailWardenCrowdControl: 2 diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 1a388997835..ebefd05cbb6 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -358,6 +358,12 @@ accentVColor: "#a23e3e" - type: Icon state: pda-qm + - type: CartridgeLoader # Adds the MailMetrics courier tracker + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - MailMetricsCartridge - type: entity parent: BasePDA diff --git a/Resources/Prototypes/Entities/Objects/Misc/mail_capsule.yml b/Resources/Prototypes/Entities/Objects/Misc/mail_capsule.yml new file mode 100644 index 00000000000..bd6c9057cd0 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/mail_capsule.yml @@ -0,0 +1,139 @@ +- type: entity + name: mail capsule + suffix: Primed + id: MailCapsulePrimed + parent: BaseItem + components: + - type: ThrowingAngle + angle: 180 + - type: EmbeddableProjectile + minimumSpeed: 1 + removalTime: 0.1 + - type: Tag + tags: + - MailCapsule + - Trash + - type: Sprite + sprite: Objects/Misc/mail_capsule.rsi + layers: + - state: icon-empty + - type: ItemSlots + slots: + mail_slot: + insertVerbText: Put in Mail + ejectVerbText: Take out Mail + name: Mail + startingItem: null + whitelist: + tags: + - Book + - Document + - Mail + components: + - Mail + - Paper + - HyperlinkBook + insertOnInteract: true + priority: 3 + food_slot: + insertVerbText: Put in Food + ejectVerbText: Take out Food + name: Food + startingItem: null + whitelist: + components: + - Food + insertOnInteract: true + priority: 2 + cash_slot: + insertVerbText: Put in Cash + ejectVerbText: Take out Cash + name: Cash + startingItem: null + whitelist: + components: + - Currency + insertOnInteract: true + priority: 1 + - type: ContainerContainer + containers: + storagebase: !type:Container + showEnts: False + occludes: true + ents: [] + mail_slot: !type:ContainerSlot + showEnts: False + occludes: true + ent: null + food_slot: !type:ContainerSlot + showEnts: False + occludes: true + ent: null + cash_slot: !type:ContainerSlot + showEnts: False + occludes: true + ent: null + - type: Appearance + - type: ItemMapper + mapLayers: + icon-food: + whitelist: + components: + - Food + icon-cash: + whitelist: + components: + - Currency + icon-mail: + whitelist: + tags: + - Book + - Document + - Mail + components: + - Mail + - Paper + - HyperlinkBook + sprite: Objects/Misc/mail_capsule.rsi + - type: Dumpable + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 20 #excess damage avoids cost of spawning entities. + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - trigger: + !type:DamageTrigger + damage: 10 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: GlassBreak + - !type:EmptyAllContainersBehaviour + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: DamageOnLand + damage: + types: + Blunt: 9.5 + +- type: entity + name: mail capsule box + parent: BoxCardboard + id: BoxMailCapsulePrimed + description: A box of primed mail capsules. + components: + - type: Storage + grid: + - 0,0,4,3 + - type: StorageFill + contents: + - id: MailCapsulePrimed + amount: 10 + - type: Sprite + layers: + - state: box diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/boxes.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/boxes.yml new file mode 100644 index 00000000000..536736fc904 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/boxes.yml @@ -0,0 +1,212 @@ +# Mail-only boxes. If/when something uses these outside of the mail, move the entry into Catalog/Fills. + +- type: entity + name: scented soap sampler pack + parent: BoxCardboard + id: BoxSoapsAssorted + description: A box of various scented soaps. Ooh, lavender. + components: + - type: StorageFill + contents: + - id: SoapNT + amount: 1 + - id: Soap + amount: 1 + - id: SoapHomemade + amount: 1 + - id: SoapDeluxe + amount: 1 + - type: Storage + maxItemSize: Normal + grid: + - 0,0,3,1 + whitelist: + tags: + - Soap + - type: Sprite + layers: + - state: box + +- type: entity + name: scented soap sampler pack + parent: BoxCardboard + id: BoxSoapsAssortedOmega + description: A box of various scented soaps. Ooh, bluespace. + components: + - type: StorageFill + contents: + - id: SoapNT + amount: 1 + - id: Soap + amount: 1 + - id: SoapOmega + amount: 1 + - id: SoapDeluxe + amount: 1 + - type: Storage + maxItemSize: Normal + grid: + - 0,0,3,1 + whitelist: + tags: + - Soap + - type: Sprite + layers: + - state: box + +- type: entity + name: Build-a-Buddy kit + suffix: Human + parent: BoxHug + id: BoxBuildABuddyHuman + description: "\"Henry the Human\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadHuman + amount: 1 + - id: TorsoHuman + amount: 1 + - id: LeftArmHuman + amount: 1 + - id: RightArmHuman + amount: 1 + - id: LeftHandHuman + amount: 1 + - id: RightHandHuman + amount: 1 + - id: LeftLegHuman + amount: 1 + - id: RightLegHuman + amount: 1 + - id: LeftFootHuman + amount: 1 + - id: RightFootHuman + amount: 1 + - type: Storage + grid: + - 0,0,4,3 + whitelist: + components: + - BodyPart + +# DeltaV - Goblins Aren't Real +#- type: entity +# name: Build-a-Buddy kit +# suffix: Goblin +# parent: BoxBuildABuddyHuman +# id: BoxBuildABuddyGoblin +# description: "\"Greta the Goblin\" Build-a-Buddy kit. Some assembly required." +# components: +# - type: StorageFill +# contents: +# - id: HeadGoblin +# amount: 1 +# - id: TorsoGoblin +# amount: 1 +# - id: LeftArmGoblin +# amount: 1 +# - id: RightArmGoblin +# amount: 1 +# - id: LeftHandGoblin +# amount: 1 +# - id: RightHandGoblin +# amount: 1 +# - id: LeftLegGoblin +# amount: 1 +# - id: RightLegGoblin +# amount: 1 +# - id: LeftFootGoblin +# amount: 1 +# - id: RightFootGoblin +# amount: 1 + +- type: entity + name: Build-a-Buddy kit + suffix: Reptilian + parent: BoxBuildABuddyHuman + id: BoxBuildABuddyReptilian + description: "\"Randy the Reptilian\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadReptilian + amount: 1 + - id: TorsoReptilian + amount: 1 + - id: LeftArmReptilian + amount: 1 + - id: RightArmReptilian + amount: 1 + - id: LeftHandReptilian + amount: 1 + - id: RightHandReptilian + amount: 1 + - id: LeftLegReptilian + amount: 1 + - id: RightLegReptilian + amount: 1 + - id: LeftFootReptilian + amount: 1 + - id: RightFootReptilian + amount: 1 + +- type: entity + name: Build-a-Buddy kit + suffix: Slime + parent: BoxBuildABuddyHuman + id: BoxBuildABuddySlime + description: "\"Steven the Slime\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadSlime + amount: 1 + - id: TorsoSlime + amount: 1 + - id: LeftArmSlime + amount: 1 + - id: RightArmSlime + amount: 1 + - id: LeftHandSlime + amount: 1 + - id: RightHandSlime + amount: 1 + - id: LeftLegSlime + amount: 1 + - id: RightLegSlime + amount: 1 + - id: LeftFootSlime + amount: 1 + - id: RightFootSlime + amount: 1 + +- type: entity + name: Build-a-Buddy kit + suffix: Vulpkanin + parent: BoxBuildABuddyHuman + id: BoxBuildABuddyVulpkanin + description: "\"Valerie the Vulpkanin\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadVulpkanin + amount: 1 + - id: TorsoVulpkanin + amount: 1 + - id: LeftArmVulpkanin + amount: 1 + - id: RightArmVulpkanin + amount: 1 + - id: LeftHandVulpkanin + amount: 1 + - id: RightHandVulpkanin + amount: 1 + - id: LeftLegVulpkanin + amount: 1 + - id: RightLegVulpkanin + amount: 1 + - id: LeftFootVulpkanin + amount: 1 + - id: RightFootVulpkanin + amount: 1 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/misc.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/misc.yml new file mode 100644 index 00000000000..6f1c86e00ba --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/misc.yml @@ -0,0 +1,117 @@ +# Mail-only items. If/when these get used for anything else, please move them to another folder. +# Pranks: admin items or effects put into an envelope, released when opened or damaged. +- type: entity + id: DelayedSmoke + parent: BaseItem + noSpawn: true + name: delayed smoke + suffix: "(10s)" + components: + - type: Sprite #DeltaV: Apparently these want sprites, probably because they're baseitems + sprite: /Textures/Objects/Fun/goldbikehorn.rsi + visible: false + state: icon + - type: DelayedItem + item: AdminInstantEffectSmoke10 + +- type: entity + id: AdminInstantEffectEMP7 + noSpawn: true + suffix: EMP, 7 meters + parent: AdminInstantEffectBase + components: + - type: EmpOnTrigger + range: 7 + energyConsumption: 50000 + +- type: entity + id: DelayedEMP + parent: BaseItem + noSpawn: true + name: delayed EMP (7 meters) + components: + - type: Sprite #DeltaV: Apparently these want sprites, probably because they're baseitems + sprite: /Textures/Objects/Fun/goldbikehorn.rsi + visible: false + state: icon + - type: DelayedItem + item: AdminInstantEffectEMP7 + +# Miscellaneous Items + +- type: entity + id: SyringeCognizine + parent: Syringe + name: cognizine syringe + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 15 + reagents: + - ReagentId: Cognizine + Quantity: 15 # Surely three friends is enough. + +- type: entity + id: SyringeOpporozidone + parent: Syringe + name: opporozidone syringe + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 15 +# reagents: # TODO: we don't have that yet. Guess the people will receive an empty syringe instead. +# - ReagentId: Opporozidone +# Quantity: 15 + +- type: entity + id: NecrosolChemistryBottle + parent: BaseChemistryBottleFilled + name: necrosol bottle + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: Necrosol + Quantity: 30 + +# Premium Alcohol: wait, it's just marketing? +# TODO: different sprites would be nice. +- type: entity + id: DrinkPremiumVodkaBottleFull + parent: DrinkVodkaBottleFull + name: Moment of Clarity vodka bottle + description: When things get a bit hectic, all you need is a Moment of Clarity. + +- type: entity + id: DrinkPremiumGinBottleFull + parent: DrinkGinBottleFull + name: Harry's gin bottle + description: An interesting set of botanicals, for sure. Is that pumpkin? + +- type: entity + id: DrinkPremiumTequilaBottleFull + parent: DrinkTequilaBottleFull + name: Casa del Eorg tequila bottle + description: Save the best for last. Casa del Eorg, 100% agave. + +- type: entity + id: DrinkPremiumWhiskeyBottleFull + parent: DrinkWhiskeyBottleFull + name: Ol' Prowler 18 whiskey bottle + description: Surprisingly smooth, it has a nasty habit of sneaking up on you. + +- type: entity + id: DrinkPremiumRumBottleFull + parent: DrinkRumBottleFull + name: Redeemer's Bounty rum bottle + description: Well, you asked for it. Navy strength. + +- type: entity + id: DrinkPremiumAbsintheBottleFull + parent: DrinkAbsintheBottleFull + name: Bureaucracy's Kiss absinthe bottle + description: A refined taste that tends to linger. diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/paper.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/paper.yml new file mode 100644 index 00000000000..b21ca407173 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/paper.yml @@ -0,0 +1,483 @@ +# Papers (letters, ad copy) +# TODO: these should really be based on localization strings. +- type: entity + id: PaperMailNFPowerTool + name: Hazard Fraught advertisement + categories: [ HideSpawnMenu ] + suffix: "power tool ad, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]Hazard Fraught Tools[/head] + + [head=2]Discount Tools at Quality Prices![/head] + + [head=2]Fax us for a catalog at + [color=#990000]ERROR: UNEXPECTED EOF[/color][/head] + +- type: entity + id: PaperMailNFVagueThreat1 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 1, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]I know what you did.[/head] + + [head=3]You don't know what I'm going to do to you.[/head] + +- type: entity + id: PaperMailNFVagueThreat2 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 2, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]I'm coming for you.[/head] + +- type: entity + id: PaperMailNFVagueThreat3 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 3, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]You're next.[/head] + +- type: entity + id: PaperMailNFVagueThreat4 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 4, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]We see you.[/head] + +- type: entity + id: PaperMailNFVagueThreat5 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 5, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]I hope your affairs are in order.[/head] + +- type: entity + id: PaperMailNFVagueThreat6 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 6, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]It's only a matter of time.[/head] + + + [head=1]Enjoy it while it lasts.[/head] + +- type: entity + id: PaperMailNFVagueThreat7 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 7, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Who should we mail your pieces to?[/head] + +- type: entity + id: PaperMailNFVagueThreat8 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 8, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Would you prefer to die slowly or quickly? + [/head] + [head=1]Just kidding.[/head] + + [head=2]We don't care what you think.[/head] + +- type: entity + id: PaperMailNFVagueThreat9 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 9, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]I think your head would look nice on my mantel.[/head] + +- type: entity + id: PaperMailNFVagueThreat10 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 10, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]You should have paid up.[/head] + + + [head=1]It's too late now.[/head] + +- type: entity + id: PaperMailNFVagueThreat11 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 11, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]Your family will miss you, but don't worry.[/head] + + + [head=1]We'll take care of them too.[/head] + +- type: entity + id: PaperMailNFVagueThreat12 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 12, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]I have a bet that you're going to die today.[/head] + + + [head=1]I'm not afraid to cheat.[/head] + +- type: entity + id: PaperMailNFPwrGameAd + name: pwr game advertisement + categories: [ HideSpawnMenu ] + suffix: "pwr game ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]Drink Pwr Game![/head] + + [head=3]Proud sponsor of the NT Block Game Championship.[/head] + +- type: entity + id: PaperMailNFRedBoolAd + name: red bool advertisement + categories: [ HideSpawnMenu ] + suffix: "red bool ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Try NEW Reformulated Red Bool![/head] + + [head=2]Over [color=#dd0000]1.5g[/color] of caffeine per can![/head] + + [head=2]Punch your heart into overdrive![/head] + +- type: entity + id: PaperMailNFSpaceColaAd + name: space cola advertisement + categories: [ HideSpawnMenu ] + suffix: "space cola ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]The classic taste you love, Space Cola.[/head] + + [head=2]Now certified lead-free.[/head] + +- type: entity + id: PaperMailNFSpaceMountainWindAd + name: space mountain wind advertisement + categories: [ HideSpawnMenu ] + suffix: "space mountain wind ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]When it's time to game, there's one choice:[/head] + + [head=1]Space Mountain Wind.[/head] + +- type: entity + id: PaperMailNFSpaceUpAd + name: space up advertisement + categories: [ HideSpawnMenu ] + suffix: "space up ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]The crisp, refreshing taste of lemon and lime.[/head] + + + [head=1]Space Up![/head] + + + [head=2]Ask your barkeep for a Sui Dream today![/head] + +- type: entity + id: PaperMailNTSoapAd1 + categories: [ HideSpawnMenu ] + suffix: "soap ad 1" + parent: Paper + components: + - type: Paper + stampedBy: + - stampedColor: '#333333FF' + stampedName: Christopher Cleanman +# stampType: Signature #DeltaV - Not compatible with our signatures code stuff apparently + content: |2 + [head=3]Hello Valued Customer,[/head] + You have been selected to receive a complimentary sampler of scented soaps that Nanotrasen has to offer. + + Why not enjoy a nice warm shower with our scented soaps? Tested and effective vs. vent crud and mold. + + We hope you enjoy. + + Sincerely, + Christopher Cleanman, Vice President, NT Habs - Toiletries Dept. + +- type: entity + id: PaperMailNTSoapAd2 + categories: [ HideSpawnMenu ] + suffix: "soap ad 2" #DeltaV - Edited to not be addressed to Frontier Citizens, localized + parent: Paper + components: + - type: Paper + content: |2 + [head=2]GREETINGS DELTA SECTOR CITIZEN[/head] + + OUR REPORTS INDICATE THAT: + 1. YOU HAVE FAILED YOUR QUARTERLY HYGIENE INSPECTION. + 2. THIS HAS REDUCED SECTOR EFFICIENCY BY [bold]0.02%[/bold]. + + ENCLOSED IS A SELECTION OF HYGIENE PRODUCTS SUITABLE FOR USE BY ORGANICS. WE HOPE THAT THIS SITUATION IS RESOLVED PROMPTLY. + + [italic]THIS IS AN AUTOMATED MESSAGE. DO NOT REPLY.[/italic] + +- type: entity + id: PaperMailNTConscript + categories: [ HideSpawnMenu ] + suffix: "conscript" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]NOT ONE STEP BACK.[/head] + + + [head=1]FOR THE FRONTIER.[/head] + +- type: entity + id: PaperMailNTMusket + categories: [ HideSpawnMenu ] + suffix: "musket" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Use a musket for sector defense, + like the founding fathers intended.[/head] + +- type: entity + id: PaperMailNFPaperPusherAd + categories: [ HideSpawnMenu ] + suffix: "paper pusher" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Here is a pen for any letters you write. + [/head] + [head=1]Keep it close, use it often.[/head] + + [head=2]May you write well, neatly, and with style.[/head] + + [head=3]Sincerely, + [italic]The Frontier Paper Pusher's Club[/italic][/head] + +- type: entity + id: PaperMailNFPetBedAssemblyManual + name: pet bed assembly manual + categories: [ HideSpawnMenu ] + suffix: "pet bed assembly manual" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]HÖGANÄS[/head] + + [italic](There is a black and white picture of a pet bed on the first page.)[/italic] + + [italic](On the next few pages, you see a list of materials and a happy stick figure assembling furniture.)[/italic] + + [italic](On the pages after that, you see a set of instructions to assemble a pet bed. You're sure you don't need them, how hard could it be?)[/italic] + +- type: entity + id: PaperMailNTBoxer + categories: [ HideSpawnMenu ] + suffix: "boxer" + parent: Paper + components: + - type: Paper + content: |2 + [head=2]You've gotta defend your belt, champ. + [/head] + [head=1]They're coming for you.[/head] + + [head=2]This should help. Knock 'em out.[/head] + +# Placeholder for an arm-on-use, flashbang fakeout pipebomb +- type: entity + id: PaperMailNFPipebombIntern + categories: [ HideSpawnMenu ] + suffix: "pipe bomb intern" + parent: Paper + components: + - type: Paper + stampedBy: + - stampedColor: '#333333FF' + stampedName: craig +# stampType: Signature #DeltaV - Not compatible with our signatures code stuff apparently + content: |2 + [bold]hey uh, they told me to send you a pipebomb i guess? + + this is all i could find around here, hope that works + + thanks[/bold] + +- type: entity + id: PaperMailNFAntivirus + name: Snortin Antivirus invoice + categories: [ HideSpawnMenu ] + suffix: "antivirus ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]Invoice[/head][head=3] + Snortin Antivirus Software[/head] + + [head=3]Order #41003 + [bold][bullet/][/bold] 1x Snortin Total-275 Antivirus Install Disk[/head] + + [head=3]Total: 947381 Spesos[/head] + + Thank you for making purchase from Snortin Antivirus Software. + We assuring you that our product is greatest. + Please sending payment at earliest convenience. + +- type: entity + id: PaperMailNFEMPPreparedness + categories: [ HideSpawnMenu ] + name: EMP preparedness response form + suffix: "emp preparedness" #DeltaV - Replaces mention of SR with HoS + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]EMP Preparedness Response[/head] + + You have been selected to receive a NT EMP Preparedness kit as a test. Note that this is only a test. In a real emergency, follow the instructions of your vessel's command staff. + + As the recipient of this, please note [bold]any improvements[/bold] that could be made towards the EMP preparedness of the vessel you were aboard when opening and submit this form to your serving Captain or Head of Security. + + [bold]Date of test:[/bold] + [bold]Number of affected items:[/bold] + [bold]Perceived severity of incident:[/bold] + [bold]Suggested improvements:[/bold] + +- type: entity + id: PaperMailNFBuildABuddy + categories: [ HideSpawnMenu ] + name: Build-a-Buddy adoption letter + suffix: "build-a-buddy" #DeltaV- Body text changed, because Goblins Aren't Real + parent: Paper + components: + - type: Paper + stampState: paper_stamp-generic + stampedBy: + - stampedColor: '#FF6699FF' + stampedName: Chief Friendship Officer + - stampedColor: '#333333FF' + stampedName: Cuts-With-Scalpel +# stampType: Signature #DeltaV - Not compatible with our signatures code stuff apparently. + content: |2 + + [head=1]Note of Adoption[/head] + + You're now the proud owner of your very own Build-a-Buddy! + + We hope that your new friend can serve as a shoulder to lean on in the depths of space, and hopefully you won't be quite as lonely out there. Personally, I find putting them together to be rather therapeutic. + + [bold]Collect the whole set![/bold] + [bold][bullet/][/bold] Henry the Human + [bold][bullet/][/bold] Randy the Reptilian + [bold][bullet/][/bold] Steven the Slime + [bold][bullet/][/bold] Valerie the Vulpkanin + +- type: entity + id: PaperMailNFSpaceLaw + categories: [ HideSpawnMenu ] + suffix: "space-law" #DeltaV- edited contents to be from the Delta Sector instead of the Frontier + parent: Paper + components: + - type: Paper + stampState: paper_stamp-centcom + stampedBy: + - stampedColor: '#006600FF' + stampedName: Central Admiralty of the Delta Sector + content: |2 + + [head=1]Space Law is your shield.[/head] + + [head=2]With it, you guard the Delta Sector.[/head][head=3] + [/head] + [head=1]Memorize it. Grasp it firmly.[/head] + + [head=2]The SOP is your sword, don't get rusty.[/head] + + [head=2]Maintain your balance, and wield it well.[/head] + + + + + + + + + [head=3][italic]Internal Bureau of Propaganda[/italic][/head] diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/base_mail_large.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/base_mail_large.yml new file mode 100644 index 00000000000..a5dcefacba5 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/base_mail_large.yml @@ -0,0 +1,79 @@ +# Large packages. +- type: entity + parent: BaseMail + abstract: true + id: BaseMailLarge + name: mail-large-item-name-unaddressed + components: + - type: Item + size: Ginormous + - type: Sprite + scale: 0.8, 0.8 + sprite: Objects/Specific/Mail/mail_large.rsi + layers: + - state: icon + map: ["enum.MailVisualLayers.Icon"] + - state: fragile + map: ["enum.MailVisualLayers.FragileStamp"] + visible: false + - map: ["enum.MailVisualLayers.JobStamp"] + scale: 0.8, 0.8 + offset: 0.235, -0.01 + - state: locked + map: ["enum.MailVisualLayers.Lock"] + - state: priority + map: ["enum.MailVisualLayers.PriorityTape"] + visible: false + shader: unshaded + - state: broken + map: ["enum.MailVisualLayers.Breakage"] + visible: false + - type: GenericVisualizer + visuals: + enum.MailVisuals.IsTrash: + enum.MailVisualLayers.Icon: + True: + state: trash + False: + state: icon + enum.MailVisuals.IsLocked: + enum.MailVisualLayers.Lock: + True: + visible: true + False: + visible: false + enum.MailVisuals.IsFragile: + enum.MailVisualLayers.FragileStamp: + True: + visible: true + False: + visible: false + enum.MailVisuals.IsPriority: + enum.MailVisualLayers.PriorityTape: + True: + visible: true + False: + visible: false + enum.MailVisuals.IsPriorityInactive: + enum.MailVisualLayers.PriorityTape: + True: + shader: shaded + state: priority_inactive + False: + shader: unshaded + state: priority + enum.MailVisuals.IsBroken: + enum.MailVisualLayers.Breakage: + True: + visible: true + False: + visible: false + - type: MultiHandedItem + - type: Mail + isLarge: true + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailLargeAdminFun + suffix: adminfun diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml index 594ffb4d4d9..62ed28e1148 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml @@ -375,3 +375,29 @@ tags: - CartridgeRocket proto: ImmovableRodSlow + +# Frontier mail RPDS +- type: entity + name: mail RPDS + parent: WeaponLauncherChinaLake + id: WeaponMailLake + description: Rap(b?)id Parcel Delivery System + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Launchers/mail.rsi + layers: + - state: icon + map: ["enum.GunVisualLayers.Base"] + - type: Clothing + sprite: Objects/Weapons/Guns/Launchers/mail.rsi + quickEquip: false + slots: + - Back + - Belt + - suitStorage + - type: BallisticAmmoProvider + proto: null + whitelist: + tags: + - MailCapsule + capacity: 4 diff --git a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml index 579c57ced4e..27642558244 100644 --- a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml +++ b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml @@ -3,6 +3,8 @@ startingInventory: ClothingUniformMailCarrier: 2 ClothingUniformSkirtMailCarrier: 2 + WeaponMailLake: 1 # Frontier + BoxMailCapsulePrimed: 2 # Frontier ClothingHeadMailCarrier: 2 MailBag: 2 ClothingHeadsetCargo: 2 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml index d898124b771..c85255f814e 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml @@ -32,7 +32,7 @@ accentVColor: "#DFDFDF" - type: Icon state: pda-security - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # Adds Crime Assist and SecWatch preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -43,7 +43,7 @@ - type: entity parent: BasePDA id: MailCarrierPDA - name: courier PDA # DeltaV - Mail Carrier to Courier replacement + name: courier PDA description: Smells like unopened letters. components: - type: Sprite @@ -67,6 +67,12 @@ - type: Icon sprite: DeltaV/Objects/Devices/pda.rsi state: pda-mailcarrier + - type: CartridgeLoader # Adds a courier performance tracker + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - MailMetricsCartridge - type: entity parent: BasePDA diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml index c1ca2cd60b1..fd77f19dccb 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml @@ -5,11 +5,13 @@ name: mail-item-name-unaddressed components: - type: Item - size: Normal +# size: Normal # Frontier + storedRotation: -90 - type: Mail - type: AccessReader - type: Sprite - sprite: Nyanotrasen/Objects/Specific/Mail/mail.rsi + scale: 0.7, 0.7 # Frontier + sprite: Objects/Specific/Mail/mail.rsi layers: - state: icon map: ["enum.MailVisualLayers.Icon"] @@ -18,8 +20,8 @@ map: ["enum.MailVisualLayers.FragileStamp"] visible: false - map: ["enum.MailVisualLayers.JobStamp"] - scale: 0.5, 0.5 - offset: 0.275, 0.2 + scale: 0.8, 0.8 # Frontier 0.5<0.8 + offset: 0.225, 0.165 # Frontier (0.275, 0.2)<(0.225, 0.165) - state: locked map: ["enum.MailVisualLayers.Lock"] - state: priority @@ -92,6 +94,16 @@ damage: types: Blunt: 10 + - type: CargoSellBlacklist # Frontier + - type: Food # Frontier - Moth food + requiresSpecialDigestion: true + - type: SolutionContainerManager + solutions: + food: + maxVol: 1 + reagents: + - ReagentId: Nothing + Quantity: 1 # This empty parcel is allowed to exist and evade the tests for the admin # mailto command. diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail.yml deleted file mode 100644 index 7f1b26d9cbf..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail.yml +++ /dev/null @@ -1,835 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailAlcohol - suffix: alcohol - components: - - type: Mail - contents: - - id: DrinkAbsintheBottleFull - orGroup: Drink - - id: DrinkBlueCuracaoBottleFull - orGroup: Drink - - id: DrinkGinBottleFull - orGroup: Drink - - id: DrinkMelonLiquorBottleFull - orGroup: Drink - - id: DrinkRumBottleFull - orGroup: Drink - - id: DrinkTequilaBottleFull - orGroup: Drink - - id: DrinkVermouthBottleFull - orGroup: Drink - - id: DrinkVodkaBottleFull - orGroup: Drink - - id: DrinkWineBottleFull - orGroup: Drink - - id: DrinkGlass - amount: 2 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSake - suffix: osake - components: - - type: Mail - contents: - - id: DrinkSakeCup - amount: 2 - - id: DrinkTokkuri - -- type: entity - noSpawn: true - parent: BaseMail - id: MailAMEGuide - suffix: ameguide - components: - - type: Mail - contents: - - id: PaperWrittenAMEScribbles - - id: Pen - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBible - suffix: bible - components: - - type: Mail - contents: - - id: Bible - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBikeHorn - suffix: bike horn - components: - - type: Mail - contents: - - id: BikeHorn - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBlockGameDIY - suffix: blockgamediy - components: - - type: Mail - contents: - - id: BlockGameArcadeComputerCircuitboard - -#- type: entity -# noSpawn: true -# parent: BaseMail -# id: MailBooks -# suffix: books -# components: -# - type: Mail -# contents: -# # Don't use BookDemonomiconRandom. -# # It uses a RandomSpawner which just spawns the book outside of the mail. -# - id: BookDemonomicon1 -# orGroup: Demonomicon -# - id: BookDemonomicon2 -# orGroup: Demonomicon -# - id: BookDemonomicon3 -# orGroup: Demonomicon -# # There's no way to signal "spawn nothing" with an orGroup, -# # so have this blank book instead. Write your own demon summoning tome! -# - id: BookRandom -# prob: 3 -# orGroup: Demonomicon -# - id: BookChemistryInsane -# prob: 0.10 -# - id: BookBotanicalTextbook -# prob: 0.5 -# - id: BookFishing -# prob: 0.10 -# - id: BookDetective -# prob: 0.10 -# - id: BookGnominomicon -# prob: 0.2 -# - id: BookFishops # DeltaV - fishops book -# prob: 0.2 -# - id: BookSalvageEpistemics1 -# prob: 0.025 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCake - suffix: cake - components: - - type: Mail - isFragile: true - isPriority: true - contents: - - id: FoodCakeBlueberry - orGroup: Cake - - id: FoodCakeCarrot - orGroup: Cake - - id: FoodCakeCheese - orGroup: Cake - - id: FoodCakeChocolate - orGroup: Cake - - id: FoodCakeChristmas - orGroup: Cake - - id: FoodCakeClown - orGroup: Cake - - id: FoodCakeLemon - orGroup: Cake - - id: FoodCakeLime - orGroup: Cake - - id: FoodCakeOrange - orGroup: Cake - - id: FoodCakePumpkin - orGroup: Cake - - id: FoodCakeVanilla - orGroup: Cake - - id: FoodMothMothmallow - orGroup: Cake - prob: 0.5 - - id: KnifePlastic - - id: ForkPlastic - amount: 2 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCallForHelp - suffix: call-for-help - components: - - type: Mail - contents: - - id: PaperMailCallForHelp1 - orGroup: Paper - - id: PaperMailCallForHelp2 - orGroup: Paper - - id: PaperMailCallForHelp3 - orGroup: Paper - - id: PaperMailCallForHelp4 - orGroup: Paper - - id: PaperMailCallForHelp5 - orGroup: Paper - - id: FlashlightLantern - orGroup: Gift - - id: Crowbar - orGroup: Gift - prob: 0.5 - - id: CrowbarRed - orGroup: Gift - prob: 0.5 - - id: ClothingMaskGas - orGroup: Gift - - id: WeaponFlareGun - orGroup: Gift - prob: 0.25 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCheese - suffix: cheese - components: - - type: Mail - isFragile: true - isPriority: true - contents: - - id: FoodCheese - - id: KnifePlastic - -- type: entity - noSpawn: true - parent: BaseMail - id: MailChocolate - suffix: chocolate - components: - - type: Mail - contents: - # TODO make some actual chocolate candy items. - - id: FoodSnackChocolate - amount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCigarettes - suffix: cigs - components: - - type: Mail - contents: - - id: CigPackRed - - id: CheapLighter - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCigars - suffix: Cigars - components: - - type: Mail - contents: - - id: CigarCase - - id: Lighter - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCookies - suffix: cookies - components: - - type: Mail - # What, you want to eat stale cookies? - isPriority: true - contents: - - id: FoodBakedCookie - - id: FoodBakedCookieOatmeal - - id: FoodBakedCookieRaisin - - id: FoodBakedCookieSugar - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayArc - suffix: cosplay-arc - components: - - type: Mail - openSound: /Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg - contents: - - id: ClothingCostumeArcDress - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayGeisha - suffix: cosplay-geisha - components: - - type: Mail - contents: - - id: UniformGeisha - - id: DrinkTeapot - - id: DrinkTeacup - amount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayMaid - suffix: cosplay-maid - components: - - type: Mail - contents: - - id: UniformMaid - - id: SprayBottleSpaceCleaner - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayNurse - suffix: cosplay-nurse - components: - - type: Mail - contents: - - id: ClothingUniformJumpskirtNurse - - id: Syringe - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplaySchoolgirl - suffix: cosplay-schoolgirl - components: - - type: Mail - contents: - - id: UniformSchoolgirlBlack - orGroup: Color - - id: UniformSchoolgirlBlue - orGroup: Color - - id: UniformSchoolgirlCyan - orGroup: Color - - id: UniformSchoolgirlGreen - orGroup: Color - - id: UniformSchoolgirlOrange - orGroup: Color - - id: UniformSchoolgirlPink - orGroup: Color - - id: UniformSchoolgirlPurple - orGroup: Color - - id: UniformSchoolgirlRed - orGroup: Color - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayWizard - suffix: cosplay-wizard - components: - - type: Mail - contents: - - id: ClothingOuterWizardFake - - id: ClothingHeadHatWizardFake - - id: ClothingShoesWizardFake - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCrayon - suffix: Crayon - components: - - type: Mail - contents: - - id: CrayonBox - -- type: entity - noSpawn: true - parent: BaseMail - id: MailFigurine - suffix: figurine - components: - - type: Mail - isFragile: true - contents: - - id: ToyAi - orGroup: Toy - - id: ToyNuke - orGroup: Toy - - id: ToyFigurinePassenger - orGroup: Toy - - id: ToyGriffin - orGroup: Toy - - id: ToyHonk - orGroup: Toy - - id: ToyIan - orGroup: Toy - - id: ToyMarauder - orGroup: Toy - - id: ToyMauler - orGroup: Toy - - id: ToyGygax - orGroup: Toy - - id: ToyOdysseus - orGroup: Toy - - id: ToyOwlman - orGroup: Toy - - id: ToyDeathRipley - orGroup: Toy - - id: ToyPhazon - orGroup: Toy - - id: ToyFireRipley - orGroup: Toy - - id: ToyReticence - orGroup: Toy - - id: ToyRipley - orGroup: Toy - - id: ToySeraph - orGroup: Toy - - id: ToyDurand - orGroup: Toy - -- type: entity - noSpawn: true - parent: BaseMail - id: MailFishingCap - suffix: fishingcap - components: - - type: Mail - contents: - - id: ClothingHeadFishCap - -- type: entity - noSpawn: true - parent: BaseMail - id: MailFlashlight - suffix: Flashlight - components: - - type: Mail - contents: - - id: FlashlightLantern - -#- type: entity -# noSpawn: true -# parent: BaseMail -# id: MailFlowers -# suffix: flowers -# components: -# - type: Mail -# contents: -# # TODO actual flowers -# - id: ClothingHeadHatFlowerCrown -# orGroup: Flower -# - id: ClothingHeadHatHairflower -# orGroup: Flower - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHighlander - suffix: highlander - components: - - type: Mail - contents: - - id: ClothingUniformJumpskirtColorRed - - id: ClothingHeadHatBeret - - id: DrinkRedMeadGlass - - id: Claymore - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHighlanderDulled - suffix: highlander, dulled - components: - - type: Mail - contents: - - id: ClothingUniformJumpskirtColorRed - - id: ClothingHeadHatBeret - - id: DrinkGlass - - id: ClaymoreDulled - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHoneyBuns - suffix: honeybuns - components: - - type: Mail - contents: - - id: FoodBakedBunHoney - amount: 2 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailJunkFood - suffix: junk food - components: - - type: Mail - contents: - - id: FoodBoxDonkpocket - - id: FoodSnackChips - -- type: entity - noSpawn: true - parent: BaseMail - id: MailKatana - suffix: Katana - components: - - type: Mail - contents: - - id: Katana - prob: 0.1 - orGroup: Katana - - id: KatanaDulled - prob: 0.9 - orGroup: Katana - -- type: entity - noSpawn: true - parent: BaseMail - id: MailKnife - suffix: Knife - components: - - type: Mail - contents: - - id: CombatKnife - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMoney - suffix: money - components: - - type: Mail - contents: - - id: SpaceCash100 - orGroup: Cash - prob: 0.3 - - id: SpaceCash500 - orGroup: Cash - prob: 0.6 - - id: SpaceCash1000 - orGroup: Cash - prob: 0.3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMuffins - suffix: muffins - components: - - type: Mail - isPriority: true - contents: - - id: FoodBakedMuffinBerry - - id: FoodBakedMuffinCherry - - id: FoodBakedMuffinBluecherry - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMoffins - suffix: moffins - components: - - type: Mail - isPriority: true - contents: - - id: FoodMothMoffin - amount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailNoir - suffix: noir - components: - - type: Mail - contents: - - id: ClothingUniformJumpsuitDetectiveGrey - - id: ClothingUniformJumpskirtDetectiveGrey - - id: ClothingHeadHatBowlerHat - - id: ClothingOuterCoatGentle - -- type: entity - noSpawn: true - parent: BaseMail - id: MailPAI - suffix: PAI - components: - - type: Mail - contents: - - id: PersonalAI - -- type: entity - noSpawn: true - parent: BaseMail - id: MailPlushie - suffix: plushie - components: - - type: Mail - contents: - - id: PlushieMothRandom - orGroup: Prize - - id: PlushieMothMusician - orGroup: Prize - - id: PlushieMothBartender - orGroup: Prize - - id: PlushieBee - orGroup: Prize - - id: PlushieHampter - orGroup: Prize - - id: PlushieRouny - orGroup: Prize - - id: PlushieLamp - orGroup: Prize - - id: PlushieArachind - orGroup: Prize - - id: PlushieLizard - orGroup: Prize - - id: PlushieLizardMirrored - orGroup: Prize - - id: PlushieSpaceLizard - orGroup: Prize - - id: PlushieDiona - orGroup: Prize - - id: PlushieSharkBlue - orGroup: Prize - - id: PlushieSharkPink - orGroup: Prize - - id: PlushieSharkGrey - orGroup: Prize - - id: PlushieCarp - orGroup: Prize - - id: PlushieMagicarp - orGroup: Prize - - id: PlushieHolocarp - orGroup: Prize - - id: PlushieSlime - orGroup: Prize - - id: PlushieSnake - orGroup: Prize - - id: PlushieVox - orGroup: Prize - - id: PlushieAtmosian - orGroup: Prize - - id: PlushiePenguin - orGroup: Prize - - id: PlushieHuman - orGroup: Prize - - id: PlushieArachne - orGroup: Prize - - id: PlushieGnome - orGroup: Prize - - id: PlushieLoveable - orGroup: Prize - - id: PlushieDeer - orGroup: Prize - - id: PlushieIpc - orGroup: Prize - - id: PlushieGrey - orGroup: Prize - - id: PlushieRedFox - orGroup: Prize - - id: PlushiePurpleFox - orGroup: Prize - - id: PlushiePinkFox - orGroup: Prize - - id: PlushieOrangeFox - orGroup: Prize - - id: PlushieMarbleFox - orGroup: Prize - - id: PlushieCrimsonFox - orGroup: Prize - - id: PlushieCoffeeFox - orGroup: Prize - - id: PlushieBlueFox - orGroup: Prize - - id: PlushieBlackFox - orGroup: Prize - - id: PlushieVulp - orGroup: Prize - - id: PlushieCorgi - orGroup: Prize - - id: PlushieGirlyCorgi - orGroup: Prize - - id: PlushieRobotCorgi - orGroup: Prize - - id: PlushieCatBlack - orGroup: Prize - - id: PlushieCatGrey - orGroup: Prize - - id: PlushieCatOrange - orGroup: Prize - - id: PlushieCatSiames - orGroup: Prize - - id: PlushieCatTabby - orGroup: Prize - - id: PlushieCatTuxedo - orGroup: Prize - - id: PlushieCatWhite - orGroup: Prize - - id: PlushieGhost - orGroup: Prize - - id: PlushieRGBee - orGroup: Prize - - id: PlushieRatvar - orGroup: Prize - - id: PlushieNar - orGroup: Prize - - id: PlushieRainbowCarp - orGroup: Prize - - id: PlushieXeno - orGroup: Prize - - id: PlushieJester - orGroup: Prize - - id: PlushieSlips - orGroup: Prize - - id: PlushieAbductor - orGroup: Prize - - id: PlushieTrystan - orGroup: Prize - - id: PlushieAbductorAgent - orGroup: Prize - - id: PlushieNuke - orGroup: Prize - -- type: entity - noSpawn: true - parent: BaseMail - id: MailRestraints - suffix: restraints - components: - - type: Mail - contents: - - id: Handcuffs - - id: ClothingMaskMuzzle - - id: ClothingEyesBlindfold - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSignallerKit - suffix: signallerkit - components: - - type: Mail - contents: - - id: Multitool - - id: RemoteSignaller - -# - type: entity -# noSpawn: true -# parent: BaseMail -# id: MailSixPack -# suffix: sixpack -# components: -# - type: Mail -# contents: -# - id: DrinkCanPack - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSkub - suffix: skub - components: - - type: Mail - contents: - - id: Skub - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSoda - suffix: soda - components: - - type: Mail - contents: - - id: DrinkColaBottleFull - orGroup: Soda - - id: DrinkSpaceMountainWindBottleFull - orGroup: Soda - - id: DrinkSpaceUpBottleFull - orGroup: Soda - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSpaceVillainDIY - suffix: spacevilliandiy - components: - - type: Mail - contents: - - id: SpaceVillainArcadeComputerCircuitboard - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSunglasses - suffix: Sunglasses - components: - - type: Mail - contents: - - id: ClothingEyesGlassesSunglasses - -- type: entity - noSpawn: true - parent: BaseMail - id: MailVagueThreat - suffix: vague-threat - components: - - type: Mail - contents: - - id: PaperMailVagueThreat1 - orGroup: Paper - - id: PaperMailVagueThreat2 - orGroup: Paper - - id: PaperMailVagueThreat3 - orGroup: Paper - - id: PaperMailVagueThreat4 - orGroup: Paper - - id: PaperMailVagueThreat5 - orGroup: Paper - - id: PaperMailVagueThreat6 - orGroup: Paper - - id: PaperMailVagueThreat7 - orGroup: Paper - - id: PaperMailVagueThreat8 - orGroup: Paper - - id: PaperMailVagueThreat9 - orGroup: Paper - - id: PaperMailVagueThreat10 - orGroup: Paper - - id: PaperMailVagueThreat11 - orGroup: Paper - - id: PaperMailVagueThreat12 - orGroup: Paper - - id: KitchenKnife - orGroup: ThreateningObject - - id: ButchCleaver - orGroup: ThreateningObject - - id: CombatKnife - orGroup: ThreateningObject - - id: SurvivalKnife - orGroup: ThreateningObject - - id: SoapHomemade - orGroup: ThreateningObject - - id: FoodMeat - orGroup: ThreateningObject - - id: OrganHumanHeart - orGroup: ThreateningObject - -- type: entity - noSpawn: true - parent: BaseMail - id: MailWinterCoat - suffix: wintercoat - components: - - type: Mail - contents: - - id: ClothingOuterWinterCoat - orGroup: Coat - - id: ClothingOuterWinterCoatLong - orGroup: Coat - - id: ClothingOuterWinterCoatPlaid - orGroup: Coat diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_command.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_command.yml deleted file mode 100644 index 7e2a935f908..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_command.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailCommandPinpointerNuclear - suffix: pinpointernuclear - components: - - type: Mail - contents: - - id: PinpointerNuclear diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_engineering.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_engineering.yml deleted file mode 100644 index 461d9bf1365..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_engineering.yml +++ /dev/null @@ -1,45 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringCables - suffix: cables - components: - - type: Mail - contents: - - id: CableHVStack - orGroup: Cables - - id: CableMVStack - orGroup: Cables - - id: CableApcStack - orGroup: Cables - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringKudzuDeterrent - suffix: antikudzu - components: - - type: Mail - contents: - - id: PlantBGoneSpray - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringSheetGlass - suffix: sheetglass - components: - - type: Mail - contents: - - id: SheetGlass - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringWelderReplacement - suffix: welder - components: - - type: Mail - contents: - - id: Welder - diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_specific_items.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_specific_items.yml deleted file mode 100644 index b4d2b547798..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_specific_items.yml +++ /dev/null @@ -1,169 +0,0 @@ -- type: entity - id: PaperMailCallForHelp1 - noSpawn: true - suffix: "call for help 1" - parent: Paper - components: - - type: Paper - content: | - Help! They're coming! Take this! - -- type: entity - id: PaperMailCallForHelp2 - noSpawn: true - suffix: "call for help 2" - parent: Paper - components: - - type: Paper - content: | - Check disposals! - -- type: entity - id: PaperMailCallForHelp3 - noSpawn: true - suffix: "call for help 3" - parent: Paper - components: - - type: Paper - content: | - GET ME OUT! - -- type: entity - id: PaperMailCallForHelp4 - noSpawn: true - suffix: "call for help 4" - parent: Paper - components: - - type: Paper - content: | - Check maintenance! - -- type: entity - id: PaperMailCallForHelp5 - noSpawn: true - suffix: "call for help 5" - parent: Paper - components: - - type: Paper - content: | - Save me, please! - -- type: entity - id: PaperMailVagueThreat1 - noSpawn: true - suffix: "vague mail threat 1" - parent: Paper - components: - - type: Paper - content: | - I know what you did. You don't know what I'm going to do to you. - -- type: entity - id: PaperMailVagueThreat2 - noSpawn: true - suffix: "vague mail threat 2" - parent: Paper - components: - - type: Paper - content: | - I'm coming for you. - -- type: entity - id: PaperMailVagueThreat3 - noSpawn: true - suffix: "vague mail threat 3" - parent: Paper - components: - - type: Paper - content: | - You're next. - -- type: entity - id: PaperMailVagueThreat4 - noSpawn: true - suffix: "vague mail threat 4" - parent: Paper - components: - - type: Paper - content: | - We see you. - -- type: entity - id: PaperMailVagueThreat5 - noSpawn: true - suffix: "vague mail threat 5" - parent: Paper - components: - - type: Paper - content: | - I hope your affairs are in order. - -- type: entity - id: PaperMailVagueThreat6 - noSpawn: true - suffix: "vague mail threat 6" - parent: Paper - components: - - type: Paper - content: | - It's only a matter of time. Enjoy it while it lasts. - -- type: entity - id: PaperMailVagueThreat7 - noSpawn: true - suffix: "vague mail threat 7" - parent: Paper - components: - - type: Paper - content: | - Who should we mail your pieces to? - -- type: entity - id: PaperMailVagueThreat8 - noSpawn: true - suffix: "vague mail threat 8" - parent: Paper - components: - - type: Paper - content: | - Do you prefer to die slowly or quickly? Just kidding. We don't care what you think. - -- type: entity - id: PaperMailVagueThreat9 - noSpawn: true - suffix: "vague mail threat 9" - parent: Paper - components: - - type: Paper - content: | - I think your head would look nice on my mantel. - -- type: entity - id: PaperMailVagueThreat10 - noSpawn: true - suffix: "vague mail threat 10" - parent: Paper - components: - - type: Paper - content: | - You should have paid up. It's too late now. - -- type: entity - id: PaperMailVagueThreat11 - noSpawn: true - suffix: "vague mail threat 11" - parent: Paper - components: - - type: Paper - content: | - Your family will miss you, but don't worry. We'll take care of them too. - -- type: entity - id: PaperMailVagueThreat12 - noSpawn: true - suffix: "vague mail threat 12" - parent: Paper - components: - - type: Paper - content: | - I have a bet that you're going to die today. I'm not afraid of cheating. diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml b/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml index ceb87bbaa1b..b85cfef87ab 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml @@ -3,3 +3,4 @@ abstract: true components: - type: StationMailRouter + - type: StationLogisticStats # DeltaV - Tracks statistics related to mail and income diff --git a/Resources/Prototypes/Recipes/Lathes/misc.yml b/Resources/Prototypes/Recipes/Lathes/misc.yml index ab13dc4573f..2c0e1eeec38 100644 --- a/Resources/Prototypes/Recipes/Lathes/misc.yml +++ b/Resources/Prototypes/Recipes/Lathes/misc.yml @@ -207,3 +207,19 @@ materials: Steel: 400 Glass: 200 + +- type: latheRecipe + id: ClothingShoesBootsMagAdv + result: ClothingShoesBootsMagAdv + completetime: 12 + materials: + Silver: 1000 + Gold: 500 + +- type: latheRecipe + id: MailCapsule + result: MailCapsulePrimed + completetime: 1 + materials: + Glass: 100 + Plastic: 100 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index cc81a2ed64b..77cc4f372f2 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -25,6 +25,9 @@ - type: Tag id: Arrow +- type: Tag + id: Ash + - type: Tag id: ATVKeys @@ -352,6 +355,9 @@ - type: Tag id: CartridgeRocket +- type: Tag + id: CaveFactory + # Allows you to walk over tile entities such as lava without steptrigger - type: Tag id: Catwalk @@ -803,6 +809,12 @@ - type: Tag id: MacroBomb +- type: Tag + id: Mail + +- type: Tag + id: MailCapsule + - type: Tag id: MimeBelt @@ -916,6 +928,12 @@ - type: Tag id: Multitool +- type: Tag + id: Mustard + +- type: Tag + id: MysteryFigureBox + - type: Tag id: NoBlockAnchoring @@ -1295,6 +1313,9 @@ - type: Tag id: WhitelistChameleon +- type: Tag + id: WhoopieCushion + - type: Tag id: Window diff --git a/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/cart-mail.png b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/cart-mail.png new file mode 100644 index 00000000000..5734abb6fd1 Binary files /dev/null and b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/cart-mail.png differ diff --git a/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json index 293870d3a3b..4a4ba3352f8 100644 --- a/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json +++ b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Timfa", + "copyright": "Timfa, plus edits by portfiend", "size": { "x": 32, "y": 32 @@ -9,6 +9,9 @@ "states": [ { "name": "cart-cri" + }, + { + "name": "cart-mail" } ] -} \ No newline at end of file +} diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-cash.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-cash.png new file mode 100644 index 00000000000..8ba33c95b57 Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-cash.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-empty.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-empty.png new file mode 100644 index 00000000000..085b787410b Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-empty.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-food.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-food.png new file mode 100644 index 00000000000..d08489cec89 Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-food.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-mail.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-mail.png new file mode 100644 index 00000000000..b35a2acc0f3 Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-mail.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/meta.json b/Resources/Textures/Objects/Misc/mail_capsule.rsi/meta.json new file mode 100644 index 00000000000..2e9bfc36170 --- /dev/null +++ b/Resources/Textures/Objects/Misc/mail_capsule.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Made for Frontier by erhardsteinhauer (discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon-empty" + }, + { + "name": "icon-mail" + }, + { + "name": "icon-food" + }, + { + "name": "icon-cash" + }, + { + "name": "spent" + } + ] +} diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/spent.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/spent.png new file mode 100644 index 00000000000..acd0d0577fc Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/spent.png differ diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/broken.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/broken.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/broken.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/broken.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/fragile.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/fragile.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/fragile.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/fragile.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/icon.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/icon.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/icon.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/icon.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/locked.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/locked.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/locked.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/locked.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/meta.json b/Resources/Textures/Objects/Specific/Mail/mail.rsi/meta.json similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/meta.json rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/meta.json diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/postmark.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/postmark.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/postmark.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/postmark.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/priority.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/priority.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority_inactive.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/priority_inactive.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority_inactive.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/priority_inactive.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/trash.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/trash.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/trash.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/trash.png diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/broken.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/broken.png new file mode 100644 index 00000000000..1c798c4075b Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/broken.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/fragile.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/fragile.png new file mode 100644 index 00000000000..0917000cbef Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/fragile.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/icon.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/icon.png new file mode 100644 index 00000000000..f3974ab116c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-left.png new file mode 100644 index 00000000000..ccbc87cf3bd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-right.png new file mode 100644 index 00000000000..ccbc87cf3bd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/locked.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/locked.png new file mode 100644 index 00000000000..1cacaf19213 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/locked.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/meta.json b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/meta.json new file mode 100644 index 00000000000..ac5345ba1a5 --- /dev/null +++ b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/meta.json @@ -0,0 +1,40 @@ +{ + "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" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority.png new file mode 100644 index 00000000000..9c5a74ad103 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority_inactive.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority_inactive.png new file mode 100644 index 00000000000..fc03165b576 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority_inactive.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/trash.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/trash.png new file mode 100644 index 00000000000..2ef4ee72338 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/trash.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/bolt-open.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/bolt-open.png new file mode 100644 index 00000000000..87c6d812ec2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/bolt-open.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BACKPACK.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BACKPACK.png new file mode 100644 index 00000000000..c17a68eb35a Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BELT.png new file mode 100644 index 00000000000..59dc5f13ed0 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/icon.png new file mode 100644 index 00000000000..dd7b21e1671 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-left.png new file mode 100644 index 00000000000..427389f40d5 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-right.png new file mode 100644 index 00000000000..8e50ef04f91 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/meta.json new file mode 100644 index 00000000000..464c22d1a7a --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/pull/6042/commits/64916c98f4847acc4adf3a2416bf78c005fd7dd7, https://github.com/discordia-space/CEV-Eris/blob/master/icons/obj/guns/launcher/grenadelauncher.dmi, backpack sprite by Peptide, resprited for mail gun by erhardsteinhauer (discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "bolt-open" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + } + ] +} \ No newline at end of file