diff --git a/Content.Client/CartridgeLoader/Cartridges/MailMetricUi.cs b/Content.Client/CartridgeLoader/Cartridges/MailMetricUi.cs
new file mode 100644
index 0000000000..504dda7c56
--- /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 0000000000..39639fe8c9
--- /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 0000000000..553e3a5793
--- /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.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index 0e56153752..c54f5002ec 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -74,6 +74,7 @@ public static void SetupContexts(IInputContextContainer contexts)
human.AddFunction(ContentKeyFunctions.OpenBelt);
human.AddFunction(ContentKeyFunctions.OfferItem);
human.AddFunction(ContentKeyFunctions.ToggleStanding);
+ human.AddFunction(ContentKeyFunctions.ToggleCrawlingUnder);
human.AddFunction(ContentKeyFunctions.MouseMiddle);
human.AddFunction(ContentKeyFunctions.ArcadeUp);
human.AddFunction(ContentKeyFunctions.ArcadeDown);
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index ab4ebd83fa..f84c20b7ed 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -102,7 +102,7 @@ private void HandleHoldLookUp(BaseButton.ButtonToggledEventArgs args)
_cfg.SetCVar(CCVars.HoldLookUp, args.Pressed);
_cfg.SaveToFile();
}
-
+
private void HandleDefaultWalk(BaseButton.ButtonToggledEventArgs args)
{
_cfg.SetCVar(CCVars.DefaultWalk, args.Pressed);
@@ -205,6 +205,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action(OnMovementInput);
- SubscribeNetworkEvent(OnDowned);
- SubscribeNetworkEvent(OnStood);
-
SubscribeNetworkEvent(OnCheckAutoGetUp);
}
+ public override void Update(float frameTime)
+ {
+ // Update draw depth of laying down entities as necessary
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var layingDown, out var standing, out var sprite))
+ {
+ // Do not modify the entities draw depth if it's modified externally
+ if (sprite.DrawDepth != layingDown.NormalDrawDepth && sprite.DrawDepth != layingDown.CrawlingUnderDrawDepth)
+ continue;
+
+ sprite.DrawDepth = standing.CurrentState is StandingState.Lying && layingDown.IsCrawlingUnder
+ ? layingDown.CrawlingUnderDrawDepth
+ : layingDown.NormalDrawDepth;
+ }
+
+ query.Dispose();
+ }
+
private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args)
{
if (!_timing.IsFirstTimePredicted
@@ -51,26 +66,6 @@ private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveE
sprite.Rotation = Angle.FromDegrees(90);
}
- private void OnDowned(DrawDownedEvent args)
- {
- var uid = GetEntity(args.Uid);
- if (!TryComp(uid, out var sprite)
- || !TryComp(uid, out var component))
- return;
-
- sprite.DrawDepth = component.CrawlingDrawDepth;
- }
-
- private void OnStood(DrawStoodEvent args)
- {
- var uid = GetEntity(args.Uid);
- if (!TryComp(uid, out var sprite)
- || !TryComp(uid, out var component))
- return;
-
- sprite.DrawDepth = component.NormalDrawDepth;
- }
-
private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args)
{
if (!_timing.IsFirstTimePredicted)
diff --git a/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs b/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs
index ecbbb46eb5..6c40d6240f 100644
--- a/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs
+++ b/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs
@@ -10,27 +10,20 @@ public partial class CustomSpecieName : Migration
///
protected override void Up(MigrationBuilder migrationBuilder)
{
- migrationBuilder.AlterColumn(
+ migrationBuilder.AddColumn(
name: "custom_specie_name",
table: "profile",
type: "text",
nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
+ defaultValue: "");
}
///
protected override void Down(MigrationBuilder migrationBuilder)
{
- migrationBuilder.AlterColumn(
+ migrationBuilder.DropColumn(
name: "custom_specie_name",
- table: "profile",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
+ table: "profile");
}
}
-}
+}
\ No newline at end of file
diff --git a/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs b/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs
index d48c7f87d9..a1e968045b 100644
--- a/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs
+++ b/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs
@@ -10,27 +10,20 @@ public partial class CustomSpecieName : Migration
///
protected override void Up(MigrationBuilder migrationBuilder)
{
- migrationBuilder.AlterColumn(
+ migrationBuilder.AddColumn(
name: "custom_specie_name",
table: "profile",
type: "TEXT",
nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "TEXT",
- oldNullable: true);
+ defaultValue: "");
}
///
protected override void Down(MigrationBuilder migrationBuilder)
{
- migrationBuilder.AlterColumn(
+ migrationBuilder.DropColumn(
name: "custom_specie_name",
- table: "profile",
- type: "TEXT",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "TEXT");
+ table: "profile");
}
}
}
diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs
index 59bf05fe03..77f543cdcf 100644
--- a/Content.Server/Antag/AntagSelectionSystem.API.cs
+++ b/Content.Server/Antag/AntagSelectionSystem.API.cs
@@ -5,6 +5,7 @@
using Content.Server.Objectives;
using Content.Shared.Chat;
using Content.Shared.Mind;
+using Content.Shared.Preferences;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.Enums;
@@ -156,6 +157,36 @@ public List GetAntagMindEntityUids(Entity e
return ent.Comp.SelectedMinds.Select(p => p.Item1).ToList();
}
+ ///
+ /// Checks if a given session has the primary antag preferences for a given definition
+ ///
+ public bool HasPrimaryAntagPreference(ICommonSession? session, AntagSelectionDefinition def)
+ {
+ if (session == null)
+ return true;
+
+ if (def.PrefRoles.Count == 0)
+ return false;
+
+ var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter;
+ return pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p));
+ }
+
+ ///
+ /// Checks if a given session has the fallback antag preferences for a given definition
+ ///
+ public bool HasFallbackAntagPreference(ICommonSession? session, AntagSelectionDefinition def)
+ {
+ if (session == null)
+ return true;
+
+ if (def.FallbackRoles.Count == 0)
+ return false;
+
+ var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter;
+ return pref.AntagPreferences.Any(p => def.FallbackRoles.Contains(p));
+ }
+
///
/// Returns all the antagonists for this rule who are currently alive
///
diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs
index d74824dd2d..cd4d836e68 100644
--- a/Content.Server/Antag/AntagSelectionSystem.cs
+++ b/Content.Server/Antag/AntagSelectionSystem.cs
@@ -17,7 +17,6 @@
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.Players;
-using Content.Shared.Preferences;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Server.Player;
@@ -118,12 +117,15 @@ private void OnSpawnComplete(PlayerSpawnCompleteEvent args)
// something to figure out later.
var query = QueryActiveRules();
+ var rules = new List<(EntityUid, AntagSelectionComponent)>();
while (query.MoveNext(out var uid, out _, out var antag, out _))
{
- // TODO ANTAG
- // what why aasdiuhasdopiuasdfhksad
- // stop this insanity please
- // probability of antag assignment shouldn't depend on the order in which rules are returned by the query.
+ rules.Add((uid, antag));
+ }
+ RobustRandom.Shuffle(rules);
+
+ foreach (var (uid, antag) in rules)
+ {
if (!RobustRandom.Prob(LateJoinRandomChance))
continue;
@@ -221,13 +223,13 @@ public void ChooseAntags(Entity ent, IList
/// Tries to makes a given player into the specified antagonist.
///
- public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false)
+ public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true)
{
- if (!IsSessionValid(ent, session, def) ||
- !IsEntityValid(session?.AttachedEntity, def))
- {
+ if (checkPref && !HasPrimaryAntagPreference(session, def))
+ return false;
+
+ if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def))
return false;
- }
MakeAntag(ent, session, def, ignoreSpawner);
return true;
@@ -324,16 +326,14 @@ public AntagSelectionPlayerPool GetPlayerPool(Entity en
var fallbackList = new List();
foreach (var session in sessions)
{
- if (!IsSessionValid(ent, session, def) ||
- !IsEntityValid(session.AttachedEntity, def))
+ if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def))
continue;
- var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter;
- if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p)))
+ if (HasPrimaryAntagPreference(session, def))
{
preferredList.Add(session);
}
- else if (def.FallbackRoles.Count != 0 && pref.AntagPreferences.Any(p => def.FallbackRoles.Contains(p)))
+ else if (HasFallbackAntagPreference(session, def))
{
fallbackList.Add(session);
}
@@ -404,13 +404,13 @@ public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def)
if (def.Whitelist != null)
{
- if (!def.Whitelist.IsValid(entity.Value, EntityManager))
+ if (!def.Whitelist.IsValid(entity.Value))
return false;
}
if (def.Blacklist != null)
{
- if (def.Blacklist.IsValid(entity.Value, EntityManager))
+ if (def.Blacklist.IsValid(entity.Value))
return false;
}
diff --git a/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs b/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs
new file mode 100644
index 0000000000..ca1fbeaad0
--- /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 0000000000..6abf4eb5a4
--- /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 0000000000..380a4d90c0
--- /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 0000000000..00b6d0a16e
--- /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 0000000000..f73aef85b0
--- /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 0000000000..af0bdaa87f
--- /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 0000000000..281a27ea79
--- /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 0000000000..81f58bc7a5
--- /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 ce87eb131f..6f6df1e10b 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 5af873f9e8..390de93fb3 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 0000000000..38d37a1326
--- /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 0000000000..59aaa3aff6
--- /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 0000000000..e80febd230
--- /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 61d94bbad3..0000000000
--- 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 4224de5de4..0000000000
--- 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 bc05d7307d..0000000000
--- 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 05cd0c88a7..0000000000
--- 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.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs
index ae742cf1f9..c855bacfc7 100644
--- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs
+++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs
@@ -425,7 +425,7 @@ public override void Update(float frameTime)
if (xform.MapUid != arrivalsXform.MapUid)
{
if (arrivals.IsValid())
- _shuttles.FTLToDock(uid, shuttle, arrivals);
+ _shuttles.FTLToDock(uid, shuttle, arrivals, _cfgManager.GetCVar(CCVars.ArrivalsStartupTime), _cfgManager.GetCVar(CCVars.ArrivalsHyperspaceTime), "DockArrivals");
comp.NextArrivalsTime = _timing.CurTime + TimeSpan.FromSeconds(tripTime);
}
@@ -435,7 +435,7 @@ public override void Update(float frameTime)
var targetGrid = _station.GetLargestGrid(data);
if (targetGrid != null)
- _shuttles.FTLToDock(uid, shuttle, targetGrid.Value);
+ _shuttles.FTLToDock(uid, shuttle, targetGrid.Value, _cfgManager.GetCVar(CCVars.ArrivalsStartupTime), _cfgManager.GetCVar(CCVars.ArrivalsHyperspaceTime), "DockArrivals");
// The ArrivalsCooldown includes the trip there, so we only need to add the time taken for
// the trip back.
diff --git a/Content.Server/Standing/LayingDownSystem.cs b/Content.Server/Standing/LayingDownSystem.cs
index e5054bdd70..fcad649c9a 100644
--- a/Content.Server/Standing/LayingDownSystem.cs
+++ b/Content.Server/Standing/LayingDownSystem.cs
@@ -1,6 +1,11 @@
using Content.Shared.Standing;
using Content.Shared.CCVar;
+using Content.Shared.Input;
+using Content.Shared.Movement.Systems;
+using Content.Shared.Popups;
using Robust.Shared.Configuration;
+using Robust.Shared.Input.Binding;
+using Robust.Shared.Player;
namespace Content.Server.Standing;
diff --git a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs
index e4a63d6108..8c47d65233 100644
--- a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs
+++ b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs
@@ -17,6 +17,6 @@ private void OnStartup(EntityUid uid, LayingDownModifierComponent component, Com
return;
layingDown.StandingUpTime *= component.LayingDownCooldownMultiplier;
- layingDown.SpeedModify *= component.DownedSpeedMultiplierMultiplier;
+ layingDown.LyingSpeedModifier *= component.DownedSpeedMultiplierMultiplier;
}
}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index ddf657bfb3..08401416d7 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -1513,6 +1513,18 @@ public static readonly CVarDef
public static readonly CVarDef ArrivalsCooldown =
CVarDef.Create("shuttle.arrivals_cooldown", 50f, CVar.SERVERONLY);
+ ///
+ /// Time it takes the shuttle to spin up it's hyper drive and jump
+ ///
+ public static readonly CVarDef ArrivalsStartupTime=
+ CVarDef.Create("shuttle.arrivals_startup_time", 5.5f, CVar.SERVERONLY);
+
+ ///
+ /// Time spent in hyperspace
+ ///
+ public static readonly CVarDef ArrivalsHyperspaceTime =
+ CVarDef.Create("shuttle.arrivals_hyperspace_time", 20f, CVar.SERVERONLY);
+
///
/// Are players allowed to return on the arrivals shuttle.
///
@@ -2475,11 +2487,10 @@ public static readonly CVarDef
CVarDef.Create("rest.hold_look_up", false, CVar.CLIENT | CVar.ARCHIVE);
///
- /// When true, entities that fall to the ground will be able to crawl under tables and
- /// plastic flaps, allowing them to take cover from gunshots.
+ /// When true, players can choose to crawl under tables while laying down, using the designated keybind.
///
public static readonly CVarDef CrawlUnderTables =
- CVarDef.Create("rest.crawlundertables", false, CVar.REPLICATED);
+ CVarDef.Create("rest.crawlundertables", true, CVar.SERVER | CVar.ARCHIVE);
#endregion
diff --git a/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs b/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs
new file mode 100644
index 0000000000..69506f5d0c
--- /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/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs
index f85983282c..1f4e1b9a67 100644
--- a/Content.Shared/Input/ContentKeyFunctions.cs
+++ b/Content.Shared/Input/ContentKeyFunctions.cs
@@ -57,6 +57,7 @@ public static class ContentKeyFunctions
public static readonly BoundKeyFunction ResetZoom = "ResetZoom";
public static readonly BoundKeyFunction OfferItem = "OfferItem";
public static readonly BoundKeyFunction ToggleStanding = "ToggleStanding";
+ public static readonly BoundKeyFunction ToggleCrawlingUnder = "ToggleCrawlingUnder";
public static readonly BoundKeyFunction LookUp = "LookUp";
public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp";
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index 0b316c5128..220c0ecddb 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -534,9 +534,13 @@ public void EnsureValid(ICommonSession session, IDependencyCollection collection
name = GetName(Species, gender);
}
- var customspeciename = speciesPrototype.CustomName
- ? FormattedMessage.RemoveMarkup(Customspeciename ?? "")[..MaxNameLength]
- : "";
+ var customspeciename =
+ !speciesPrototype.CustomName
+ || string.IsNullOrEmpty(Customspeciename)
+ ? ""
+ : Customspeciename.Length > MaxNameLength
+ ? FormattedMessage.RemoveMarkup(Customspeciename)[..MaxNameLength]
+ : FormattedMessage.RemoveMarkup(Customspeciename);
string flavortext;
if (FlavorText.Length > MaxDescLength)
diff --git a/Content.Shared/Standing/LayingDownComponent.cs b/Content.Shared/Standing/LayingDownComponent.cs
index b3c07220e1..ec9351e22c 100644
--- a/Content.Shared/Standing/LayingDownComponent.cs
+++ b/Content.Shared/Standing/LayingDownComponent.cs
@@ -1,6 +1,5 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
-using Content.Shared.DrawDepth;
namespace Content.Shared.Standing;
@@ -8,19 +7,24 @@ namespace Content.Shared.Standing;
public sealed partial class LayingDownComponent : Component
{
[DataField, AutoNetworkedField]
- public float StandingUpTime { get; set; } = 1f;
+ public TimeSpan StandingUpTime = TimeSpan.FromSeconds(1);
[DataField, AutoNetworkedField]
- public float SpeedModify { get; set; } = 0.4f;
+ public float LyingSpeedModifier = 0.35f,
+ CrawlingUnderSpeedModifier = 0.5f;
[DataField, AutoNetworkedField]
public bool AutoGetUp;
+ ///
+ /// If true, the entity is choosing to crawl under furniture. This is purely visual and has no effect on physics.
+ ///
[DataField, AutoNetworkedField]
- public int NormalDrawDepth = (int) DrawDepth.DrawDepth.Mobs;
+ public bool IsCrawlingUnder = false;
[DataField, AutoNetworkedField]
- public int CrawlingDrawDepth = (int) DrawDepth.DrawDepth.SmallMobs;
+ public int NormalDrawDepth = (int) DrawDepth.DrawDepth.Mobs,
+ CrawlingUnderDrawDepth = (int) DrawDepth.DrawDepth.SmallMobs;
}
[Serializable, NetSerializable]
@@ -31,15 +35,3 @@ public sealed class CheckAutoGetUpEvent(NetEntity user) : CancellableEntityEvent
{
public NetEntity User = user;
}
-
-[Serializable, NetSerializable]
-public sealed class DrawDownedEvent(NetEntity uid) : EntityEventArgs
-{
- public NetEntity Uid = uid;
-}
-
-[Serializable, NetSerializable]
-public sealed class DrawStoodEvent(NetEntity uid) : EntityEventArgs
-{
- public NetEntity Uid = uid;
-}
\ No newline at end of file
diff --git a/Content.Shared/Standing/SharedLayingDownSystem.cs b/Content.Shared/Standing/SharedLayingDownSystem.cs
index bed4ec53bf..914b04cdef 100644
--- a/Content.Shared/Standing/SharedLayingDownSystem.cs
+++ b/Content.Shared/Standing/SharedLayingDownSystem.cs
@@ -1,10 +1,13 @@
+using Content.Shared.ActionBlocker;
+using Content.Shared.CCVar;
using Content.Shared.DoAfter;
using Content.Shared.Gravity;
using Content.Shared.Input;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
-using Content.Shared.Standing;
+using Content.Shared.Popups;
using Content.Shared.Stunnable;
+using Robust.Shared.Configuration;
using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
@@ -17,11 +20,16 @@ public abstract class SharedLayingDownSystem : EntitySystem
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
+ [Dependency] private readonly IConfigurationManager _config = default!;
+ [Dependency] private readonly SharedPopupSystem _popups = default!;
+ [Dependency] private readonly MovementSpeedModifierSystem _speed = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
public override void Initialize()
{
CommandBinds.Builder
.Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding))
+ .Bind(ContentKeyFunctions.ToggleCrawlingUnder, InputCmdHandler.FromDelegate(HandleCrawlUnderRequest, handle: false))
.Register();
SubscribeNetworkEvent(OnChangeState);
@@ -49,17 +57,37 @@ private void ToggleStanding(ICommonSession? session)
RaiseNetworkEvent(new ChangeLayingDownEvent());
}
+ private void HandleCrawlUnderRequest(ICommonSession? session)
+ {
+ if (session == null
+ || session.AttachedEntity is not {} uid
+ || !TryComp(uid, out var standingState)
+ || !TryComp(uid, out var layingDown)
+ || !_actionBlocker.CanInteract(uid, null))
+ return;
+
+ var newState = !layingDown.IsCrawlingUnder;
+ if (standingState.CurrentState is StandingState.Standing)
+ newState = false; // If the entity is already standing, this function only serves a fallback method to fix its draw depth
+
+ // Do not allow to begin crawling under if it's disabled in config. We still, however, allow to stop it, as a failsafe.
+ if (newState && !_config.GetCVar(CCVars.CrawlUnderTables))
+ {
+ _popups.PopupEntity(Loc.GetString("crawling-under-tables-disabled-popup"), uid, session);
+ return;
+ }
+
+ layingDown.IsCrawlingUnder = newState;
+ _speed.RefreshMovementSpeedModifiers(uid);
+ Dirty(uid, layingDown);
+ }
+
private void OnChangeState(ChangeLayingDownEvent ev, EntitySessionEventArgs args)
{
if (!args.SenderSession.AttachedEntity.HasValue)
return;
var uid = args.SenderSession.AttachedEntity.Value;
-
- // TODO: Wizard
- //if (HasComp(uid))
- // return;
-
if (!TryComp(uid, out StandingStateComponent? standing)
|| !TryComp(uid, out LayingDownComponent? layingDown))
return;
@@ -89,10 +117,11 @@ private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component
private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args)
{
- if (_standing.IsDown(uid))
- args.ModifySpeed(component.SpeedModify, component.SpeedModify);
- else
- args.ModifySpeed(1f, 1f);
+ if (!_standing.IsDown(uid))
+ return;
+
+ var modifier = component.LyingSpeedModifier * (component.IsCrawlingUnder ? component.CrawlingUnderSpeedModifier : 1);
+ args.ModifySpeed(modifier, modifier);
}
private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args)
@@ -125,6 +154,7 @@ public bool TryStandUp(EntityUid uid, LayingDownComponent? layingDown = null, St
return false;
standingState.CurrentState = StandingState.GettingUp;
+ layingDown.IsCrawlingUnder = false;
return true;
}
diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs
index 37f1095fd4..aed6ce372f 100644
--- a/Content.Shared/Standing/StandingStateSystem.cs
+++ b/Content.Shared/Standing/StandingStateSystem.cs
@@ -1,15 +1,12 @@
using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
-using Content.Shared.CCVar;
using Content.Shared.Hands.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Physics;
using Content.Shared.Rotation;
using Robust.Shared.Audio.Systems;
-using Robust.Shared.Configuration;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
-using Robust.Shared.Serialization;
namespace Content.Shared.Standing;
@@ -20,7 +17,6 @@ public sealed class StandingStateSystem : EntitySystem
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedBuckleSystem _buckle = default!;
- [Dependency] private readonly IConfigurationManager _config = default!;
// If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited.
private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable;
@@ -69,10 +65,6 @@ public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true
Dirty(standingState);
RaiseLocalEvent(uid, new DownedEvent(), false);
- // Raising this event will lower the entity's draw depth to the same as a small mob.
- if (_config.GetCVar(CCVars.CrawlUnderTables) && setDrawDepth)
- RaiseNetworkEvent(new DrawDownedEvent(GetNetEntity(uid)));
-
// Seemed like the best place to put it
_appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance);
@@ -129,10 +121,6 @@ public bool Stand(EntityUid uid,
Dirty(uid, standingState);
RaiseLocalEvent(uid, new StoodEvent(), false);
- // Raising this event will increase the entity's draw depth to a normal mob's.
- if (_config.GetCVar(CCVars.CrawlUnderTables))
- RaiseNetworkEvent(new DrawStoodEvent(GetNetEntity(uid)));
-
_appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance);
if (TryComp(uid, out FixturesComponent? fixtureComponent))
@@ -170,4 +158,4 @@ public sealed class StoodEvent : EntityEventArgs { }
///
/// Raised when an entity is not standing
///
-public sealed class DownedEvent : EntityEventArgs { }
+public sealed class DownedEvent : EntityEventArgs { }
diff --git a/Content.Shared/Traits/Assorted/Components/CyberEyesComponent.cs b/Content.Shared/Traits/Assorted/Components/CyberEyesComponent.cs
new file mode 100644
index 0000000000..7009077e9c
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/Components/CyberEyesComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Shared.Traits.Assorted.Components;
+
+[RegisterComponent]
+public sealed partial class CyberEyesComponent : Component
+{
+ ///
+ /// The text that will appear when someone with the CyberEyes component is examined at close range
+ ///
+ [DataField]
+ public string CyberEyesExaminationText = "examine-cybereyes-message";
+}
diff --git a/Content.Shared/Traits/Assorted/Systems/CyberEyesSystem.cs b/Content.Shared/Traits/Assorted/Systems/CyberEyesSystem.cs
new file mode 100644
index 0000000000..2dde437972
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/Systems/CyberEyesSystem.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Examine;
+using Content.Shared.Traits.Assorted.Components;
+
+namespace Content.Shared.Traits.Assorted.Systems;
+
+public sealed class CyberEyesSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnExamined);
+ }
+
+ private void OnExamined(EntityUid uid, CyberEyesComponent component, ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ args.PushMarkup($"[color=white]{Loc.GetString(component.CyberEyesExaminationText, ("entity", uid))}[/color]");
+ }
+}
diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml
index 0f1bf764b8..96656ab107 100644
--- a/Resources/Changelog/Changelog.yml
+++ b/Resources/Changelog/Changelog.yml
@@ -7165,3 +7165,80 @@ Entries:
id: 6439
time: '2024-10-12T20:49:32.0000000+00:00'
url: https://github.com/Simple-Station/Einstein-Engines/pull/1042
+- author: Fansana
+ changes:
+ - type: Fix
+ message: Fixes the arrivals shuttle choosing to correct docking port.
+ id: 6440
+ time: '2024-10-13T16:38:30.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1040
+- author: FoxxoTrystan
+ changes:
+ - type: Fix
+ message: Oneirophage is once more psionic invisible in webs.
+ id: 6441
+ time: '2024-10-13T18:33:59.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1033
+- author: Mnemotechnician
+ changes:
+ - type: Add
+ message: >-
+ You can now toggle crawling under furniture! The default keybind is
+ Shift-R, you can change it in settings.
+ id: 6442
+ time: '2024-10-13T18:34:58.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1036
+- author: DEATHB4DEFEAT
+ changes:
+ - type: Tweak
+ message: Made the show clothing/loadouts button labels more clear
+ id: 6443
+ time: '2024-10-13T18:41:16.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1030
+- author: VMSolidus
+ changes:
+ - type: Tweak
+ message: Brains now can no longer be eaten.
+ id: 6444
+ time: '2024-10-13T18:51:26.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1044
+- author: VMSolidus
+ changes:
+ - type: Add
+ message: >-
+ Added 8 new Physical Traits. These are, Cyber-Eyes Basic System(Plus 4
+ different modular options), Bionic Arm, Dermal Armor, and Platelet
+ Factories.
+ id: 6445
+ time: '2024-10-13T19:25:05.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1035
+- author: VMSolidus
+ changes:
+ - type: Add
+ message: >-
+ NT has started hiring contractors from other corporations to fill out
+ station staff. New loadout items for corporate contractors are now
+ available for Bartender, Botanist, Cataloguer, Chef, and Janitor.
+ Currently there are no jumpskirt versions of these uniforms. We are
+ looking for people willing to make skirt variations of each of these.
+ id: 6446
+ time: '2024-10-13T19:26:23.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1041
+- author: Mnemotechnician
+ changes:
+ - type: Add
+ message: >-
+ The Courier and Logistics Officer now have a new program in their PDA
+ for tracking mail delivery performance, including earnings and percent
+ of packages opened, damaged, or expired.
+ - type: Add
+ message: >-
+ The list of possible mail packages has been greately expanded, and now
+ includes large parcels.
+ - type: Add
+ message: >-
+ The CourierDrobe now offers a rapid mail delivery device, along with
+ capsules for it.
+ id: 6447
+ time: '2024-10-13T19:38:19.0000000+00:00'
+ url: https://github.com/Simple-Station/Einstein-Engines/pull/1011
diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt
index aeed963662..3a7e0e7ed6 100644
--- a/Resources/Credits/GitHub.txt
+++ b/Resources/Credits/GitHub.txt
@@ -1 +1 @@
-0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlueHNT, Boaz1111, BobdaBiscuit, brainfood1183, BramvanZijp, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, CilliePaint, clorl, Clyybber, CodedCrow, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, Deeeeja, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, dootythefrooty, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, Evgencheg, exincore, exp111, Fahasor, FairlySadPanda, Fansana, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, FoxxoTrystan, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, geraeumig, Git-Nivrak, github-actions[bot], gituhabu, gluesniffler, GNF54, Golinth, GoodWheatley, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, HoofedEar, Hoolny, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, Lgibb18, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Mnemotechnician, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, notafet, notquitehadouken, noudoit, nuke-haus, NULL882, nyeogmi, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, ShadowCommander, Shadowtheprotogen546, ShatteredSwords, SignalWalker, SimpleStation14, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, SleepyScarecrow, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, stalengd, Stealthbomber16, stellar-novas, StrawberryMoses, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, Tmanzxd, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UltimateJester, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, Winkarst-cpu, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, ZNixian, ZoldorfTheWizard, Zumorica, Zymem
+0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlueHNT, Boaz1111, BobdaBiscuit, brainfood1183, BramvanZijp, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, CilliePaint, clorl, Clyybber, CodedCrow, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, Deeeeja, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, dootythefrooty, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, Evgencheg, exincore, exp111, Fahasor, FairlySadPanda, Fansana, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, FoxxoTrystan, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, geraeumig, Git-Nivrak, github-actions[bot], gituhabu, gluesniffler, Golinth, GoodWheatley, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, HoofedEar, Hoolny, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, Lgibb18, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Mnemotechnician, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, notafet, notquitehadouken, noudoit, nuke-haus, NULL882, nyeogmi, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, ShadowCommander, Shadowtheprotogen546, ShatteredSwords, SignalWalker, SimpleStation14, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, SleepyScarecrow, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, stalengd, Stealthbomber16, stellar-novas, StrawberryMoses, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, Tmanzxd, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UltimateJester, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, Winkarst-cpu, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, zelezniciar1, ZelteHonor, zerorulez, zionnBE, ZNixian, ZoldorfTheWizard, Zumorica, Zymem
diff --git a/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl
index 9b4c59d001..ede1a36b8e 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/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
index ea24439f70..66e525b8f3 100644
--- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
@@ -147,6 +147,7 @@ ui-options-function-rotate-stored-item = Rotate stored item
ui-options-function-offer-item = Offer something
ui-options-function-save-item-location = Save item location
ui-options-function-toggle-standing = Toggle standing
+ui-options-function-toggle-crawling-under = Toggle crawling under furniture
ui-options-static-storage-ui = Lock storage window to hotbar
ui-options-function-smart-equip-backpack = Smart-equip to backpack
diff --git a/Resources/Locale/en-US/loadouts/categories.ftl b/Resources/Locale/en-US/loadouts/categories.ftl
index ddabf9db13..193760005e 100644
--- a/Resources/Locale/en-US/loadouts/categories.ftl
+++ b/Resources/Locale/en-US/loadouts/categories.ftl
@@ -26,6 +26,9 @@ loadout-category-JobsSecurity = Security
loadout-category-JobsService = Service
loadout-category-JobsServiceUncategorized = Uncategorized
loadout-category-JobsServiceBartender = Bartender
+loadout-category-JobsServiceBotanist = Botanist
+loadout-category-JobsServiceChef = Chef
+loadout-category-JobsServiceJanitor = Janitor
loadout-category-Mask = Mask
loadout-category-Neck = Neck
loadout-category-Outer = Outer
diff --git a/Resources/Locale/en-US/loadouts/itemgroups.ftl b/Resources/Locale/en-US/loadouts/itemgroups.ftl
index 50f37e7c7a..9e66e200bc 100644
--- a/Resources/Locale/en-US/loadouts/itemgroups.ftl
+++ b/Resources/Locale/en-US/loadouts/itemgroups.ftl
@@ -29,6 +29,19 @@ character-item-group-LoadoutHeadEngineering = Engineering Headgear
character-item-group-LoadoutOuterEngineering = Engineering Outerwear
character-item-group-LoadoutUniformsEngineering = Engineering Uniforms
+# Epistemics
+character-item-group-LoadoutEyesScience = Epistemics Eyewear
+character-item-group-LoadoutGlovesScience = Epistemics Gloves
+character-item-group-LoadoutHeadScience = Epistemics Headgear
+character-item-group-LoadoutMaskScience = Epistemics Masks
+character-item-group-LoadoutNeckScience = Epistemics Neckwear
+character-item-group-LoadoutOuterScience = Epistemics Outerwear
+character-item-group-LoadoutShoesScience = Epistemics Shoes
+character-item-group-LoadoutUniformsScience = Epistemics Uniforms
+
+# Epistemics - Cataloguer
+character-item-group-LoadoutCataloguerUniforms = Cataloguer Uniforms
+
# Medical
character-item-group-LoadoutEyesMedical = Medical Eyewear
character-item-group-LoadoutGlovesMedical = Medical Gloves
@@ -55,6 +68,7 @@ character-item-group-LoadoutHoSWeapon = Head of Security's Antique Weapon Collec
# Service
character-item-group-LoadoutEquipmentService = Service Equipment
+character-item-group-LoadoutHeadService = Service Headgear
character-item-group-LoadoutMaskService = Service Masks
character-item-group-LoadoutNeckService = Service Neckwear
character-item-group-LoadoutOuterService = Service Outerwear
@@ -63,9 +77,22 @@ character-item-group-LoadoutUniformsService = Service Uniforms
# Service - Bartender
character-item-group-LoadoutBartenderAmmo = Bartender Ammo
+character-item-group-LoadoutBartenderHead = Bartender Headgear
character-item-group-LoadoutBartenderOuterwear = Bartender Outerwear
+character-item-group-LoadoutBartenderUniforms = Bartender Uniforms
character-item-group-LoadoutBartenderWeapon = Bartender Weapon
+# Service - Botanist
+character-item-group-LoadoutBotanistUniforms = Botanist Uniforms
+
+# Service - Chef
+character-item-group-LoadoutChefHead = Chef Headgear
+character-item-group-LoadoutChefOuter = Chef Outerwear
+character-item-group-LoadoutChefUniforms = Chef Uniforms
+
+# Service - Janitor
+character-item-group-LoadoutJanitorUniforms = Janitor Uniforms
+
# Service - Musician
character-item-group-LoadoutMusicianInstruments = Musician Instruments
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 72cd3879b3..1f471bb7a5 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 0000000000..5a27737732
--- /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/Locale/en-US/movement/laying.ftl b/Resources/Locale/en-US/movement/laying.ftl
index f75061d6a7..de84ca629e 100644
--- a/Resources/Locale/en-US/movement/laying.ftl
+++ b/Resources/Locale/en-US/movement/laying.ftl
@@ -1,3 +1,6 @@
+crawling-under-tables-disabled-popup = Crawling under tables is disabled on this server.
+
+# TODO either remove those, or make use of them
laying-comp-lay-success-self = You lay down.
laying-comp-lay-success-other = {THE($entity)} lays down.
laying-comp-lay-fail-self = You can't lay down right now.
diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
index c9b7105314..022f3423c0 100644
--- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
+++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
@@ -2,8 +2,8 @@ humanoid-profile-editor-randomize-everything-button = Randomize everything
humanoid-profile-editor-name-label = Name:
humanoid-profile-editor-name-random-button = Randomize
humanoid-profile-editor-appearance-tab = Appearance
-humanoid-profile-editor-clothing = Show clothing
-humanoid-profile-editor-loadouts = Show loadout
+humanoid-profile-editor-clothing = Preview job equipment:
+humanoid-profile-editor-loadouts = Preview loadout items:
humanoid-profile-editor-clothing-show = Show
humanoid-profile-editor-sex-label = Sex:
humanoid-profile-editor-sex-male-text = Male
diff --git a/Resources/Locale/en-US/traits/misc.ftl b/Resources/Locale/en-US/traits/misc.ftl
new file mode 100644
index 0000000000..b76dfa9729
--- /dev/null
+++ b/Resources/Locale/en-US/traits/misc.ftl
@@ -0,0 +1 @@
+examine-cybereyes-message = {$entity}'s eyes shine with a faint inner light.
diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl
index 31770a33e4..1c16a94421 100644
--- a/Resources/Locale/en-US/traits/traits.ftl
+++ b/Resources/Locale/en-US/traits/traits.ftl
@@ -352,4 +352,45 @@ trait-description-HighDampening =
trait-name-Azaziba = Sinta'Azaziba
trait-description-Azaziba =
A language of Moghes consisting of a combination of spoken word and gesticulation.
- While waning since Moghes entered the galactic stage - it enjoys popular use by Unathi that never fell to the Hegemony's cultural dominance.
\ No newline at end of file
+ While waning since Moghes entered the galactic stage - it enjoys popular use by Unathi that never fell to the Hegemony's cultural dominance.
+
+trait-name-BionicArm = Bionic Arm
+trait-description-BionicArm =
+ One or more of your limbs have been replaced with an expensive, state of the art bionic. It could be either one made of highly realistic synthflesh,
+ or a more obvious metal limb. This limb provides enhanced strength to its user, allowing one to pry open barriers with their bare hands.
+
+trait-name-PlateletFactories = Platelet Factories
+trait-description-PlateletFactories =
+ Your body has been augmented with a series of biotailored organs that enhance the owner's long term survivability. These organs will attempt
+ to keep the user alive, even in the face of advanced trauma, all the way up until - but not including - death.
+ Your natural healing is no longer capped, and will now slowly heal any damage type. This includes more exotic injuries like radiation exposure, or cancer.
+
+trait-name-DermalArmor = Dermal Armor
+trait-description-DermalArmor =
+ Your skin has been replaced with a flexible, yet sturdy, hard-polymer shell wrapped in a layer of synthetic flesh.
+ This augmentation provides an innate 10% resistance to physical damage.
+
+trait-name-CyberEyes = Cyber-Eyes Basic System
+trait-description-CyberEyes =
+ One or more of your eyes have been replaced with a highly modular mechanical ocular implant.
+ Their most basic functionality is to provide amelioration for weaknesses of the wearer's natural eyes,
+ but additionally these implants provide protection from bright flashes of light.
+
+trait-name-CyberEyesSecurity = Cyber-Eyes SecHud
+trait-description-CyberEyesSecurity =
+ Your Cyber-Eyes have been upgraded to include a built-in Security Hud. Note that this augmentation is considered Contraband
+ for anyone not under the employ of station Security personel, and may be disabled by your employer before dispatch to the station.
+
+trait-name-CyberEyesMedical = Cyber-Eyes MedHud
+trait-description-CyberEyesMedical =
+ Your Cyber-Eyes have been upgraded to include a built-in Medical Hud, allowing you to track the relative health condition of biological organisms.
+
+trait-name-CyberEyesDiagnostic = Cyber-Eyes DiagHud
+trait-description-CyberEyesDiagnostic =
+ Your Cyber-Eyes have been upgraded to include a built-in Diagnostic Hud, allowing you to track the condition of synthetic entities.
+
+trait-name-CyberEyesOmni = Cyber-Eyes HudSuite
+trait-description-CyberEyesOmni =
+ This expensive implant provides the combined benefits of a SecHud, MedHud, and a DiagHud.
+ Note that this augmentation is considered Contraband for anyone not under the employ of station Security personel,
+ and may be disabled by your employer before dispatch to the station.
diff --git a/Resources/Migrations/frontierMigrations.yml b/Resources/Migrations/frontierMigrations.yml
new file mode 100644
index 0000000000..e8d607494e
--- /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/Body/Organs/diona.yml b/Resources/Prototypes/Body/Organs/diona.yml
index 69fc630b9e..a2133f7f90 100644
--- a/Resources/Prototypes/Body/Organs/diona.yml
+++ b/Resources/Prototypes/Body/Organs/diona.yml
@@ -27,11 +27,12 @@
- type: entity
id: OrganDionaBrain
- parent: [BaseDionaOrgan, OrganHumanBrain]
+ parent: OrganHumanBrain
name: brain
description: "The source of incredible, unending intelligence. Honk."
components:
- type: Sprite
+ sprite: Mobs/Species/Diona/organs.rsi
state: brain
- type: SolutionContainerManager
solutions:
@@ -102,7 +103,7 @@
layers:
- state: lung-l
- state: lung-r
- - type: Lung
+ - type: Lung
- type: Metabolizer
removeEmpty: true
solutionOnBody: false
@@ -131,7 +132,7 @@
description: "The source of incredible, unending intelligence. Honk."
components:
- type: Brain
- - type: Nymph # This will make the organs turn into a nymph when they're removed.
+ - type: Nymph # This will make the organs turn into a nymph when they're removed.
entityPrototype: OrganDionaNymphBrain
transferMind: true
@@ -170,11 +171,11 @@
- type: entity
id: OrganDionaNymphStomach
- parent: MobDionaNymphAccent
+ parent: MobDionaNymphAccent
noSpawn: true
name: diona nymph
suffix: Stomach
- description: Contains the stomach of a formerly fully-formed Diona. It doesn't taste any better for it.
+ description: Contains the stomach of a formerly fully-formed Diona. It doesn't taste any better for it.
components:
- type: IsDeadIC
- type: Body
@@ -186,7 +187,7 @@
noSpawn: true
name: diona nymph
suffix: Lungs
- description: Contains the lungs of a formerly fully-formed Diona. Breathtaking.
+ description: Contains the lungs of a formerly fully-formed Diona. Breathtaking.
components:
- type: IsDeadIC
- type: Body
diff --git a/Resources/Prototypes/Body/Organs/human.yml b/Resources/Prototypes/Body/Organs/human.yml
index 69081020ce..e6a04d4a60 100644
--- a/Resources/Prototypes/Body/Organs/human.yml
+++ b/Resources/Prototypes/Body/Organs/human.yml
@@ -6,6 +6,13 @@
- type: Sprite
sprite: Mobs/Species/Human/organs.rsi
- type: Organ
+
+- type: entity
+ id: BaseHumanOrgan
+ parent: BaseHumanOrganUnGibbable
+ abstract: true
+ components:
+ - type: Gibbable
- type: Food
- type: Extractable
grindableSolutionName: organ
@@ -27,13 +34,6 @@
tags:
- Meat
-- type: entity
- id: BaseHumanOrgan
- parent: BaseHumanOrganUnGibbable
- abstract: true
- components:
- - type: Gibbable
-
- type: entity
id: OrganHumanBrain
parent: BaseHumanOrganUnGibbable
@@ -67,7 +67,7 @@
- type: FlavorProfile
flavors:
- people
-
+
- type: entity
id: OrganHumanEyes
parent: BaseHumanOrgan
diff --git a/Resources/Prototypes/Body/Organs/slime.yml b/Resources/Prototypes/Body/Organs/slime.yml
index 3da76c5d4a..5b908e75f4 100644
--- a/Resources/Prototypes/Body/Organs/slime.yml
+++ b/Resources/Prototypes/Body/Organs/slime.yml
@@ -1,6 +1,6 @@
- type: entity
id: SentientSlimeCore
- parent: [BaseItem, OrganHumanBrain]
+ parent: OrganHumanBrain
name: sentient slime core
description: "The source of incredible, unending gooeyness."
components:
@@ -34,7 +34,7 @@
- ReagentId: Slime
Quantity: 10
-
+
- type: entity
id: OrganSlimeLungs
parent: BaseHumanOrgan
diff --git a/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml b/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml
index e5e43760da..ac41a9286a 100644
--- a/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml
+++ b/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml
@@ -101,3 +101,22 @@
items:
- type: loadout
id: LoadoutScienceShoesBootsWinterSci
+
+# Cataloguer
+- type: characterItemGroup
+ id: LoadoutCataloguerUniforms
+ items:
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianNt
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianIdris
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianOrion
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianHeph
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianPMCG
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianZav
+ - type: loadout
+ id: LoadoutScienceJumpsuitLibrarianZeng
\ No newline at end of file
diff --git a/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml b/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml
index 01ec0aaeea..61c2b286b7 100644
--- a/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml
+++ b/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml
@@ -76,12 +76,24 @@
- type: loadout
id: LoadoutServiceClownCowToolboxFilled
+- type: characterItemGroup
+ id: LoadoutHeadService
+ items:
+ - type: loadout
+ id: LoadoutServiceClownCowToolboxFilled
+
# Bartender
- type: characterItemGroup
id: LoadoutBartenderOuterwear
items:
- type: loadout
id: LoadoutServiceBartenderArmorDuraVest
+ - type: loadout
+ id: LoadoutServiceOuterBartenderNt
+ - type: loadout
+ id: LoadoutServiceOuterBartenderIdris
+ - type: loadout
+ id: LoadoutServiceOuterBartenderOrion
- type: characterItemGroup
id: LoadoutBartenderAmmo
@@ -98,3 +110,76 @@
id: LoadoutServiceBartenderShotgunDoubleBarreledRubber
- type: loadout
id: LoadoutServiceBartenderMosinRubber
+
+- type: characterItemGroup
+ id: LoadoutBartenderUniforms
+ items:
+ - type: loadout
+ id: LoadoutServiceJumpsuitBartenderNt
+ - type: loadout
+ id: LoadoutServiceJumpsuitBartenderIdris
+ - type: loadout
+ id: LoadoutServiceJumpsuitBartenderOrion
+
+- type: characterItemGroup
+ id: LoadoutBartenderHead
+ items:
+ - type: loadout
+ id: LoadoutServiceHeadBartenderNt
+ - type: loadout
+ id: LoadoutServiceHeadBartenderIdris
+ - type: loadout
+ id: LoadoutServiceHeadBartenderOrion
+
+# Botanist
+- type: characterItemGroup
+ id: LoadoutBotanistUniforms
+ items:
+ - type: loadout
+ id: LoadoutServiceJumpsuitHydroponicsNt
+ - type: loadout
+ id: LoadoutServiceJumpsuitHydroponicsIdris
+ - type: loadout
+ id: LoadoutServiceJumpsuitHydroponicsOrion
+
+# Chef
+- type: characterItemGroup
+ id: LoadoutChefUniforms
+ items:
+ - type: loadout
+ id: LoadoutServiceJumpsuitChefNt
+ - type: loadout
+ id: LoadoutServiceJumpsuitChefIdris
+ - type: loadout
+ id: LoadoutServiceJumpsuitChefOrion
+
+- type: characterItemGroup
+ id: LoadoutChefHead
+ items:
+ - type: loadout
+ id: LoadoutServiceHeadChefNt
+ - type: loadout
+ id: LoadoutServiceHeadChefIdris
+ - type: loadout
+ id: LoadoutServiceHeadChefOrion
+
+- type: characterItemGroup
+ id: LoadoutChefOuter
+ items:
+ - type: loadout
+ id: LoadoutServiceOuterChefNt
+ - type: loadout
+ id: LoadoutServiceOuterChefIdris
+ - type: loadout
+ id: LoadoutServiceOuterChefOrion
+
+# Janitor
+- type: characterItemGroup
+ id: LoadoutJanitorUniforms
+ items:
+ - type: loadout
+ id: LoadoutServiceJumpsuitJanitorNt
+ - type: loadout
+ id: LoadoutServiceJumpsuitJanitorIdris
+ - type: loadout
+ id: LoadoutServiceJumpsuitJanitorOrion
\ No newline at end of file
diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml
index 811d5a580c..30721d5e42 100644
--- a/Resources/Prototypes/Damage/modifier_sets.yml
+++ b/Resources/Prototypes/Damage/modifier_sets.yml
@@ -359,3 +359,10 @@
Slash: 0.6
Piercing: 0.6
Holy: 1.5
+
+- type: damageModifierSet
+ id: DermalArmor
+ coefficients:
+ Blunt: 0.9
+ Slash: 0.9
+ Piercing: 0.9
diff --git a/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml
index 88f691ba9b..778b372585 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 def215cee4..81e11d9d08 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 7de554f5ff..6f96930078 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 a41fac14ff..19a1ee3c53 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 0000000000..86dec46a65
--- /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 0000000000..af70fc621c
--- /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 38526966b8..be6f9818e2 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 4e797272e5..735f840a20 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 b47d5af56e..eed846a090 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 0000000000..c0d6df9ec2
--- /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/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml
index 0c9f06a58b..2d2961c424 100644
--- a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml
+++ b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml
@@ -854,72 +854,69 @@
id: ClothingUniformJumpsuitLibrarianOrionFlipped
name: orion express librarian jumpsuit
-################################
-# Unused Librarian Jumpsuits #
-################################
-# - type: entity
-# parent: ClothingUniformBaseFlippable
-# id: ClothingUniformJumpsuitLibrarianHeph
-# name: hephaestus industries librarian jumpsuit
-# description: A cosy green jumper fit for a curator of books.
-# components:
-# - type: Sprite
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_heph.rsi
-# - type: Clothing
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_heph.rsi
-
-# - type: entity
-# parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianHeph ]
-# id: ClothingUniformJumpsuitLibrarianHephFlipped
-# name: hephaestus industries librarian jumpsuit
-
-# - type: entity
-# parent: ClothingUniformBaseFlippable
-# id: ClothingUniformJumpsuitLibrarianPMCG
-# name: private military contracting group librarian jumpsuit
-# description: A cosy white jumper fit for a curator of books.
-# components:
-# - type: Sprite
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi
-# - type: Clothing
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi
-
-# - type: entity
-# parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianPMCG ]
-# id: ClothingUniformJumpsuitLibrarianPMCGFlipped
-# name: private military contracting group librarian jumpsuit
-
-# - type: entity
-# parent: ClothingUniformBaseFlippable
-# id: ClothingUniformJumpsuitLibrarianZav
-# name: zavodskoi interstellar librarian jumpsuit
-# description: A blood brown jumper fit for a curator of books.
-# components:
-# - type: Sprite
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_zav.rsi
-# - type: Clothing
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_zav.rsi
-
-# - type: entity
-# parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianZav ]
-# id: ClothingUniformJumpsuitLibrarianZavFlipped
-# name: zavodskoi interstellar librarian jumpsuit
-
-# - type: entity
-# parent: ClothingUniformBaseFlippable
-# id: ClothingUniformJumpsuitLibrarianZeng
-# name: zeng-hu pharmaceuticals librarian jumpsuit
-# description: A blood brown jumper fit for a curator of books.
-# components:
-# - type: Sprite
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi
-# - type: Clothing
-# sprite: Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi
-
-# - type: entity
-# parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianZeng ]
-# id: ClothingUniformJumpsuitLibrarianZengFlipped
-# name: zeng-hu pharmaceuticals librarian jumpsuit
+- type: entity
+ parent: ClothingUniformBaseFlippable
+ id: ClothingUniformJumpsuitLibrarianHeph
+ name: hephaestus industries librarian jumpsuit
+ description: A cosy green jumper fit for a curator of books.
+ components:
+ - type: Sprite
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_heph.rsi
+ - type: Clothing
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_heph.rsi
+
+- type: entity
+ parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianHeph ]
+ id: ClothingUniformJumpsuitLibrarianHephFlipped
+ name: hephaestus industries librarian jumpsuit
+
+- type: entity
+ parent: ClothingUniformBaseFlippable
+ id: ClothingUniformJumpsuitLibrarianPMCG
+ name: private military contracting group librarian jumpsuit
+ description: A cosy white jumper fit for a curator of books.
+ components:
+ - type: Sprite
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi
+ - type: Clothing
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi
+
+- type: entity
+ parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianPMCG ]
+ id: ClothingUniformJumpsuitLibrarianPMCGFlipped
+ name: private military contracting group librarian jumpsuit
+
+- type: entity
+ parent: ClothingUniformBaseFlippable
+ id: ClothingUniformJumpsuitLibrarianZav
+ name: zavodskoi interstellar librarian jumpsuit
+ description: A blood brown jumper fit for a curator of books.
+ components:
+ - type: Sprite
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_zav.rsi
+ - type: Clothing
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_zav.rsi
+
+- type: entity
+ parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianZav ]
+ id: ClothingUniformJumpsuitLibrarianZavFlipped
+ name: zavodskoi interstellar librarian jumpsuit
+
+- type: entity
+ parent: ClothingUniformBaseFlippable
+ id: ClothingUniformJumpsuitLibrarianZeng
+ name: zeng-hu pharmaceuticals librarian jumpsuit
+ description: A blood brown jumper fit for a curator of books.
+ components:
+ - type: Sprite
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi
+ - type: Clothing
+ sprite: Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi
+
+- type: entity
+ parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianZeng ]
+ id: ClothingUniformJumpsuitLibrarianZengFlipped
+ name: zeng-hu pharmaceuticals librarian jumpsuit
- type: entity
parent: ClothingUniformBase
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index 1a38899783..ebefd05cbb 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 0000000000..bd6c9057cd
--- /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/Misc/spider_web.yml b/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml
index bb284000a7..68ba94b578 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml
@@ -76,6 +76,9 @@
ignoreWhitelist:
components:
- IgnoreSpiderWeb
+ - type: Tag
+ tags:
+ - ArachneWeb
- type: entity
id: SpiderWebClown
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 0000000000..536736fc90
--- /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 0000000000..6f1c86e00b
--- /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 0000000000..b21ca40717
--- /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 0000000000..a5dcefacba
--- /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 594ffb4d4d..62ed28e114 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/Loadouts/Categories/categories.yml b/Resources/Prototypes/Loadouts/Categories/categories.yml
index 94e90b663f..c47d6ddef8 100644
--- a/Resources/Prototypes/Loadouts/Categories/categories.yml
+++ b/Resources/Prototypes/Loadouts/Categories/categories.yml
@@ -65,6 +65,9 @@
subCategories:
- JobsServiceUncategorized
- JobsServiceBartender
+ - JobsServiceBotanist
+ - JobsServiceChef
+ - JobsServiceJanitor
- type: loadoutCategory
id: JobsServiceUncategorized
@@ -72,6 +75,15 @@
- type: loadoutCategory
id: JobsServiceBartender
+- type: loadoutCategory
+ id: JobsServiceBotanist
+
+- type: loadoutCategory
+ id: JobsServiceChef
+
+- type: loadoutCategory
+ id: JobsServiceJanitor
+
- type: loadoutCategory
id: Mask
root: true
diff --git a/Resources/Prototypes/Loadouts/Jobs/science.yml b/Resources/Prototypes/Loadouts/Jobs/science.yml
index 9a9526f83f..198d7f9cc6 100644
--- a/Resources/Prototypes/Loadouts/Jobs/science.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/science.yml
@@ -3,14 +3,14 @@
- type: loadout
id: LoadoutScienceUniformJumpskirtSenior
category: JobsScience
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutUniformsScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
- !type:CharacterDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
@@ -20,14 +20,14 @@
- type: loadout
id: LoadoutScienceUniformJumpsuitSenior
category: JobsScience
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutUniformsScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
- !type:CharacterDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
@@ -37,28 +37,28 @@
- type: loadout
id: LoadoutScienceUniformJumpskirtRoboticist
category: JobsScience
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutUniformsScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingUniformJumpskirtRoboticist
- type: loadout
id: LoadoutScienceUniformJumpsuitRoboticist
category: JobsScience
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutUniformsScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingUniformJumpsuitRoboticist
@@ -100,11 +100,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingOuterCoatRnd
@@ -116,11 +114,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingOuterCoatLab
@@ -132,37 +128,37 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingOuterCoatRobo
- type: loadout
id: LoadoutScienceOuterWinterSci
category: JobsScience
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingOuterWinterSci
- type: loadout
id: LoadoutScienceOuterLabcoatSeniorResearcher
category: JobsScience
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
- !type:CharacterDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
@@ -177,11 +173,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingOuterExplorerCoat
@@ -265,10 +259,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutGlovesScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingHandsGlovesColorPurple
@@ -280,10 +273,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutGlovesScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingHandsGlovesLatex
@@ -295,10 +287,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutGlovesScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingHandsGlovesRobohands
@@ -312,10 +303,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutNeckScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingNeckTieSci
@@ -327,10 +317,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutNeckScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingNeckScarfStripedPurple
@@ -356,9 +345,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutNeckScience
- - !type:CharacterJobRequirement
- jobs:
- - Chaplain
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingNeckScarfStripedBlack
@@ -388,11 +377,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutHeadScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingHeadHatBeretRND
@@ -404,9 +391,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutHeadScience
- - !type:CharacterJobRequirement
- jobs:
- - Chaplain
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingHeadHatFez
@@ -476,11 +463,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutEyesScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingEyesHudDiagnostic
@@ -492,11 +477,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutEyesScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingEyesEyepatchHudDiag
@@ -510,11 +493,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutShoesScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
items:
- ClothingShoesBootsWinterSci
@@ -529,11 +510,9 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutOuterScience
- - !type:CharacterJobRequirement
- jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
- type: loadout
id: LoadoutHeadHoodTechPriest
@@ -545,8 +524,105 @@
requirements:
- !type:CharacterItemGroupRequirement
group: LoadoutHeadScience
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Epistemics
+
+# Cataloguer
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianNt
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianNt
+
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianIdris
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianIdris
+
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianOrion
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianOrion
+
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianHeph
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
- !type:CharacterJobRequirement
jobs:
- - Scientist
- - ResearchAssistant
- - ResearchDirector
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianHeph
+
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianPMCG
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianPMCG
+
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianZav
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianZav
+
+- type: loadout
+ id: LoadoutScienceJumpsuitLibrarianZeng
+ category: JobsScience
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutCataloguerUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Librarian
+ items:
+ - ClothingUniformJumpsuitLibrarianZeng
\ No newline at end of file
diff --git a/Resources/Prototypes/Loadouts/Jobs/service.yml b/Resources/Prototypes/Loadouts/Jobs/service.yml
index bdfa3d8165..b346bc3182 100644
--- a/Resources/Prototypes/Loadouts/Jobs/service.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/service.yml
@@ -280,7 +280,7 @@
- type: loadout
id: LoadoutServiceBartenderBoxLightRifleRubber
category: JobsServiceBartender
- cost: 1
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
@@ -308,7 +308,7 @@
- type: loadout
id: LoadoutServiceBartenderMosinRubber
category: JobsServiceBartender
- cost: 3
+ cost: 0
exclusive: true
requirements:
- !type:CharacterItemGroupRequirement
@@ -319,10 +319,136 @@
items:
- WeaponSniperMosinRubber
+- type: loadout
+ id: LoadoutServiceJumpsuitBartenderNt
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingUniformJumpsuitBartenderNt
+
+- type: loadout
+ id: LoadoutServiceJumpsuitBartenderIdris
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingUniformJumpsuitBartenderIdris
+
+- type: loadout
+ id: LoadoutServiceJumpsuitBartenderOrion
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingUniformJumpsuitBartenderOrion
+
+- type: loadout
+ id: LoadoutServiceHeadBartenderNt
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderHead
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingHeadHatFlatcapBartenderNanotrasen
+
+- type: loadout
+ id: LoadoutServiceHeadBartenderIdris
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderHead
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingHeadHatFlatcapBartenderIdris
+
+- type: loadout
+ id: LoadoutServiceHeadBartenderOrion
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderHead
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingHeadHatFlatcapBartenderOrion
+
+- type: loadout
+ id: LoadoutServiceOuterBartenderNt
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderOuterwear
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingOuterVestNt
+
+- type: loadout
+ id: LoadoutServiceOuterBartenderIdris
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderOuterwear
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingOuterVestIdris
+
+- type: loadout
+ id: LoadoutServiceOuterBartenderOrion
+ category: JobsServiceBartender
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBartenderOuterwear
+ - !type:CharacterJobRequirement
+ jobs:
+ - Bartender
+ items:
+ - ClothingOuterVestOrion
+
# Botanist
- type: loadout
id: LoadoutServiceBotanistUniformOveralls
- category: JobsServiceUncategorized
+ category: JobsServiceBotanist
cost: 0
exclusive: true
requirements:
@@ -334,6 +460,48 @@
items:
- ClothingUniformOveralls
+- type: loadout
+ id: LoadoutServiceJumpsuitHydroponicsNt
+ category: JobsServiceBotanist
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBotanistUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Botanist
+ items:
+ - ClothingUniformJumpsuitHydroponicsNt
+
+- type: loadout
+ id: LoadoutServiceJumpsuitHydroponicsIdris
+ category: JobsServiceBotanist
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBotanistUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Botanist
+ items:
+ - ClothingUniformJumpsuitHydroponicsIdris
+
+- type: loadout
+ id: LoadoutServiceJumpsuitHydroponicsOrion
+ category: JobsServiceBotanist
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutBotanistUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Botanist
+ items:
+ - ClothingUniformJumpsuitHydroponicsOrion
+
# Lawyer
- type: loadout
id: LoadoutServiceLawyerUniformBlueSuit
@@ -658,4 +826,174 @@
jobs:
- Musician
items:
- - OcarinaInstrument
\ No newline at end of file
+ - OcarinaInstrument
+
+# Chef
+- type: loadout
+ id: LoadoutServiceJumpsuitChefNt
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingUniformJumpsuitChefNt
+
+- type: loadout
+ id: LoadoutServiceJumpsuitChefIdris
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingUniformJumpsuitChefIdris
+
+- type: loadout
+ id: LoadoutServiceJumpsuitChefOrion
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingUniformJumpsuitChefOrion
+
+- type: loadout
+ id: LoadoutServiceHeadChefNt
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefHead
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingHeadHatChefNt
+
+- type: loadout
+ id: LoadoutServiceHeadChefIdris
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefHead
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingHeadHatChefIdris
+
+- type: loadout
+ id: LoadoutServiceHeadChefOrion
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefHead
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingHeadHatChefOrion
+
+- type: loadout
+ id: LoadoutServiceOuterChefNt
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefOuter
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingOuterJacketChefNt
+
+- type: loadout
+ id: LoadoutServiceOuterChefIdris
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefOuter
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingOuterJacketChefIdris
+
+- type: loadout
+ id: LoadoutServiceOuterChefOrion
+ category: JobsServiceChef
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutChefOuter
+ - !type:CharacterJobRequirement
+ jobs:
+ - Chef
+ items:
+ - ClothingOuterJacketChefOrion
+
+# Janitor
+- type: loadout
+ id: LoadoutServiceJumpsuitJanitorNt
+ category: JobsServiceJanitor
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutJanitorUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Janitor
+ items:
+ - ClothingUniformJumpsuitJanitorNt
+
+- type: loadout
+ id: LoadoutServiceJumpsuitJanitorIdris
+ category: JobsServiceJanitor
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutJanitorUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Janitor
+ items:
+ - ClothingUniformJumpsuitJanitorIdris
+
+- type: loadout
+ id: LoadoutServiceJumpsuitJanitorOrion
+ category: JobsServiceJanitor
+ cost: 0
+ exclusive: true
+ requirements:
+ - !type:CharacterItemGroupRequirement
+ group: LoadoutJanitorUniforms
+ - !type:CharacterJobRequirement
+ jobs:
+ - Janitor
+ items:
+ - ClothingUniformJumpsuitJanitorOrion
\ No newline at end of file
diff --git a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml
index 579c57ced4..2764255824 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 d898124b77..c85255f814 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 c1ca2cd60b..fd77f19dcc 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 7f1b26d9cb..0000000000
--- 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 7e2a935f90..0000000000
--- 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 461d9bf136..0000000000
--- 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 b4d2b54779..0000000000
--- 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 ceb87bbaa1..b85cfef87a 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 ab13dc4573..2c0e1eeec3 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/Traits/physical.yml b/Resources/Prototypes/Traits/physical.yml
index 8debf7ffbf..ec8760a095 100644
--- a/Resources/Prototypes/Traits/physical.yml
+++ b/Resources/Prototypes/Traits/physical.yml
@@ -356,6 +356,10 @@
category: Physical
points: -4
requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
- !type:CharacterSpeciesRequirement
species:
- Human # Entirely arbitrary, I've decided I want a trait unique to humans. Since they don't normally get anything exciting.
@@ -390,6 +394,10 @@
category: Physical
points: -4
requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
- !type:CharacterSpeciesRequirement
inverted: true
species:
@@ -401,3 +409,183 @@
productionLength: 2
entityProduced: MaterialWebSilk1
hungerCost: 4
+
+- type: trait
+ id: BionicArm
+ category: Physical
+ points: -9
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ components:
+ - type: Prying
+ speedModifier: 1
+ pryPowered: true
+ force: true
+
+- type: trait
+ id: PlateletFactories
+ category: Physical
+ points: -10
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - IPC
+ componentRemovals:
+ - PassiveDamage
+ components:
+ - type: PassiveDamage
+ allowedStates:
+ - Alive
+ - Critical
+ damageCap: 200
+ damage:
+ groups:
+ Brute: -0.07
+ Burn: -0.07
+ Airloss: -0.07
+ Toxin: -0.07
+ Genetic: -0.07
+
+- type: trait
+ id: DermalArmor
+ category: Physical
+ points: -9
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterSpeciesRequirement
+ species:
+ - Human
+ componentRemovals:
+ - Damageable
+ components:
+ - type: Damageable
+ damageModifierSet: DermalArmor
+
+- type: trait
+ id: CyberEyes
+ category: Physical
+ points: -8
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterTraitRequirement
+ inverted: true
+ traits:
+ - Photophobia
+ - Blindness
+ - Nearsighted
+ componentRemovals:
+ - Flashable
+ components:
+ - type: FlashImmunity
+ - type: EyeProtection
+ - type: CyberEyes
+
+- type: trait
+ id: CyberEyesSecurity
+ category: Physical
+ points: -1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Security
+ - !type:CharacterTraitRequirement
+ traits:
+ - CyberEyes
+ - !type:CharacterTraitRequirement
+ inverted: true
+ traits:
+ - CyberEyesOmni
+ components:
+ - type: ShowSecurityIcons
+
+- type: trait
+ id: CyberEyesMedical
+ category: Physical
+ points: -1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterTraitRequirement
+ traits:
+ - CyberEyes
+ - !type:CharacterTraitRequirement
+ inverted: true
+ traits:
+ - CyberEyesDiagnostic
+ - CyberEyesOmni
+ components:
+ - type: ShowHealthBars
+ damageContainers:
+ - Biological
+
+- type: trait
+ id: CyberEyesDiagnostic
+ category: Physical
+ points: -1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterTraitRequirement
+ traits:
+ - CyberEyes
+ - !type:CharacterTraitRequirement
+ inverted: true
+ traits:
+ - CyberEyesMedical
+ - CyberEyesOmni
+ components:
+ - type: ShowHealthBars
+ damageContainers:
+ - Inorganic
+ - Silicon
+
+- type: trait
+ id: CyberEyesOmni
+ category: Physical
+ points: -3
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Prisoner # Bionics should be "Confiscated" from long term prisoners.
+ - !type:CharacterDepartmentRequirement
+ departments:
+ - Security
+ - !type:CharacterTraitRequirement
+ traits:
+ - CyberEyes
+ - !type:CharacterTraitRequirement
+ inverted: true
+ traits:
+ - CyberEyesMedical
+ - CyberEyesDiagnostic
+ - CyberEyesSecurity
+ components:
+ - type: ShowSecurityIcons
+ - type: ShowHealthBars
+ damageContainers:
+ - Biological
+ - Inorganic
+ - Silicon
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index cc81a2ed64..77cc4f372f 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 0000000000..5734abb6fd
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 293870d3a3..4a4ba3352f 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 0000000000..8ba33c95b5
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 0000000000..085b787410
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 0000000000..d08489cec8
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 0000000000..b35a2acc0f
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 0000000000..2e9bfc3617
--- /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 0000000000..acd0d0577f
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 0000000000..1c798c4075
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 0000000000..0917000cbe
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 0000000000..f3974ab116
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 0000000000..ccbc87cf3b
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 0000000000..ccbc87cf3b
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 0000000000..1cacaf1921
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 0000000000..ac5345ba1a
--- /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 0000000000..9c5a74ad10
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 0000000000..fc03165b57
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 0000000000..2ef4ee7233
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 0000000000..87c6d812ec
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 0000000000..c17a68eb35
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 0000000000..59dc5f13ed
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 0000000000..dd7b21e167
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 0000000000..427389f40d
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 0000000000..8e50ef04f9
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 0000000000..464c22d1a7
--- /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
diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml
index 33b4166161..fdc872887e 100644
--- a/Resources/keybinds.yml
+++ b/Resources/keybinds.yml
@@ -264,6 +264,10 @@ binds:
- function: ToggleStanding
type: State
key: R
+- function: ToggleCrawlingUnder
+ type: State
+ mod1: Shift
+ key: R
- function: ShowDebugConsole
type: State
key: Tilde