diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs
new file mode 100644
index 0000000000..11c7ab9d5f
--- /dev/null
+++ b/Content.Benchmarks/ComponentQueryBenchmark.cs
@@ -0,0 +1,273 @@
+#nullable enable
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Shared.Clothing.Components;
+using Content.Shared.Doors.Components;
+using Content.Shared.Item;
+using Robust.Server.GameObjects;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Random;
+
+namespace Content.Benchmarks;
+
+///
+/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
+/// subscriptions
+///
+[Virtual]
+[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+[CategoriesColumn]
+public class ComponentQueryBenchmark
+{
+ public const string Map = "Maps/atlas.yml";
+
+ private TestPair _pair = default!;
+ private IEntityManager _entMan = default!;
+ private MapId _mapId = new(10);
+ private EntityQuery _itemQuery;
+ private EntityQuery _clothingQuery;
+ private EntityQuery _mapQuery;
+ private EntityUid[] _items = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ ProgramShared.PathOffset = "../../../../";
+ PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
+
+ _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
+ _entMan = _pair.Server.ResolveDependency();
+
+ _itemQuery = _entMan.GetEntityQuery();
+ _clothingQuery = _entMan.GetEntityQuery();
+ _mapQuery = _entMan.GetEntityQuery();
+
+ _pair.Server.ResolveDependency().SetSeed(42);
+ _pair.Server.WaitPost(() =>
+ {
+ var success = _entMan.System().TryLoad(_mapId, Map, out _);
+ if (!success)
+ throw new Exception("Map load failed");
+ _pair.Server.MapMan.DoMapInitialize(_mapId);
+ }).GetAwaiter().GetResult();
+
+ _items = new EntityUid[_entMan.Count()];
+ var i = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out var uid, out _))
+ {
+ _items[i++] = uid;
+ }
+ }
+
+ [GlobalCleanup]
+ public async Task Cleanup()
+ {
+ await _pair.DisposeAsync();
+ PoolManager.Shutdown();
+ }
+
+ #region TryComp
+
+ ///
+ /// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
+ ///
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("TryComp")]
+ public int TryComp()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ if (_clothingQuery.TryGetComponent(uid, out var clothing))
+ hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Variant of that is meant to always fail to get a component.
+ ///
+ [Benchmark]
+ [BenchmarkCategory("TryComp")]
+ public int TryCompFail()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ if (_mapQuery.TryGetComponent(uid, out var map))
+ hashCode = HashCode.Combine(hashCode, map.GetHashCode());
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Variant of that is meant to always succeed getting a component.
+ ///
+ [Benchmark]
+ [BenchmarkCategory("TryComp")]
+ public int TryCompSucceed()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ if (_itemQuery.TryGetComponent(uid, out var item))
+ hashCode = HashCode.Combine(hashCode, item.GetHashCode());
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Variant of that uses `Resolve()` to try get the component.
+ ///
+ [Benchmark]
+ [BenchmarkCategory("TryComp")]
+ public int Resolve()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ DoResolve(uid, ref hashCode);
+ }
+ return hashCode;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
+ {
+ if (_clothingQuery.Resolve(uid, ref clothing, false))
+ hash = HashCode.Combine(hash, clothing.GetHashCode());
+ }
+
+ #endregion
+
+ #region Enumeration
+
+ [Benchmark]
+ [BenchmarkCategory("Item Enumerator")]
+ public int SingleItemEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out var item))
+ {
+ hashCode = HashCode.Combine(hashCode, item.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Item Enumerator")]
+ public int DoubleItemEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out var item))
+ {
+ hashCode = HashCode.Combine(hashCode, item.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Item Enumerator")]
+ public int TripleItemEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out _, out var xform))
+ {
+ hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Airlock Enumerator")]
+ public int SingleAirlockEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out var airlock))
+ {
+ hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Airlock Enumerator")]
+ public int DoubleAirlockEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out var door))
+ {
+ hashCode = HashCode.Combine(hashCode, door.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Airlock Enumerator")]
+ public int TripleAirlockEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out _, out var xform))
+ {
+ hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ #endregion
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Events")]
+ public int StructEvents()
+ {
+ var ev = new QueryBenchEvent();
+ foreach (var uid in _items)
+ {
+ _entMan.EventBus.RaiseLocalEvent(uid, ref ev);
+ }
+
+ return ev.HashCode;
+ }
+}
+
+[ByRefEvent]
+public struct QueryBenchEvent
+{
+ public int HashCode;
+}
+
+public sealed class QueryBenchSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnEvent);
+ }
+
+ private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
+ {
+ args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
+ }
+}
diff --git a/Content.Benchmarks/EntityQueryBenchmark.cs b/Content.Benchmarks/EntityQueryBenchmark.cs
deleted file mode 100644
index cef6a5e35c..0000000000
--- a/Content.Benchmarks/EntityQueryBenchmark.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-#nullable enable
-using System;
-using System.Threading.Tasks;
-using BenchmarkDotNet.Attributes;
-using Content.IntegrationTests;
-using Content.IntegrationTests.Pair;
-using Content.Shared.Clothing.Components;
-using Content.Shared.Item;
-using Robust.Server.GameObjects;
-using Robust.Shared;
-using Robust.Shared.Analyzers;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Benchmarks;
-
-[Virtual]
-public class EntityQueryBenchmark
-{
- public const string Map = "Maps/atlas.yml";
-
- private TestPair _pair = default!;
- private IEntityManager _entMan = default!;
- private MapId _mapId = new MapId(10);
- private EntityQuery _clothingQuery;
-
- [GlobalSetup]
- public void Setup()
- {
- ProgramShared.PathOffset = "../../../../";
- PoolManager.Startup(null);
-
- _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
- _entMan = _pair.Server.ResolveDependency();
-
- _pair.Server.ResolveDependency().SetSeed(42);
- _pair.Server.WaitPost(() =>
- {
- var success = _entMan.System().TryLoad(_mapId, Map, out _);
- if (!success)
- throw new Exception("Map load failed");
- _pair.Server.MapMan.DoMapInitialize(_mapId);
- }).GetAwaiter().GetResult();
-
- _clothingQuery = _entMan.GetEntityQuery();
-
- // Apparently ~40% of entities are items, and 1 in 6 of those are clothing.
- /*
- var entCount = _entMan.EntityCount;
- var itemCount = _entMan.Count();
- var clothingCount = _entMan.Count();
- var itemRatio = (float) itemCount / entCount;
- var clothingRatio = (float) clothingCount / entCount;
- Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}.");
- */
- }
-
- [GlobalCleanup]
- public async Task Cleanup()
- {
- await _pair.DisposeAsync();
- PoolManager.Shutdown();
- }
-
- [Benchmark]
- public int HasComponent()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_entMan.HasComponent(uid))
- hashCode = HashCode.Combine(hashCode, uid.Id);
- }
-
- return hashCode;
- }
-
- [Benchmark]
- public int HasComponentQuery()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_clothingQuery.HasComponent(uid))
- hashCode = HashCode.Combine(hashCode, uid.Id);
- }
-
- return hashCode;
- }
-
- [Benchmark]
- public int TryGetComponent()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing))
- hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
- }
-
- return hashCode;
- }
-
- [Benchmark]
- public int TryGetComponentQuery()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_clothingQuery.TryGetComponent(uid, out var clothing))
- hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
- }
-
- return hashCode;
- }
-
- ///
- /// Enumerate all entities with both an item and clothing component.
- ///
- [Benchmark]
- public int Enumerator()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var _, out var clothing))
- {
- hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
- }
-
- return hashCode;
- }
-}
diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs
index 261e164f17..a3319e3067 100644
--- a/Content.Benchmarks/MapLoadBenchmark.cs
+++ b/Content.Benchmarks/MapLoadBenchmark.cs
@@ -26,7 +26,7 @@ public class MapLoadBenchmark
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
- PoolManager.Startup(null);
+ PoolManager.Startup();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var server = _pair.Server;
diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs
index c7f22bdb0c..0b4dd90762 100644
--- a/Content.Benchmarks/PvsBenchmark.cs
+++ b/Content.Benchmarks/PvsBenchmark.cs
@@ -49,7 +49,7 @@ public class PvsBenchmark
#if !DEBUG
ProgramShared.PathOffset = "../../../../";
#endif
- PoolManager.Startup(null);
+ PoolManager.Startup();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency();
diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
index 8512107b69..0638d945aa 100644
--- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
+++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
@@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
- PoolManager.Startup(null);
+ PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
diff --git a/Content.Client/FlavorText/FlavorText.xaml.cs b/Content.Client/FlavorText/FlavorText.xaml.cs
index ffcf653f11..91b59046a4 100644
--- a/Content.Client/FlavorText/FlavorText.xaml.cs
+++ b/Content.Client/FlavorText/FlavorText.xaml.cs
@@ -17,7 +17,7 @@ namespace Content.Client.FlavorText
var loc = IoCManager.Resolve();
CFlavorTextInput.Placeholder = new Rope.Leaf(loc.GetString("flavor-text-placeholder"));
- CFlavorTextInput.OnKeyBindDown += _ => FlavorTextChanged();
+ CFlavorTextInput.OnTextChanged += _ => FlavorTextChanged();
}
public void FlavorTextChanged()
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index a575f1ba51..6fa416ed59 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -215,6 +215,7 @@ namespace Content.Client.Options.UI.Tabs
AddButton(ContentKeyFunctions.OpenInventoryMenu);
AddButton(ContentKeyFunctions.OpenAHelp);
AddButton(ContentKeyFunctions.OpenActionsMenu);
+ AddButton(ContentKeyFunctions.OpenEmotesMenu);
AddButton(ContentKeyFunctions.ToggleRoundEndSummaryWindow);
AddButton(ContentKeyFunctions.OpenEntitySpawnWindow);
AddButton(ContentKeyFunctions.OpenSandboxWindow);
diff --git a/Content.IntegrationTests/PoolManager.Prototypes.cs b/Content.IntegrationTests/PoolManager.Prototypes.cs
index 760e8b1d37..eb7518ea15 100644
--- a/Content.IntegrationTests/PoolManager.Prototypes.cs
+++ b/Content.IntegrationTests/PoolManager.Prototypes.cs
@@ -15,11 +15,8 @@ public static partial class PoolManager
| BindingFlags.Public
| BindingFlags.DeclaredOnly;
- private static void DiscoverTestPrototypes(Assembly? assembly = null)
+ private static void DiscoverTestPrototypes(Assembly assembly)
{
- assembly ??= typeof(PoolManager).Assembly;
- _testPrototypes.Clear();
-
foreach (var type in assembly.GetTypes())
{
foreach (var field in type.GetFields(Flags))
diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs
index b544fe2854..25e6c7ef26 100644
--- a/Content.IntegrationTests/PoolManager.cs
+++ b/Content.IntegrationTests/PoolManager.cs
@@ -42,6 +42,8 @@ public static partial class PoolManager
private static bool _dead;
private static Exception? _poolFailureReason;
+ private static HashSet _contentAssemblies = default!;
+
public static async Task<(RobustIntegrationTest.ServerIntegrationInstance, PoolTestLogHandler)> GenerateServer(
PoolSettings poolSettings,
TextWriter testOut)
@@ -54,12 +56,7 @@ public static partial class PoolManager
LoadConfigAndUserData = false,
LoadContentResources = !poolSettings.NoLoadContent,
},
- ContentAssemblies = new[]
- {
- typeof(Shared.Entry.EntryPoint).Assembly,
- typeof(Server.Entry.EntryPoint).Assembly,
- typeof(PoolManager).Assembly
- }
+ ContentAssemblies = _contentAssemblies.ToArray()
};
var logHandler = new PoolTestLogHandler("SERVER");
@@ -140,7 +137,7 @@ public static partial class PoolManager
{
typeof(Shared.Entry.EntryPoint).Assembly,
typeof(Client.Entry.EntryPoint).Assembly,
- typeof(PoolManager).Assembly
+ typeof(PoolManager).Assembly,
}
};
@@ -422,13 +419,26 @@ we are just going to end this here to save a lot of time. This is the exception
///
/// Initialize the pool manager.
///
- /// Assembly to search for to discover extra test prototypes.
- public static void Startup(Assembly? assembly)
+ /// Assemblies to search for to discover extra prototypes and systems.
+ public static void Startup(params Assembly[] extraAssemblies)
{
if (_initialized)
throw new InvalidOperationException("Already initialized");
_initialized = true;
- DiscoverTestPrototypes(assembly);
+ _contentAssemblies =
+ [
+ typeof(Shared.Entry.EntryPoint).Assembly,
+ typeof(Server.Entry.EntryPoint).Assembly,
+ typeof(PoolManager).Assembly
+ ];
+ _contentAssemblies.UnionWith(extraAssemblies);
+
+ _testPrototypes.Clear();
+ DiscoverTestPrototypes(typeof(PoolManager).Assembly);
+ foreach (var assembly in extraAssemblies)
+ {
+ DiscoverTestPrototypes(assembly);
+ }
}
}
diff --git a/Content.IntegrationTests/PoolManagerTestEventHandler.cs b/Content.IntegrationTests/PoolManagerTestEventHandler.cs
index d37dffff50..3b26d6637f 100644
--- a/Content.IntegrationTests/PoolManagerTestEventHandler.cs
+++ b/Content.IntegrationTests/PoolManagerTestEventHandler.cs
@@ -13,7 +13,7 @@ public sealed class PoolManagerTestEventHandler
[OneTimeSetUp]
public void Setup()
{
- PoolManager.Startup(typeof(PoolManagerTestEventHandler).Assembly);
+ PoolManager.Startup();
// If the tests seem to be stuck, we try to end it semi-nicely
_ = Task.Delay(MaximumTotalTestingTimeLimit).ContinueWith(_ =>
{
diff --git a/Content.MapRenderer/Program.cs b/Content.MapRenderer/Program.cs
index 43dcff2c02..7314119108 100644
--- a/Content.MapRenderer/Program.cs
+++ b/Content.MapRenderer/Program.cs
@@ -29,7 +29,7 @@ namespace Content.MapRenderer
if (!CommandLineArguments.TryParse(args, out var arguments))
return;
- PoolManager.Startup(null);
+ PoolManager.Startup();
if (arguments.Maps.Count == 0)
{
Console.WriteLine("Didn't specify any maps to paint! Loading the map list...");
diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs
index c845b17230..0c60e40dac 100644
--- a/Content.Server/Bible/BibleSystem.cs
+++ b/Content.Server/Bible/BibleSystem.cs
@@ -241,7 +241,7 @@ namespace Content.Server.Bible
// If this is going to use a ghost role mob spawner, attach it to the bible.
if (HasComp(familiar))
{
- _popupSystem.PopupEntity(Loc.GetString("bible-summon-requested"), user, PopupType.Medium);
+ _popupSystem.PopupEntity(Loc.GetString("bible-summon-requested"), user, user, PopupType.Medium);
_transform.SetParent(familiar, uid);
}
component.AlreadySummoned = true;
diff --git a/Content.Server/Paper/PaperRandomStoryComponent.cs b/Content.Server/Paper/PaperRandomStoryComponent.cs
index 7c5744f087..b8e07f0ee8 100644
--- a/Content.Server/Paper/PaperRandomStoryComponent.cs
+++ b/Content.Server/Paper/PaperRandomStoryComponent.cs
@@ -1,14 +1,17 @@
+using Content.Shared.StoryGen;
+using Robust.Shared.Prototypes;
+
namespace Content.Server.Paper;
///
-/// Adds randomly generated stories to Paper component
+/// Adds a randomly generated story to the content of a
///
[RegisterComponent, Access(typeof(PaperRandomStorySystem))]
public sealed partial class PaperRandomStoryComponent : Component
{
+ ///
+ /// The ID to use for story generation.
+ ///
[DataField]
- public List? StorySegments;
-
- [DataField]
- public string StorySeparator = " ";
+ public ProtoId Template;
}
diff --git a/Content.Server/Paper/PaperRandomStorySystem.cs b/Content.Server/Paper/PaperRandomStorySystem.cs
index e7712009c2..156718f545 100644
--- a/Content.Server/Paper/PaperRandomStorySystem.cs
+++ b/Content.Server/Paper/PaperRandomStorySystem.cs
@@ -1,11 +1,11 @@
-using Content.Server.RandomMetadata;
+using Content.Shared.StoryGen;
namespace Content.Server.Paper;
public sealed class PaperRandomStorySystem : EntitySystem
{
-
- [Dependency] private readonly RandomMetadataSystem _randomMeta = default!;
+ [Dependency] private readonly StoryGeneratorSystem _storyGen = default!;
+ [Dependency] private readonly PaperSystem _paper = default!;
public override void Initialize()
{
@@ -19,11 +19,9 @@ public sealed class PaperRandomStorySystem : EntitySystem
if (!TryComp(paperStory, out var paper))
return;
- if (paperStory.Comp.StorySegments == null)
+ if (!_storyGen.TryGenerateStoryFromTemplate(paperStory.Comp.Template, out var story))
return;
- var story = _randomMeta.GetRandomFromSegments(paperStory.Comp.StorySegments, paperStory.Comp.StorySeparator);
-
- paper.Content += $"\n{story}";
+ _paper.SetContent(paperStory.Owner, story, paper);
}
}
diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
index 5d52bde377..0afd86679b 100644
--- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
+++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
@@ -240,7 +240,8 @@ namespace Content.Server.Power.Pow3r
}
}
- if (unmet <= 0 || totalBatterySupply <= 0)
+ // Return if normal supplies met all demand or there are no supplying batteries
+ if (unmet <= 0 || totalMaxBatterySupply <= 0)
return;
// Target output capacity for batteries
@@ -275,8 +276,8 @@ namespace Content.Server.Power.Pow3r
battery.SupplyRampTarget = battery.MaxEffectiveSupply * relativeTargetBatteryOutput - battery.CurrentReceiving * battery.Efficiency;
- DebugTools.Assert(battery.SupplyRampTarget + battery.CurrentReceiving * battery.Efficiency <= battery.LoadingNetworkDemand
- || MathHelper.CloseToPercent(battery.SupplyRampTarget + battery.CurrentReceiving * battery.Efficiency, battery.LoadingNetworkDemand, 0.001));
+ DebugTools.Assert(battery.MaxEffectiveSupply * relativeTargetBatteryOutput <= battery.LoadingNetworkDemand
+ || MathHelper.CloseToPercent(battery.MaxEffectiveSupply * relativeTargetBatteryOutput, battery.LoadingNetworkDemand, 0.001));
}
}
diff --git a/Content.Server/Speech/Components/ReplacementAccentComponent.cs b/Content.Server/Speech/Components/ReplacementAccentComponent.cs
index e7f57b80d0..037da72029 100644
--- a/Content.Server/Speech/Components/ReplacementAccentComponent.cs
+++ b/Content.Server/Speech/Components/ReplacementAccentComponent.cs
@@ -22,6 +22,12 @@ namespace Content.Server.Speech.Components
///
[DataField("wordReplacements")]
public Dictionary? WordReplacements;
+
+ ///
+ /// Allows you to substitute words, not always, but with some chance
+ ///
+ [DataField]
+ public float ReplacementChance = 1f;
}
///
@@ -33,10 +39,5 @@ namespace Content.Server.Speech.Components
[DataField("accent", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)]
public string Accent = default!;
- ///
- /// Allows you to substitute words, not always, but with some chance
- ///
- [DataField]
- public float ReplacementChance = 1f;
}
}
diff --git a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs
index da198bcc12..d81d913a36 100644
--- a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs
+++ b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs
@@ -24,9 +24,6 @@ namespace Content.Server.Speech.EntitySystems
private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args)
{
- if (!_random.Prob(component.ReplacementChance))
- return;
-
args.Message = ApplyReplacements(args.Message, component.Accent);
}
@@ -39,6 +36,9 @@ namespace Content.Server.Speech.EntitySystems
if (!_proto.TryIndex(accent, out var prototype))
return message;
+ if (!_random.Prob(prototype.ReplacementChance))
+ return message;
+
// Prioritize fully replacing if that exists--
// ideally both aren't used at the same time (but we don't have a way to enforce that in serialization yet)
if (prototype.FullReplacements != null)
diff --git a/Content.Shared/Hands/Components/HandHelpers.cs b/Content.Shared/Hands/Components/HandHelpers.cs
index 11fff6d9c8..aecf3a6936 100644
--- a/Content.Shared/Hands/Components/HandHelpers.cs
+++ b/Content.Shared/Hands/Components/HandHelpers.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using Content.Shared.Hands.EntitySystems;
namespace Content.Shared.Hands.Components;
@@ -20,6 +21,15 @@ public static class HandHelpers
///
public static int CountFreeHands(this HandsComponent component) => component.Hands.Values.Count(hand => hand.IsEmpty);
+ ///
+ /// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so
+ /// cache it instead of accessing this multiple times.
+ ///
+ public static int CountFreeableHands(this Entity component, SharedHandsSystem system)
+ {
+ return system.CountFreeableHands(component);
+ }
+
///
/// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache
/// it instead of accessing this multiple times.
diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
index fd732009e9..e48aafeab5 100644
--- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
+++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
@@ -5,7 +5,6 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory.VirtualItem;
-using Content.Shared.Item;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Input.Binding;
@@ -299,4 +298,16 @@ public abstract partial class SharedHandsSystem
return hands.Hands.TryGetValue(handId, out hand);
}
+
+ public int CountFreeableHands(Entity hands)
+ {
+ var freeable = 0;
+ foreach (var hand in hands.Comp.Hands.Values)
+ {
+ if (hand.IsEmpty || CanDropHeld(hands, hand))
+ freeable++;
+ }
+
+ return freeable;
+ }
}
diff --git a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs
index e45530e458..b31cc75576 100644
--- a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs
+++ b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs
@@ -4,6 +4,7 @@ using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
+using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
@@ -29,6 +30,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
[ValidatePrototypeId]
private const string VirtualItem = "VirtualItem";
@@ -71,23 +73,53 @@ public abstract class SharedVirtualItemSystem : EntitySystem
}
#region Hands
+
///
/// Spawns a virtual item in a empty hand
///
/// The entity we will make a virtual entity copy of
/// The entity that we want to insert the virtual entity
- public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user)
+ /// Whether or not to try and drop other items to make space
+ public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, bool dropOthers = false)
{
- return TrySpawnVirtualItemInHand(blockingEnt, user, out _);
+ return TrySpawnVirtualItemInHand(blockingEnt, user, out _, dropOthers);
}
- ///
- public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem)
+ ///
+ public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false)
{
- if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem) || !_handsSystem.TryGetEmptyHand(user, out var hand))
+ virtualItem = null;
+ if (!_handsSystem.TryGetEmptyHand(user, out var empty))
+ {
+ if (!dropOthers)
+ return false;
+
+ foreach (var hand in _handsSystem.EnumerateHands(user))
+ {
+ if (hand.HeldEntity is not { } held)
+ continue;
+
+ if (held == blockingEnt || HasComp(held))
+ continue;
+
+ if (!_handsSystem.TryDrop(user, hand))
+ continue;
+
+ if (!TerminatingOrDeleted(held))
+ _popup.PopupClient(Loc.GetString("virtual-item-dropped-other", ("dropped", held)), user, user);
+
+ empty = hand;
+ break;
+ }
+ }
+
+ if (empty == null)
return false;
- _handsSystem.DoPickup(user, hand, virtualItem.Value);
+ if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem))
+ return false;
+
+ _handsSystem.DoPickup(user, empty, virtualItem.Value);
return true;
}
@@ -120,6 +152,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
/// The entity we will make a virtual entity copy of
/// The entity that we want to insert the virtual entity
/// The slot to which we will insert the virtual entity (could be the "shoes" slot, for example)
+ /// Whether or not to force an equip
public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user, string slot, bool force = false)
{
return TrySpawnVirtualItemInInventory(blockingEnt, user, slot, force, out _);
@@ -140,6 +173,8 @@ public abstract class SharedVirtualItemSystem : EntitySystem
/// that's done check if the found virtual entity is a copy of our matching entity,
/// if it is, delete it
///
+ /// The entity that we want to delete the virtual entity from
+ /// The entity that made the virtual entity
/// Set this param if you have the name of the slot, it avoids unnecessary queries
public void DeleteInSlotMatching(EntityUid user, EntityUid matching, string? slotName = null)
{
@@ -178,6 +213,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
///
/// The entity we will make a virtual entity copy of
/// The entity that we want to insert the virtual entity
+ /// The virtual item, if spawned
public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem)
{
if (_netManager.IsClient)
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index 89d5c9f87c..fad8b5941f 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -384,6 +384,7 @@ namespace Content.Shared.Preferences
if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false;
if (!_traitPreferences.SequenceEqual(other._traitPreferences)) return false;
if (!Loadouts.SequenceEqual(other.Loadouts)) return false;
+ if (FlavorText != other.FlavorText) return false;
return Appearance.MemberwiseEquals(other.Appearance);
}
diff --git a/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs b/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs
new file mode 100644
index 0000000000..51ad85730c
--- /dev/null
+++ b/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs
@@ -0,0 +1,54 @@
+using System.Diagnostics.CodeAnalysis;
+using Robust.Shared.Collections;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Shared.StoryGen;
+
+///
+/// Provides functionality to generate a story from a .
+///
+public sealed partial class StoryGeneratorSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ ///
+ /// Tries to generate a random story using the given template, picking a random word from the referenced
+ /// datasets for each variable and passing them into the localization system with template.
+ /// If is specified, the randomizer will be seeded with it for consistent story generation;
+ /// otherwise the variables will be randomized.
+ /// Fails if the template prototype cannot be loaded.
+ ///
+ /// true if the template was loaded, otherwise false.
+ public bool TryGenerateStoryFromTemplate(ProtoId template, [NotNullWhen(true)] out string? story, int? seed = null)
+ {
+ // Get the story template prototype from the ID
+ if (!_protoMan.TryIndex(template, out var templateProto))
+ {
+ story = null;
+ return false;
+ }
+
+ // If given a seed, use it
+ if (seed != null)
+ _random.SetSeed(seed.Value);
+
+ // Pick values for all of the variables in the template
+ var variables = new ValueList<(string, object)>(templateProto.Variables.Count);
+ foreach (var (name, list) in templateProto.Variables)
+ {
+ // Get the prototype for the world list dataset
+ if (!_protoMan.TryIndex(list, out var listProto))
+ continue; // Missed one, but keep going with the rest of the story
+
+ // Pick a random word from the dataset and localize it
+ var chosenWord = Loc.GetString(_random.Pick(listProto.Values));
+ variables.Add((name, chosenWord));
+ }
+
+ // Pass the variables to the localization system and build the story
+ story = Loc.GetString(templateProto.LocId, variables.ToArray());
+ return true;
+ }
+}
diff --git a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs
new file mode 100644
index 0000000000..7f6afacccc
--- /dev/null
+++ b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs
@@ -0,0 +1,33 @@
+using Content.Shared.Dataset;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.StoryGen;
+
+///
+/// Prototype for a story template that can be filled in with words chosen from s.
+///
+[Serializable, Prototype("storyTemplate")]
+public sealed partial class StoryTemplatePrototype : IPrototype
+{
+ ///
+ /// Identifier for this prototype instance.
+ ///
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ ///
+ /// Localization ID of the Fluent string that forms the structure of this story.
+ ///
+ [DataField(required: true)]
+ public LocId LocId { get; } = default!;
+
+ ///
+ /// Dictionary containing the name of each variable to pass to the template and the ID of the
+ /// from which a random entry will be selected as its value.
+ /// For example, name: book_character will pick a random entry from the book_character
+ /// dataset which can then be used in the template by {$name}.
+ ///
+ [DataField]
+ public Dictionary> Variables { get; } = default!;
+}
diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs
index 3ac8835dd0..a6d27ac545 100644
--- a/Content.Shared/UserInterface/ActivatableUISystem.cs
+++ b/Content.Shared/UserInterface/ActivatableUISystem.cs
@@ -215,7 +215,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser)
{
var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity));
- _popupSystem.PopupEntity(message, uiEntity, user);
+ _popupSystem.PopupClient(message, uiEntity, user);
if (_uiSystem.IsUiOpen(uiEntity, aui.Key))
return true;
diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs
index b765566f44..cee6c65fa1 100644
--- a/Content.Shared/Wieldable/WieldableSystem.cs
+++ b/Content.Shared/Wieldable/WieldableSystem.cs
@@ -161,7 +161,7 @@ public sealed class WieldableSystem : EntitySystem
return false;
}
- if (hands.CountFreeHands() < component.FreeHandsRequired)
+ if (_handsSystem.CountFreeableHands((user, hands)) < component.FreeHandsRequired)
{
if (!quiet)
{
@@ -202,9 +202,21 @@ public sealed class WieldableSystem : EntitySystem
if (component.WieldSound != null)
_audioSystem.PlayPredicted(component.WieldSound, used, user);
+ var virtuals = new List();
for (var i = 0; i < component.FreeHandsRequired; i++)
{
- _virtualItemSystem.TrySpawnVirtualItemInHand(used, user);
+ if (_virtualItemSystem.TrySpawnVirtualItemInHand(used, user, out var virtualItem, true))
+ {
+ virtuals.Add(virtualItem.Value);
+ continue;
+ }
+
+ foreach (var existingVirtual in virtuals)
+ {
+ QueueDel(existingVirtual);
+ }
+
+ return false;
}
if (TryComp(used, out UseDelayComponent? useDelay)
diff --git a/Content.YAMLLinter/Program.cs b/Content.YAMLLinter/Program.cs
index 7f0b740fe8..32078faeef 100644
--- a/Content.YAMLLinter/Program.cs
+++ b/Content.YAMLLinter/Program.cs
@@ -17,7 +17,7 @@ namespace Content.YAMLLinter
{
private static async Task Main(string[] _)
{
- PoolManager.Startup(null);
+ PoolManager.Startup();
var stopwatch = new Stopwatch();
stopwatch.Start();
diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml
index 7e598ba4b5..9dffda8901 100644
--- a/Resources/Changelog/Changelog.yml
+++ b/Resources/Changelog/Changelog.yml
@@ -1,43 +1,4 @@
Entries:
-- author: Just_Art
- changes:
- - message: Added bob 5 hair!
- type: Add
- - message: Added long hair with bundles!
- type: Add
- id: 6100
- time: '2024-03-06T01:42:05.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/25772
-- author: Errant
- changes:
- - message: Debug coordinates are now automatically enabled on the F3 overlay upon
- readmin.
- type: Fix
- id: 6101
- time: '2024-03-06T01:43:51.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/25063
-- author: metalgearsloth
- changes:
- - message: Pod launches will now be offset slightly.
- type: Add
- id: 6102
- time: '2024-03-06T01:44:26.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/25855
-- author: Brandon-Huu
- changes:
- - message: The Syndicate is now supplying barber scissors in the Hristov bundle
- to make your disguises even balde- better.
- type: Tweak
- id: 6103
- time: '2024-03-06T02:03:08.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/25695
-- author: Tayrtahn
- changes:
- - message: Flasks can once again be put into dispensers.
- type: Fix
- id: 6104
- time: '2024-03-06T17:19:01.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/25883
- author: Doctor-Cpu
changes:
- message: Cargo palllets can no longer be ordered from cargo request terminal
@@ -3868,3 +3829,42 @@
id: 6599
time: '2024-05-17T07:51:28.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/27960
+- author: lzk228
+ changes:
+ - message: Fixed bug with pathological liar and zombie accents.
+ type: Fix
+ id: 6600
+ time: '2024-05-17T16:33:22.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/28049
+- author: YuNii
+ changes:
+ - message: Added the ability to re-bind the emote menu.
+ type: Add
+ id: 6601
+ time: '2024-05-17T19:46:45.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/28083
+- author: benjamin-burges
+ changes:
+ - message: Bible summon messages are now only visible to the chaplain using the
+ Bible.
+ type: Tweak
+ id: 6602
+ time: '2024-05-18T14:23:17.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/28104
+- author: Aquif
+ changes:
+ - message: Changing your character's description now properly updates the save button.
+ type: Fix
+ - message: The character description editor does not discard your last edit anymore.
+ type: Fix
+ id: 6603
+ time: '2024-05-19T00:23:45.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/28122
+- author: DrSmugleaf
+ changes:
+ - message: Attempting to dual wield something will now automatically drop the item
+ in your other hand.
+ type: Tweak
+ id: 6604
+ time: '2024-05-19T01:35:46.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/27975
diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt
index 5344b6a2db..172dd2ad37 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, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, casperr04, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, 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, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, DuskyJay, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, Jark255, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, K-Dynamic, Kadeo64, KaiShibaa, kalane15, kalanosh, Keer-Sar, KEEYNy, Keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Kmc2000, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, Subversionary, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem
+0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, casperr04, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, 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, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, DuskyJay, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, Jark255, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, KEEYNy, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, Subversionary, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem
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 1c0c0005b4..d07f9127a4 100644
--- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
@@ -173,6 +173,7 @@ ui-options-function-open-crafting-menu = Open crafting menu
ui-options-function-open-inventory-menu = Open inventory
ui-options-function-open-a-help = Open admin help
ui-options-function-open-abilities-menu = Open action menu
+ui-options-function-open-emotes-menu = Open emotes menu
ui-options-function-toggle-round-end-summary-window = Toggle round end summary window
ui-options-function-open-entity-spawn-window = Open entity spawn menu
ui-options-function-open-sandbox-window = Open sandbox menu
diff --git a/Resources/Locale/en-US/paper/story-generation.ftl b/Resources/Locale/en-US/paper/story-generation.ftl
index bcd1c8901e..94ecbc3caa 100644
--- a/Resources/Locale/en-US/paper/story-generation.ftl
+++ b/Resources/Locale/en-US/paper/story-generation.ftl
@@ -86,7 +86,7 @@ story-gen-book-character29 = space dragon
story-gen-book-character30 = revolutionary
story-gen-book-character31 = nuclear operative
story-gen-book-character32 = narsie cultist
-story-gen-book-character33 = ratwar cultist
+story-gen-book-character33 = ratvar cultist
story-gen-book-character34 = greytider
story-gen-book-character35 = arachnid
story-gen-book-character36 = vox
@@ -98,7 +98,7 @@ story-gen-book-character40 = slime
story-gen-book-character-trait1 = stupid
story-gen-book-character-trait2 = smart
story-gen-book-character-trait3 = funny
-story-gen-book-character-trait4 = attractive
+story-gen-book-character-trait4 = attractive
story-gen-book-character-trait5 = charming
story-gen-book-character-trait6 = nasty
story-gen-book-character-trait7 = dying
@@ -113,7 +113,7 @@ story-gen-book-character-trait15 = сharismatic
story-gen-book-character-trait16 = stoic
story-gen-book-character-trait17 = cute
story-gen-book-character-trait18 = dwarven
-story-gen-book-character-trait19 = beer-smelling
+story-gen-book-character-trait19 = beer-smelling
story-gen-book-character-trait20 = joyful
story-gen-book-character-trait21 = painfully beautiful
story-gen-book-character-trait22 = robotic
@@ -121,20 +121,20 @@ story-gen-book-character-trait23 = holographic
story-gen-book-character-trait24 = hysterically laughing
story-gen-book-event1 = a zombie outbreak
-story-gen-book-event2 = a nuclear explosion
+story-gen-book-event2 = a nuclear explosion
story-gen-book-event3 = a mass murder
story-gen-book-event4 = a sudden depressurization
story-gen-book-event5 = a blackout
-story-gen-book-event6 = the starvation of the protagonists
+story-gen-book-event6 = the protagonists nearly starving
story-gen-book-event7 = a wasting illness
story-gen-book-event8 = love at first sight
story-gen-book-event9 = a rush of inspiration
-story-gen-book-event10 = the occurrence of some mystical phenomena
+story-gen-book-event10 = some mystical phenomena
story-gen-book-event11 = divine intervention
story-gen-book-event12 = the characters' own selfish motives
story-gen-book-event13 = an unforeseen deception
-story-gen-book-event14 = the resurrection of one of these characters from the dead
-story-gen-book-event15 = the terrible torture of the protagonist
+story-gen-book-event14 = the resurrection of one of the characters from the dead
+story-gen-book-event15 = the brutal torture of the protagonists
story-gen-book-event16 = the inadvertent loosing of a gravitational singularity
story-gen-book-event17 = a psychic prediction of future events
story-gen-book-event18 = an antimatter explosion
@@ -145,31 +145,31 @@ story-gen-book-event22 = having a quarrel with a close friend
story-gen-book-event23 = the sudden loss of their home in a fiery blaze
story-gen-book-event24 = the loss of a PDA
-story-gen-book-action1 = share in a kiss with a
-story-gen-book-action2 = strangle to death a
-story-gen-book-action3 = manage to blow apart a
-story-gen-book-action4 = manage to win a game of chess against a
-story-gen-book-action5 = narrowly lose a game of chess against a
-story-gen-book-action6 = reveal the hidden secrets of a
-story-gen-book-action7 = manipulate a
-story-gen-book-action8 = sacrifice upon an altar a
-story-gen-book-action9 = attend the wedding of a
-story-gen-book-action10 = join forces to defeat their common enemy, a
-story-gen-book-action11 = are forced to work together to escape a
+story-gen-book-action1 = share in a kiss with
+story-gen-book-action2 = strangle
+story-gen-book-action3 = blow apart
+story-gen-book-action4 = win a game of chess against
+story-gen-book-action5 = lose a game of chess against
+story-gen-book-action6 = reveal the hidden secrets of
+story-gen-book-action7 = manipulate
+story-gen-book-action8 = sacrifice a hamster to
+story-gen-book-action9 = infiltrate the wedding of
+story-gen-book-action10 = join forces to defeat their common enemy,
+story-gen-book-action11 = are forced to work together to escape
story-gen-book-action12 = give a valuable gift to
-story-gen-book-action-trait1 = terribly
-story-gen-book-action-trait2 = disgustingly
+story-gen-book-action-trait1 = clumsily
+story-gen-book-action-trait2 = disgustingly
story-gen-book-action-trait3 = marvelously
story-gen-book-action-trait4 = nicely
story-gen-book-action-trait5 = weirdly
story-gen-book-action-trait6 = amusingly
story-gen-book-action-trait7 = fancifully
story-gen-book-action-trait8 = impressively
-story-gen-book-action-trait9 = irresponsibly
-story-gen-book-action-trait10 = severely
-story-gen-book-action-trait11 = ruthlessly
-story-gen-book-action-trait12 = playfully
+story-gen-book-action-trait9 = irresponsibly
+story-gen-book-action-trait10 = severely
+story-gen-book-action-trait11 = ruthlessly
+story-gen-book-action-trait12 = playfully
story-gen-book-action-trait13 = thoughtfully
story-gen-book-location1 = in an underground complex
@@ -178,7 +178,7 @@ story-gen-book-location3 = while trapped in outer space
story-gen-book-location4 = while in a news office
story-gen-book-location5 = in a hidden garden
story-gen-book-location6 = in the kitchen of a local restaurant
-story-gen-book-location7 = under the counter of the local sports bar
+story-gen-book-location7 = under the counter of the local sports bar
story-gen-book-location8 = in an ancient library
story-gen-book-location9 = while deep in bowels of the space station's maintenance corridors
story-gen-book-location10 = on the bridge of a starship
@@ -192,7 +192,7 @@ story-gen-book-location17 = standing too close to an anomaly
story-gen-book-location18 = while huddling on the evacuation shuttle
story-gen-book-location19 = standing in freshly fallen snow
story-gen-book-location20 = lost in the woods
-story-gen-book-location21 = iin the harsh desert
+story-gen-book-location21 = in the harsh desert
story-gen-book-location22 = worrying about their social media networks
story-gen-book-location23 = atop of a mountain
story-gen-book-location24 = while driving a car
@@ -207,15 +207,15 @@ story-gen-book-location32 = while trapped in a shadow dimension
story-gen-book-location33 = while trying to escape a destroyed space station
story-gen-book-location34 = while sandwiched between a Tesla ball and a gravitational singularity
-story-gen-book-element1 = The plot
-story-gen-book-element2 = The twist
-story-gen-book-element3 = The climax
-story-gen-book-element4 = The final act
-story-gen-book-element5 = The ending
-story-gen-book-element6 = The moral of the story
-story-gen-book-element7 = The theme of this work
-story-gen-book-element8 = The literary style
-story-gen-book-element9 = The illustrations
+story-gen-book-element1 = plot
+story-gen-book-element2 = twist
+story-gen-book-element3 = climax
+story-gen-book-element4 = final act
+story-gen-book-element5 = ending
+story-gen-book-element6 = moral of the story
+story-gen-book-element7 = theme of this work
+story-gen-book-element8 = literary style
+story-gen-book-element9 = artwork
story-gen-book-element-trait1 = terrifying
story-gen-book-element-trait2 = disgusting
diff --git a/Resources/Locale/en-US/storygen/story-template.ftl b/Resources/Locale/en-US/storygen/story-template.ftl
new file mode 100644
index 0000000000..b535e2fd95
--- /dev/null
+++ b/Resources/Locale/en-US/storygen/story-template.ftl
@@ -0,0 +1,4 @@
+story-template-generic =
+ This is { INDEFINITE($bookGenre) } {$bookGenre} about { INDEFINITE($char1Adj) } {$char1Adj} {$char1Type} and { INDEFINITE($char2Adj) } {$char2Adj} {$char2Type}. Due to {$event}, they {$actionTrait} {$action} { INDEFINITE($char3Type) } {$char3Type} {$location}.
+
+ The {$element} is {$elementTrait}.
diff --git a/Resources/Locale/en-US/virtual/virtual-item.ftl b/Resources/Locale/en-US/virtual/virtual-item.ftl
new file mode 100644
index 0000000000..cb91f24cf7
--- /dev/null
+++ b/Resources/Locale/en-US/virtual/virtual-item.ftl
@@ -0,0 +1 @@
+virtual-item-dropped-other = You dropped {THE($dropped)}!
diff --git a/Resources/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml
index 30c739b845..9a801d786d 100644
--- a/Resources/Prototypes/Accents/word_replacements.yml
+++ b/Resources/Prototypes/Accents/word_replacements.yml
@@ -428,6 +428,7 @@
- type: accent
id: liar
+ replacementChance: 0.15
wordReplacements:
liar-word-1: liar-word-replacement-1
liar-word-2: liar-word-replacement-2
@@ -471,4 +472,4 @@
liar-word-39: liar-word-replacement-39
liar-word-40: liar-word-replacement-40
liar-word-41: liar-word-replacement-41
- liar-word-42: liar-word-replacement-42
\ No newline at end of file
+ liar-word-42: liar-word-replacement-42
diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml
index 267f706f3b..ebcd9dfc5e 100644
--- a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml
+++ b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml
@@ -61,7 +61,7 @@
- type: cargoProduct
id: ServiceBureaucracy
icon:
- sprite: Objects/Misc/bureaucracy.rsi
+ sprite: Objects/Misc/pens.rsi
state: pen
product: CrateServiceBureaucracy
cost: 1000
diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml
index 747328f305..c8d6eb4f61 100644
--- a/Resources/Prototypes/Catalog/uplink_catalog.yml
+++ b/Resources/Prototypes/Catalog/uplink_catalog.yml
@@ -298,7 +298,7 @@
id: UplinkExplodingPen
name: uplink-exploding-pen-name
description: uplink-exploding-pen-desc
- icon: { sprite: /Textures/Objects/Misc/bureaucracy.rsi, state: pen }
+ icon: { sprite: /Textures/Objects/Misc/pens.rsi, state: pen }
productEntity: PenExplodingBox
cost:
Telecrystal: 4
@@ -472,7 +472,7 @@
id: UplinkHypopen
name: uplink-hypopen-name
description: uplink-hypopen-desc
- icon: { sprite: /Textures/Objects/Misc/bureaucracy.rsi, state: pen }
+ icon: { sprite: /Textures/Objects/Misc/pens.rsi, state: pen }
productEntity: HypopenBox
cost:
Telecrystal: 6
@@ -1715,4 +1715,4 @@
- !type:BuyerWhitelistCondition
blacklist:
components:
- - SurplusBundle
\ No newline at end of file
+ - SurplusBundle
diff --git a/Resources/Prototypes/Datasets/story_generation.yml b/Resources/Prototypes/Datasets/story_generation.yml
index 1083a6acdb..1a461c7596 100644
--- a/Resources/Prototypes/Datasets/story_generation.yml
+++ b/Resources/Prototypes/Datasets/story_generation.yml
@@ -1,31 +1,31 @@
- type: dataset
- id: book_type
+ id: BookTypes
values:
- - story-gen-book-type1
- - story-gen-book-type2
- - story-gen-book-type3
- - story-gen-book-type4
- - story-gen-book-type5
- - story-gen-book-type6
- - story-gen-book-type7
- - story-gen-book-type8
- - story-gen-book-type9
+ - story-gen-book-type1
+ - story-gen-book-type2
+ - story-gen-book-type3
+ - story-gen-book-type4
+ - story-gen-book-type5
+ - story-gen-book-type6
+ - story-gen-book-type7
+ - story-gen-book-type8
+ - story-gen-book-type9
- story-gen-book-type10
- story-gen-book-type11
- story-gen-book-type12
- type: dataset
- id: book_genre
+ id: BookGenres
values:
- - story-gen-book-genre1
- - story-gen-book-genre2
- - story-gen-book-genre3
- - story-gen-book-genre4
- - story-gen-book-genre5
- - story-gen-book-genre6
- - story-gen-book-genre7
- - story-gen-book-genre8
- - story-gen-book-genre9
+ - story-gen-book-genre1
+ - story-gen-book-genre2
+ - story-gen-book-genre3
+ - story-gen-book-genre4
+ - story-gen-book-genre5
+ - story-gen-book-genre6
+ - story-gen-book-genre7
+ - story-gen-book-genre8
+ - story-gen-book-genre9
- story-gen-book-genre10
- story-gen-book-genre11
- story-gen-book-genre12
@@ -33,17 +33,17 @@
- story-gen-book-genre14
- type: dataset
- id: book_hint_appearance
+ id: BookHintAppearances
values:
- - story-gen-book-appearance1
- - story-gen-book-appearance2
- - story-gen-book-appearance3
- - story-gen-book-appearance4
- - story-gen-book-appearance5
- - story-gen-book-appearance6
- - story-gen-book-appearance7
- - story-gen-book-appearance8
- - story-gen-book-appearance9
+ - story-gen-book-appearance1
+ - story-gen-book-appearance2
+ - story-gen-book-appearance3
+ - story-gen-book-appearance4
+ - story-gen-book-appearance5
+ - story-gen-book-appearance6
+ - story-gen-book-appearance7
+ - story-gen-book-appearance8
+ - story-gen-book-appearance9
- story-gen-book-appearance10
- story-gen-book-appearance11
- story-gen-book-appearance12
@@ -64,17 +64,17 @@
- story-gen-book-appearance27
- type: dataset
- id: book_character
+ id: BookCharacters
values:
- - story-gen-book-character1
- - story-gen-book-character2
- - story-gen-book-character3
- - story-gen-book-character4
- - story-gen-book-character5
- - story-gen-book-character6
- - story-gen-book-character7
- - story-gen-book-character8
- - story-gen-book-character9
+ - story-gen-book-character1
+ - story-gen-book-character2
+ - story-gen-book-character3
+ - story-gen-book-character4
+ - story-gen-book-character5
+ - story-gen-book-character6
+ - story-gen-book-character7
+ - story-gen-book-character8
+ - story-gen-book-character9
- story-gen-book-character10
- story-gen-book-character11
- story-gen-book-character12
@@ -108,17 +108,17 @@
- story-gen-book-character40
- type: dataset
- id: book_character_trait
+ id: BookCharacterTraits
values:
- - story-gen-book-character-trait1
- - story-gen-book-character-trait2
- - story-gen-book-character-trait3
- - story-gen-book-character-trait4
- - story-gen-book-character-trait5
- - story-gen-book-character-trait6
- - story-gen-book-character-trait7
- - story-gen-book-character-trait8
- - story-gen-book-character-trait9
+ - story-gen-book-character-trait1
+ - story-gen-book-character-trait2
+ - story-gen-book-character-trait3
+ - story-gen-book-character-trait4
+ - story-gen-book-character-trait5
+ - story-gen-book-character-trait6
+ - story-gen-book-character-trait7
+ - story-gen-book-character-trait8
+ - story-gen-book-character-trait9
- story-gen-book-character-trait10
- story-gen-book-character-trait11
- story-gen-book-character-trait12
@@ -137,17 +137,17 @@
- type: dataset
- id: book_event
+ id: BookEvents
values:
- - story-gen-book-event1
- - story-gen-book-event2
- - story-gen-book-event3
- - story-gen-book-event4
- - story-gen-book-event5
- - story-gen-book-event6
- - story-gen-book-event7
- - story-gen-book-event8
- - story-gen-book-event9
+ - story-gen-book-event1
+ - story-gen-book-event2
+ - story-gen-book-event3
+ - story-gen-book-event4
+ - story-gen-book-event5
+ - story-gen-book-event6
+ - story-gen-book-event7
+ - story-gen-book-event8
+ - story-gen-book-event9
- story-gen-book-event10
- story-gen-book-event11
- story-gen-book-event12
@@ -165,50 +165,50 @@
- story-gen-book-event24
- type: dataset
- id: book_action
+ id: BookActions
values:
- - story-gen-book-action1
- - story-gen-book-action2
- - story-gen-book-action3
- - story-gen-book-action4
- - story-gen-book-action5
- - story-gen-book-action6
- - story-gen-book-action7
- - story-gen-book-action8
- - story-gen-book-action9
+ - story-gen-book-action1
+ - story-gen-book-action2
+ - story-gen-book-action3
+ - story-gen-book-action4
+ - story-gen-book-action5
+ - story-gen-book-action6
+ - story-gen-book-action7
+ - story-gen-book-action8
+ - story-gen-book-action9
- story-gen-book-action10
- story-gen-book-action11
- story-gen-book-action12
- type: dataset
- id: book_action_trait
+ id: BookActionTraits
values:
- - story-gen-book-action-trait1
- - story-gen-book-action-trait2
- - story-gen-book-action-trait3
- - story-gen-book-action-trait4
- - story-gen-book-action-trait5
- - story-gen-book-action-trait6
- - story-gen-book-action-trait7
- - story-gen-book-action-trait8
- - story-gen-book-action-trait9
+ - story-gen-book-action-trait1
+ - story-gen-book-action-trait2
+ - story-gen-book-action-trait3
+ - story-gen-book-action-trait4
+ - story-gen-book-action-trait5
+ - story-gen-book-action-trait6
+ - story-gen-book-action-trait7
+ - story-gen-book-action-trait8
+ - story-gen-book-action-trait9
- story-gen-book-action-trait10
- story-gen-book-action-trait11
- story-gen-book-action-trait12
- story-gen-book-action-trait13
- type: dataset
- id: book_location
+ id: BookLocations
values:
- - story-gen-book-location1
- - story-gen-book-location2
- - story-gen-book-location3
- - story-gen-book-location4
- - story-gen-book-location5
- - story-gen-book-location6
- - story-gen-book-location7
- - story-gen-book-location8
- - story-gen-book-location9
+ - story-gen-book-location1
+ - story-gen-book-location2
+ - story-gen-book-location3
+ - story-gen-book-location4
+ - story-gen-book-location5
+ - story-gen-book-location6
+ - story-gen-book-location7
+ - story-gen-book-location8
+ - story-gen-book-location9
- story-gen-book-location10
- story-gen-book-location11
- story-gen-book-location12
@@ -236,7 +236,7 @@
- story-gen-book-location34
- type: dataset
- id: book_story_element
+ id: BookStoryElements
values:
- story-gen-book-element1
- story-gen-book-element2
@@ -249,18 +249,18 @@
- story-gen-book-element9
- type: dataset
- id: book_story_element_trait
+ id: BookStoryElementTraits
values:
- - story-gen-book-element-trait1
- - story-gen-book-element-trait2
- - story-gen-book-element-trait3
- - story-gen-book-element-trait4
- - story-gen-book-element-trait5
- - story-gen-book-element-trait6
- - story-gen-book-element-trait7
- - story-gen-book-element-trait8
- - story-gen-book-element-trait9
+ - story-gen-book-element-trait1
+ - story-gen-book-element-trait2
+ - story-gen-book-element-trait3
+ - story-gen-book-element-trait4
+ - story-gen-book-element-trait5
+ - story-gen-book-element-trait6
+ - story-gen-book-element-trait7
+ - story-gen-book-element-trait8
+ - story-gen-book-element-trait9
- story-gen-book-element-trait10
- story-gen-book-element-trait11
- story-gen-book-element-trait12
- - story-gen-book-element-trait13
\ No newline at end of file
+ - story-gen-book-element-trait13
diff --git a/Resources/Prototypes/Entities/Objects/Misc/books.yml b/Resources/Prototypes/Entities/Objects/Misc/books.yml
index 25e6bb9f94..3fc90048dd 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/books.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/books.yml
@@ -361,8 +361,8 @@
components:
- type: RandomMetadata
nameSegments:
- - book_hint_appearance
- - book_type
+ - BookHintAppearances
+ - BookTypes
- type: RandomSprite
available:
- cover:
@@ -423,33 +423,7 @@
suffix: random visual, random story
components:
- type: PaperRandomStory
- storySegments:
- - "This is a "
- - book_genre
- - " about a "
- - book_character_trait
- - " "
- - book_character
- - " and "
- - book_character_trait
- - " "
- - book_character
- - ". Due to "
- - book_event
- - ", they "
- - book_action_trait
- - " "
- - book_action
- - " "
- - book_character
- - " "
- - book_location
- - ". \n\n"
- - book_story_element
- - " is "
- - book_story_element_trait
- - "."
- storySeparator: ""
+ template: GenericStory
- type: entity
parent: BookBase
diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml
index 5fa341c976..05a0b9d345 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml
@@ -283,119 +283,6 @@
components:
- type: NukeCodePaper
-- type: entity
- name: pen
- parent: BaseItem
- id: Pen
- description: 'A dark ink pen.'
- components:
- - type: Tag
- tags:
- - Write
- - Pen
- - type: Sprite
- sprite: Objects/Misc/bureaucracy.rsi
- state: pen
- - type: Item
- sprite: Objects/Misc/bureaucracy.rsi
- heldPrefix: pen
- size: Tiny
- - type: PhysicalComposition
- materialComposition:
- Steel: 25
-
-- type: entity
- parent: Pen
- id: PenEmbeddable
- abstract: true
- components:
- - type: EmbeddableProjectile
- offset: 0.3,0.0
- removalTime: 0.0
- - type: ThrowingAngle
- angle: 315
- - type: DamageOtherOnHit
- damage:
- types:
- Piercing: 3
-
-#TODO: I want the luxury pen to write a cool font like Merriweather in the future.
-
-- type: entity
- name: luxury pen
- parent: Pen
- id: LuxuryPen
- description: A fancy and expensive pen that you only deserve to own if you're qualified to handle vast amounts of paperwork.
- components:
- - type: Sprite
- state: luxury_pen
- - type: Item
- heldPrefix: luxury_pen
-
-- type: entity
- name: Cybersun pen
- parent: PenEmbeddable
- id: CyberPen
- description: A high-tech pen straight from Cybersun's legal department, capable of refracting hard-light at impossible angles through its diamond tip in order to write. So powerful, it's even able to rewrite officially stamped documents should the need arise.
- components:
- - type: Tag
- tags:
- - Write
- - WriteIgnoreStamps
- - Pickaxe
- - type: Sprite
- sprite: Objects/Misc/bureaucracy.rsi
- state: overpriced_pen
- - type: MeleeWeapon
- wideAnimationRotation: -45
- damage:
- types:
- Piercing: 15
- soundHit:
- path: /Audio/Weapons/bladeslice.ogg
- - type: Tool
- qualities:
- - Screwing
- useSound:
- collection: Screwdriver
- - type: Item
- sprite: Objects/Misc/bureaucracy.rsi
- heldPrefix: overpriced_pen
- size: Tiny
-
-- type: entity
- name: captain's fountain pen
- parent: PenEmbeddable
- id: PenCap
- description: 'A luxurious fountain pen for the captain of the station.'
- components:
- - type: Sprite
- sprite: Objects/Misc/bureaucracy.rsi
- state: pen_cap
-
-- type: entity
- name: CentCom pen
- parent: CyberPen
- id: PenCentcom
- description: In an attempt to keep up with the "power" of the cybersun bureaucracy, NT made a replica of cyber pen, in their corporate style.
- components:
- - type: Sprite
- sprite: Objects/Misc/bureaucracy.rsi
- state: pen_centcom
- - type: Item
- sprite: Objects/Misc/bureaucracy.rsi
- heldPrefix: pen_centcom
-
-- type: entity
- name: hop's fountain pen
- parent: PenEmbeddable
- id: PenHop
- description: 'A luxurious fountain pen for the hop of the station.'
- components:
- - type: Sprite
- sprite: Objects/Misc/bureaucracy.rsi
- state: pen_hop
-
- type: entity
id: BoxFolderBase
parent: BoxBase
diff --git a/Resources/Prototypes/Entities/Objects/Misc/pen.yml b/Resources/Prototypes/Entities/Objects/Misc/pen.yml
new file mode 100644
index 0000000000..635df230a4
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Misc/pen.yml
@@ -0,0 +1,106 @@
+- type: entity
+ name: pen
+ parent: BaseItem
+ id: Pen
+ description: A dark ink pen.
+ components:
+ - type: Sprite
+ sprite: Objects/Misc/pens.rsi
+ state: pen
+ - type: Item
+ sprite: Objects/Misc/pens.rsi
+ heldPrefix: pen
+ size: Tiny
+ - type: Tag
+ tags:
+ - Write
+ - Pen
+ - type: PhysicalComposition
+ materialComposition:
+ Steel: 25
+
+- type: entity
+ parent: Pen
+ id: PenEmbeddable
+ abstract: true
+ components:
+ - type: EmbeddableProjectile
+ offset: 0.3,0.0
+ removalTime: 0.0
+ - type: ThrowingAngle
+ angle: 315
+ - type: DamageOtherOnHit
+ damage:
+ types:
+ Piercing: 3
+
+#TODO: I want the luxury pen to write a cool font like Merriweather in the future.
+
+- type: entity
+ name: luxury pen
+ parent: Pen
+ id: LuxuryPen
+ description: A fancy and expensive pen that you only deserve to own if you're qualified to handle vast amounts of paperwork.
+ components:
+ - type: Sprite
+ state: luxury_pen
+ - type: Item
+ heldPrefix: luxury_pen
+
+- type: entity
+ name: Cybersun pen
+ parent: PenEmbeddable
+ id: CyberPen
+ description: A high-tech pen straight from Cybersun's legal department, capable of refracting hard-light at impossible angles through its diamond tip in order to write. So powerful, it's even able to rewrite officially stamped documents should the need arise.
+ components:
+ - type: Tag
+ tags:
+ - Write
+ - WriteIgnoreStamps
+ - Pickaxe
+ - Pen
+ - type: Sprite
+ state: overpriced_pen
+ - type: Item
+ heldPrefix: overpriced_pen
+ - type: MeleeWeapon
+ wideAnimationRotation: -45
+ damage:
+ types:
+ Piercing: 15
+ soundHit:
+ path: /Audio/Weapons/bladeslice.ogg
+ - type: Tool
+ qualities:
+ - Screwing
+ useSound:
+ collection: Screwdriver
+
+- type: entity
+ name: captain's fountain pen
+ parent: PenEmbeddable
+ id: PenCap
+ description: A luxurious fountain pen for the captain of the station.
+ components:
+ - type: Sprite
+ state: pen_cap
+
+- type: entity
+ name: CentCom pen
+ parent: CyberPen
+ id: PenCentcom
+ description: In an attempt to keep up with the "power" of the cybersun bureaucracy, NT made a replica of cyber pen, in their corporate style.
+ components:
+ - type: Sprite
+ state: pen_centcom
+ - type: Item
+ heldPrefix: pen_centcom
+
+- type: entity
+ name: hop's fountain pen
+ parent: PenEmbeddable
+ id: PenHop
+ description: A luxurious fountain pen for the hop of the station.
+ components:
+ - type: Sprite
+ state: pen_hop
diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml
index 8053a4196c..c6c3692e59 100644
--- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml
+++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml
@@ -374,7 +374,7 @@
- type: Transform
noRot: true
- type: SoundOnGather
- - type: Gatherable
+ - type: Gatherable
toolWhitelist:
tags:
- Pickaxe
diff --git a/Resources/Prototypes/StoryGen/story-templates.yml b/Resources/Prototypes/StoryGen/story-templates.yml
new file mode 100644
index 0000000000..f05fd5afa6
--- /dev/null
+++ b/Resources/Prototypes/StoryGen/story-templates.yml
@@ -0,0 +1,16 @@
+- type: storyTemplate
+ id: GenericStory
+ locId: story-template-generic
+ variables:
+ bookGenre: BookGenres
+ char1Type: BookCharacters
+ char1Adj: BookCharacterTraits
+ char2Type: BookCharacters
+ char2Adj: BookCharacterTraits
+ event: BookEvents
+ action: BookActions
+ actionTrait: BookActionTraits
+ char3Type: BookCharacters
+ location: BookLocations
+ element: BookStoryElements
+ elementTrait: BookStoryElementTraits
diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml
index d1e0afc83c..78d2bba049 100644
--- a/Resources/Prototypes/Traits/neutral.yml
+++ b/Resources/Prototypes/Traits/neutral.yml
@@ -1,4 +1,4 @@
-- type: trait
+- type: trait
id: PirateAccent
name: trait-pirate-accent-name
description: trait-pirate-accent-desc
@@ -30,5 +30,4 @@
description: trait-liar-desc
components:
- type: ReplacementAccent
- replacementChance: 0.15
accent: liar
diff --git a/Resources/Prototypes/ore.yml b/Resources/Prototypes/ore.yml
index dde1516c85..84d1c66736 100644
--- a/Resources/Prototypes/ore.yml
+++ b/Resources/Prototypes/ore.yml
@@ -1,9 +1,5 @@
# TODO: Kill ore veins
# Split it into 2 components, 1 for "spawn XYZ on destruction" and 1 for "randomly select one of these for spawn on destruction"
-# You could even just use an entityspawncollection instead.
-- type: ore
- id: SpaceShrooms
- oreEntity: FoodSpaceshroom
# High yields
- type: ore
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
index 91602db51e..d62baa885d 100644
--- a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
+++ b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
@@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432. paper_stamp-syndicate by Veritius. paper_receipt, paper_receipt_horizontal by eoineoineoin. pen_centcom is a resprited version of pen_cap by PuroSlavKing (Github). Luxury pen is drawn by Ubaser.",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432. paper_stamp-syndicate by Veritius. paper_receipt, paper_receipt_horizontal by eoineoineoin.",
"size": {
"x": 32,
"y": 32
@@ -159,38 +159,6 @@
{
"name": "paper_dotmatrix_words"
},
- {
- "name": "pen"
- },
- {
- "name": "pen_cap"
- },
- {
- "name": "pen_centcom"
- },
- {
- "name": "pen_hop"
- },
- {
- "name": "overpriced_pen"
- },
- {
- "name": "luxury_pen"
- },
- {
- "name": "pen_blue"
- },
- {
- "name": "pen_red"
- },
- {
- "name": "pen-inhand-left",
- "directions": 4
- },
- {
- "name": "pen-inhand-right",
- "directions": 4
- },
{
"name": "scrap"
},
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/luxury_pen.png b/Resources/Textures/Objects/Misc/pens.rsi/luxury_pen.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/luxury_pen.png
rename to Resources/Textures/Objects/Misc/pens.rsi/luxury_pen.png
diff --git a/Resources/Textures/Objects/Misc/pens.rsi/meta.json b/Resources/Textures/Objects/Misc/pens.rsi/meta.json
new file mode 100644
index 0000000000..738bc09ee3
--- /dev/null
+++ b/Resources/Textures/Objects/Misc/pens.rsi/meta.json
@@ -0,0 +1,59 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432. pen_centcom is a resprited version of pen_cap by PuroSlavKing (Github). Luxury pen is drawn by Ubaser.",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "pen"
+ },
+ {
+ "name": "pen_cap"
+ },
+ {
+ "name": "pen_centcom"
+ },
+ {
+ "name": "pen_hop"
+ },
+ {
+ "name": "overpriced_pen"
+ },
+ {
+ "name": "luxury_pen"
+ },
+ {
+ "name": "pen_blue"
+ },
+ {
+ "name": "pen_red"
+ },
+ {
+ "name": "pen-inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "pen-inhand-right",
+ "directions": 4
+ },
+ {
+ "name": "overpriced_pen-inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "overpriced_pen-inhand-right",
+ "directions": 4
+ },
+ {
+ "name": "pen_centcom-inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "pen_centcom-inhand-right",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen-inhand-left.png b/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen-inhand-left.png
new file mode 100644
index 0000000000..55b4fe1bd2
Binary files /dev/null and b/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen-inhand-left.png differ
diff --git a/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen-inhand-right.png b/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen-inhand-right.png
new file mode 100644
index 0000000000..22fd23b6ed
Binary files /dev/null and b/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen-inhand-right.png differ
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/overpriced_pen.png b/Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/overpriced_pen.png
rename to Resources/Textures/Objects/Misc/pens.rsi/overpriced_pen.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen-inhand-left.png b/Resources/Textures/Objects/Misc/pens.rsi/pen-inhand-left.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen-inhand-left.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen-inhand-left.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen-inhand-right.png b/Resources/Textures/Objects/Misc/pens.rsi/pen-inhand-right.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen-inhand-right.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen-inhand-right.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen.png b/Resources/Textures/Objects/Misc/pens.rsi/pen.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_blue.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_blue.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_blue.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen_blue.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_cap.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_cap.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_cap.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen_cap.png
diff --git a/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom-inhand-left.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom-inhand-left.png
new file mode 100644
index 0000000000..2fee506464
Binary files /dev/null and b/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom-inhand-left.png differ
diff --git a/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom-inhand-right.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom-inhand-right.png
new file mode 100644
index 0000000000..f9286a25db
Binary files /dev/null and b/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom-inhand-right.png differ
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_centcom.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_centcom.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_centcom.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen_centcom.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_hop.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_hop.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_hop.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen_hop.png
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_red.png b/Resources/Textures/Objects/Misc/pens.rsi/pen_red.png
similarity index 100%
rename from Resources/Textures/Objects/Misc/bureaucracy.rsi/pen_red.png
rename to Resources/Textures/Objects/Misc/pens.rsi/pen_red.png