diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 4c6096ef571..29d4b87b891 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -6,6 +6,9 @@
RA0032;nullable
+
+
+
@@ -30,6 +33,18 @@
MSBuild:Compile
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
@@ -82,6 +97,9 @@
+
+
+
MSBuild:Compile
@@ -93,4 +111,7 @@
MSBuild:Compile
+
+
+
diff --git a/Content.Client/MessageBoard/UI/CommentHeader.xaml b/Content.Client/MessageBoard/UI/CommentHeader.xaml
new file mode 100644
index 00000000000..342a3e642d7
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/CommentHeader.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MessageBoard/UI/CommentHeader.xaml.cs b/Content.Client/MessageBoard/UI/CommentHeader.xaml.cs
new file mode 100644
index 00000000000..8c78f1136e1
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/CommentHeader.xaml.cs
@@ -0,0 +1,20 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.MessageBoard.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class CommentHeader : PanelContainer
+{
+ public CommentHeader(string content, string author, string date, bool deleteEnabled)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ CommentLabel.Text = content;
+ AuthorLabel.Text = $"Posted by {author} on {date}";
+ DeleteButton.Visible = deleteEnabled;
+ }
+}
diff --git a/Content.Client/MessageBoard/UI/CreateEntry.xaml b/Content.Client/MessageBoard/UI/CreateEntry.xaml
new file mode 100644
index 00000000000..4c5d752116e
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/CreateEntry.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MessageBoard/UI/CreateEntry.xaml.cs b/Content.Client/MessageBoard/UI/CreateEntry.xaml.cs
new file mode 100644
index 00000000000..bf2b3357d9c
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/CreateEntry.xaml.cs
@@ -0,0 +1,17 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+
+namespace Content.Client.MessageBoard.UI;
+[GenerateTypedNameReferences]
+public sealed partial class CreateEntry : DefaultWindow
+{
+ public enum EntryType : byte
+ {
+ Public,
+ Direct
+ }
+
+ public EntryType CurrentEntryType = EntryType.Public;
+
+}
+
diff --git a/Content.Client/MessageBoard/UI/EntryHeader.xaml b/Content.Client/MessageBoard/UI/EntryHeader.xaml
new file mode 100644
index 00000000000..0ceeaa56a90
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/EntryHeader.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MessageBoard/UI/EntryHeader.xaml.cs b/Content.Client/MessageBoard/UI/EntryHeader.xaml.cs
new file mode 100644
index 00000000000..f83e7253bd6
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/EntryHeader.xaml.cs
@@ -0,0 +1,22 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.MessageBoard.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class EntryHeader : PanelContainer
+{
+ public EntryHeader(string title, string author, string date, int commentCount, bool deleteEnabled)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ TitleLabel.Text = title;
+ AuthorLabel.Text = $"Posted by {author}";
+ DateLabel.Text = date;
+ CommentsLabel.Text = $"{commentCount} Comments";
+ DeleteButton.Visible = deleteEnabled;
+ }
+}
diff --git a/Content.Client/MessageBoard/UI/EntryWindow.xaml b/Content.Client/MessageBoard/UI/EntryWindow.xaml
new file mode 100644
index 00000000000..88ac918ed0a
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/EntryWindow.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MessageBoard/UI/EntryWindow.xaml.cs b/Content.Client/MessageBoard/UI/EntryWindow.xaml.cs
new file mode 100644
index 00000000000..e9002de72b8
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/EntryWindow.xaml.cs
@@ -0,0 +1,71 @@
+using Content.Client.Message;
+using Content.Shared.CCVar;
+using Content.Shared.MessageBoard.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
+
+
+namespace Content.Client.MessageBoard.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class EntryWindow : DefaultWindow
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public MessageBoardEntry Entry;
+ private bool _isAdmin;
+ private string _playerName;
+ private MessageBoardBoundUserInterface _owner;
+
+ public EntryWindow(MessageBoardEntry entry, bool isAdmin, string playerName, MessageBoardBoundUserInterface owner)
+ {
+ _owner = owner;
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ Entry = entry;
+ this.Title = entry.Title;
+ _isAdmin = isAdmin;
+ _playerName = playerName;
+ MainTitleLabel.Text = entry.Title;
+ var adjustedMainTime = entry.CreationTime.AddYears(_cfg.GetCVar(CCVars.YearOffset));
+ AuthorLabel.Text = $"Posted by {entry.Author} on {adjustedMainTime.ToString()}";
+ MainContentLabel.SetMarkup(entry.Body);
+ entry.Comments.Reverse();
+ foreach (var comment in entry.Comments)
+ {
+ var adjustedTime = comment.CreationTime.AddYears(_cfg.GetCVar(CCVars.YearOffset));
+ bool delete = false;
+ if (_isAdmin || _playerName == comment.Author) delete = true;
+ var item = new CommentHeader(comment.Body, comment.Author, $"{adjustedTime.ToString()}", delete);
+ CommentBC.AddChild(item);
+ item.DeleteButton.OnPressed += (args) =>
+ {
+ var ID = comment.UID;
+ _owner.SendMessage(new MessageBoardDeleteCommentPublicMessage(ID, Entry.UID));
+ };
+ }
+
+ }
+
+ public void UpdateEntry(MessageBoardEntry entry)
+ {
+ Entry = entry;
+ this.Title = entry.Title;
+ MainTitleLabel.Text = entry.Title;
+ var adjustedMainTime = entry.CreationTime.AddYears(_cfg.GetCVar(CCVars.YearOffset));
+ AuthorLabel.Text = $"Posted by {entry.Author} on {adjustedMainTime.ToString()}";
+ MainContentLabel.SetMarkup(entry.Body);
+ CommentBC.RemoveAllChildren();
+ entry.Comments.Reverse();
+ foreach (var comment in entry.Comments)
+ {
+ var adjustedTime = comment.CreationTime.AddYears(_cfg.GetCVar(CCVars.YearOffset));
+ bool delete = false;
+ if (_isAdmin || _playerName == comment.Author) delete = true;
+ CommentBC.AddChild(new CommentHeader(comment.Body, comment.Author, $"{adjustedTime.ToString()}", delete));
+ }
+ }
+}
diff --git a/Content.Client/MessageBoard/UI/MessageBoard.xaml b/Content.Client/MessageBoard/UI/MessageBoard.xaml
new file mode 100644
index 00000000000..8f5cc6dd0f8
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/MessageBoard.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MessageBoard/UI/MessageBoard.xaml.cs b/Content.Client/MessageBoard/UI/MessageBoard.xaml.cs
new file mode 100644
index 00000000000..8dc4c001c6b
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/MessageBoard.xaml.cs
@@ -0,0 +1,29 @@
+using Content.Client.Cargo.Systems;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.MessageBoard.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class MessageBoard : FancyWindow
+{
+ private readonly IPrototypeManager _protoManager;
+ private readonly CargoSystem _cargoSystem;
+ private readonly SpriteSystem _spriteSystem;
+ private EntityUid _owner;
+ public MessageBoard(EntityUid owner, IEntityManager entMan, IPrototypeManager protoManager, SpriteSystem spriteSystem)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _protoManager = protoManager;
+ _cargoSystem = entMan.System();
+ _spriteSystem = spriteSystem;
+ _owner = owner;
+ Title = entMan.GetComponent(owner).EntityName;
+ }
+
+}
diff --git a/Content.Client/MessageBoard/UI/MessageBoardBoundUserInterface.cs b/Content.Client/MessageBoard/UI/MessageBoardBoundUserInterface.cs
new file mode 100644
index 00000000000..d1d0a993fa4
--- /dev/null
+++ b/Content.Client/MessageBoard/UI/MessageBoardBoundUserInterface.cs
@@ -0,0 +1,136 @@
+using Content.Client.Administration.Managers;
+using Content.Client.CrewAssignments.UI;
+using Content.Shared.Cargo.BUI;
+using Content.Shared.CCVar;
+using Content.Shared.IdentityManagement;
+using Content.Shared.MessageBoard.Components;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Configuration;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using static Robust.Client.UserInterface.Controls.MenuBar;
+
+namespace Content.Client.MessageBoard.UI;
+
+[UsedImplicitly]
+public sealed class MessageBoardBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly IClientAdminManager _admin = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ private MessageBoard? _menu;
+ private CreateEntry? _createEntry;
+ private EntryWindow? _entryWindow;
+
+ public MessageBoardBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ bool isAdmin = _admin.IsActive();
+ var player = PlayerManager.LocalEntity;
+ if (player == null) return;
+ var playerName = Identity.Name(player.Value, EntMan);
+ base.UpdateState(state);
+ if(_menu == null)
+ {
+ return;
+ }
+ if (state is not MessageBoardInterfaceState cState)
+ return;
+ _menu.PublicBoardEntriesBC.DisposeAllChildren();
+ cState.PublicEntries.Reverse();
+ foreach (var entry in cState.PublicEntries)
+ {
+ bool delete = false;
+ if (isAdmin || playerName == entry.Author) delete = true;
+ var adjustedTime = entry.CreationTime.AddYears(_cfg.GetCVar(CCVars.YearOffset));
+ EntryHeader header = new EntryHeader(entry.Title, entry.Author, $"{adjustedTime.ToString()}", entry.Comments.Count, delete);
+ _menu.PublicBoardEntriesBC.AddChild(header);
+ header.DeleteButton.OnPressed += (args) =>
+ {
+ SendMessage(new MessageBoardDeleteEntryPublicMessage(entry.UID));
+ };
+ header.ViewButton.OnPressed += (args) =>
+ {
+ if(_entryWindow != null)
+ {
+ _entryWindow.Dispose();
+ }
+ var entryWindow = new EntryWindow(entry, isAdmin, playerName, this);
+ _entryWindow = entryWindow;
+ entryWindow.OpenCentered();
+ entryWindow.AddCommentBtn.OnPressed += (commentArgs) =>
+ {
+ var comment = entryWindow.AddCommentLE.Text;
+ if (comment == string.Empty) return;
+ var ID = entryWindow.Entry.UID;
+ entryWindow.AddCommentLE.Text = string.Empty;
+ SendMessage(new MessageBoardPostCommentPublicMessage(ID, comment));
+ };
+ };
+ if (_entryWindow != null && _entryWindow.Entry.UID == entry.UID)
+ {
+ _entryWindow.UpdateEntry(entry);
+ }
+ }
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ var spriteSystem = EntMan.System();
+ var dependencies = IoCManager.Instance!;
+ _menu = new MessageBoard(Owner, EntMan, dependencies.Resolve(), spriteSystem);
+ var localPlayer = dependencies.Resolve().LocalEntity;
+ var description = new FormattedMessage();
+ _menu.OnClose += Close;
+ _menu.OpenCentered();
+ _menu.CreateEntryPublicButton.OnPressed += CreateEntryPublic;
+ }
+
+ public void CreateEntryPublic(BaseButton.ButtonEventArgs args)
+ {
+ if(_createEntry != null)
+ {
+ _createEntry.Dispose();
+ }
+ _createEntry = new CreateEntry();
+ _createEntry.OpenCentered();
+ _createEntry.CurrentEntryType = CreateEntry.EntryType.Public;
+ _createEntry.PostButton.OnPressed += FinalizeEntryPublic;
+ }
+
+ public void FinalizeEntryPublic(BaseButton.ButtonEventArgs args)
+ {
+ if (_createEntry == null)
+ return;
+ var title = _createEntry.MainTitleLabel.Text;
+ var content = Rope.Collapse(_createEntry.DescriptionLabel.TextRope);
+ if(title == string.Empty || content == string.Empty)
+ {
+ return;
+ }
+ SendMessage(new MessageBoardCreateEntryPublicMessage(title, content));
+ _createEntry.Dispose();
+ _createEntry = null;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+
+}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index 90d63a7edf0..f75717ef576 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -779,6 +779,7 @@ public bool TryAddBounty(EntityUid uid, StationCargoBountyDatabaseComponent? com
}
var pool = filteredBounties.Count == 0 ? allBounties : filteredBounties;
+ if (pool.Count < 1) return false;
var bounty = _random.Pick(pool);
return TryAddBounty(uid, bounty, component);
}
diff --git a/Content.Server/Materials/ProduceMaterialExtractorSystem.cs b/Content.Server/Materials/ProduceMaterialExtractorSystem.cs
index fef58df64b9..e405d7e17c6 100644
--- a/Content.Server/Materials/ProduceMaterialExtractorSystem.cs
+++ b/Content.Server/Materials/ProduceMaterialExtractorSystem.cs
@@ -6,6 +6,7 @@
using Content.Shared.Popups;
using Robust.Server.Audio;
using System.Linq;
+using Content.Shared.Body;
namespace Content.Server.Materials;
@@ -30,10 +31,12 @@ private void OnInteractUsing(Entity ent, ref
if (!this.IsPowered(ent, EntityManager))
return;
- if (!TryComp(args.Used, out var produce))
+ if (!TryComp(args.Used, out var produce) && !TryComp(args.Used, out var produce2))
return;
- if (!_solutionContainer.TryGetSolution(args.Used, produce.SolutionName, out var solution))
+ // TODO The "food" string should be replaced with a variable from Produce at some point
+
+ if (!_solutionContainer.TryGetSolution(args.Used, "food", out var solution))
return;
// Can produce even have fractional amounts? Does it matter if they do?
diff --git a/Content.Server/MessageBoard/Systems/MessageBoardSystem.cs b/Content.Server/MessageBoard/Systems/MessageBoardSystem.cs
new file mode 100644
index 00000000000..ea31406fca5
--- /dev/null
+++ b/Content.Server/MessageBoard/Systems/MessageBoardSystem.cs
@@ -0,0 +1,175 @@
+using Content.Server.Administration.Managers;
+using Content.Server.Chat.Managers;
+using Content.Server.CrewRecords.Systems;
+using Content.Shared.MessageBoard.Components;
+using Content.Shared.MessageBoard.Systems;
+using Robust.Server.GameObjects;
+using Robust.Shared.Player;
+using Robust.Shared.Timing;
+
+namespace Content.Server.MessageBoard.Systems;
+
+public sealed partial class MessageBoardSystem : SharedMessageBoardSystem
+{
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+ [Dependency] private readonly CrewMetaRecordsSystem _crewMetaRecordsSystem = default!;
+ [Dependency] private readonly IAdminManager _adminManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUIOpened);
+ SubscribeLocalEvent(OnCreateEntryPublic);
+ SubscribeLocalEvent(OnPostCommentPublic);
+ SubscribeLocalEvent(OnDeleteCommentPublic);
+ SubscribeLocalEvent(OnDeleteEntryPublic);
+ }
+
+ private void OnDeleteEntryPublic(Entity ent, ref MessageBoardDeleteEntryPublicMessage args)
+ {
+ bool isAdmin = _adminManager.IsAdmin(args.Actor);
+
+ MessageBoardEntry? entry = null;
+ var metaRecord = _crewMetaRecordsSystem.MetaRecords;
+ if (metaRecord == null) return;
+ foreach (var e in metaRecord.MessageBoardEntries)
+ {
+ if (e.UID == args.EntryId)
+ {
+ entry = e;
+ break;
+ }
+ }
+ if (entry == null) return;
+ if (isAdmin || entry.Author == Name(args.Actor))
+ {
+ metaRecord.MessageBoardEntries.Remove(entry);
+ UpdateAllUserInterfaces();
+ return;
+ }
+ }
+
+ private void OnDeleteCommentPublic(Entity ent, ref MessageBoardDeleteCommentPublicMessage args)
+ {
+ bool isAdmin = _adminManager.IsAdmin(args.Actor);
+
+ MessageBoardEntry? entry = null;
+ var metaRecord = _crewMetaRecordsSystem.MetaRecords;
+ if (metaRecord == null) return;
+ foreach (var e in metaRecord.MessageBoardEntries)
+ {
+ if (e.UID == args.EntryId)
+ {
+ entry = e;
+ break;
+ }
+ }
+ if (entry == null) return;
+ foreach (var c in entry.Comments)
+ {
+ if (c.UID == args.CommentId)
+ {
+ if (isAdmin || c.Author == Name(args.Actor))
+ {
+ entry.Comments.Remove(c);
+ UpdateAllUserInterfaces();
+ return;
+ }
+ }
+ }
+ }
+
+ private void OnPostCommentPublic(Entity ent, ref MessageBoardPostCommentPublicMessage args)
+ {
+ bool isAdmin = _adminManager.IsAdmin(args.Actor);
+ MessageBoardEntry? entry = null;
+ var metaRecord = _crewMetaRecordsSystem.MetaRecords;
+ if (metaRecord == null) return;
+ metaRecord.TryGetRecord(Name(args.Actor), out var authorRecord);
+ if (authorRecord == null) return;
+ TimeSpan diff = authorRecord.NextMessageBoardComment - _timing.CurTime;
+ if (!isAdmin && authorRecord.NextMessageBoardComment > _timing.CurTime)
+ {
+ if (TryComp(args.Actor, out var actor) && actor != null && actor.PlayerSession != null)
+ {
+ var msg = $"You cannot post comments so quickly. Wait another {diff.TotalSeconds:F1} seconds";
+ _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Notifications,
+ msg,
+ msg,
+ args.Actor,
+ false,
+ actor.PlayerSession.Channel
+ );
+ }
+ return;
+ }
+ foreach (var e in metaRecord.MessageBoardEntries)
+ {
+ if (e.UID == args.EntryId)
+ {
+ entry = e;
+ break;
+ }
+ }
+ if (entry == null) return;
+ MessageBoardComment newComment = new(entry.NextCommentID++, Name(args.Actor), args.Body);
+ entry.Comments.Add(newComment);
+ authorRecord.NextMessageBoardComment = _timing.CurTime + TimeSpan.FromSeconds(5);
+ UpdateAllUserInterfaces();
+ }
+
+ public void UpdateAllUserInterfaces()
+ {
+ var query = EntityQueryEnumerator();
+
+ while (query.MoveNext(out var uid, out var component))
+ {
+ UpdateUserInterface(uid, component);
+ }
+ }
+
+ private void OnUIOpened(Entity ent, ref BoundUIOpenedEvent args)
+ {
+ UpdateUserInterface(ent.Owner, ent.Comp);
+ }
+
+ private void OnCreateEntryPublic(Entity ent, ref MessageBoardCreateEntryPublicMessage args)
+ {
+ bool isAdmin = _adminManager.IsAdmin(args.Actor);
+ var metaRecord = _crewMetaRecordsSystem.MetaRecords;
+ if (metaRecord == null) return;
+ metaRecord.TryGetRecord(Name(args.Actor), out var authorRecord);
+ if (authorRecord == null) return;
+ TimeSpan diff = authorRecord.NextMessageBoardEntry - _timing.CurTime;
+ if (!isAdmin && authorRecord.NextMessageBoardEntry > _timing.CurTime)
+ {
+ if (TryComp(args.Actor, out var actor) && actor != null && actor.PlayerSession != null)
+ {
+ var msg = $"You cannot create entries so quickly. Wait another {diff.TotalMinutes:F1} minutes";
+ _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Notifications,
+ msg,
+ msg,
+ args.Actor,
+ false,
+ actor.PlayerSession.Channel
+ );
+ }
+ return;
+ }
+ MessageBoardEntry newEntry = new(metaRecord.NextMessageBoardEntryID++, args.Title, Name(args.Actor), args.Body);
+ metaRecord.MessageBoardEntries.Add(newEntry);
+ authorRecord.NextMessageBoardEntry = _timing.CurTime + TimeSpan.FromHours(4);
+ UpdateAllUserInterfaces();
+ }
+
+ private void UpdateUserInterface(EntityUid uid, MessageBoardComponent component)
+ {
+ var metaRecord = _crewMetaRecordsSystem.MetaRecords;
+ if (metaRecord == null) return;
+ var entries = metaRecord.MessageBoardEntries;
+ _uiSystem.SetUiState(uid, MessageBoardUiKey.Main, new MessageBoardInterfaceState(entries));
+ }
+}
diff --git a/Content.Shared/CrewAssignments/Components/CrewAssignmentsComponent.cs b/Content.Shared/CrewAssignments/Components/CrewAssignmentsComponent.cs
index 6d57e2be0f7..38bae780abd 100644
--- a/Content.Shared/CrewAssignments/Components/CrewAssignmentsComponent.cs
+++ b/Content.Shared/CrewAssignments/Components/CrewAssignmentsComponent.cs
@@ -2,15 +2,12 @@
namespace Content.Shared.CrewAssignments.Components;
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState]
+[RegisterComponent]
public sealed partial class CrewAssignmentsComponent : Component
{
[DataField]
- [AutoNetworkedField]
public Dictionary CrewAssignments { get; set; } = new();
[DataField]
- [AutoNetworkedField]
public int NextID = 1;
public bool TryGetAssignment(int id, out CrewAssignment? assignment)
{
diff --git a/Content.Shared/CrewAssignments/Components/JobNetComponent.cs b/Content.Shared/CrewAssignments/Components/JobNetComponent.cs
index 29e6dbeb37f..1446bf41303 100644
--- a/Content.Shared/CrewAssignments/Components/JobNetComponent.cs
+++ b/Content.Shared/CrewAssignments/Components/JobNetComponent.cs
@@ -33,7 +33,8 @@ public sealed partial class JobNetComponent : Component
public TimeSpan WorkedTime = TimeSpan.Zero;
[DataField]
public int LastWorkedFor = 0;
- [DataField]
+
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan PrecursorResetTime = TimeSpan.Zero;
[DataField]
public List> PrecursorObjectives = new List>();
@@ -47,7 +48,8 @@ public sealed partial class JobNetComponent : Component
public RogueNetworkType NetworkType = RogueNetworkType.None;
[DataField]
public string? KillTarget = null;
- [DataField]
+
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan RogueNetResetTime = TimeSpan.Zero;
[DataField]
diff --git a/Content.Shared/CrewAssignments/Components/StationModificationConsoleComponent.cs b/Content.Shared/CrewAssignments/Components/StationModificationConsoleComponent.cs
index f9d9dec78a8..0eb03547a46 100644
--- a/Content.Shared/CrewAssignments/Components/StationModificationConsoleComponent.cs
+++ b/Content.Shared/CrewAssignments/Components/StationModificationConsoleComponent.cs
@@ -4,36 +4,8 @@
namespace Content.Shared.CrewAssignments.Components;
-///
-/// Handles sending order requests to cargo. Doesn't handle orders themselves via shuttle or telepads.
-///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedCrewAssignmentSystem))]
public sealed partial class StationModificationConsoleComponent : Component
{
- ///
- /// If true, account transfers have no limit and a lower cooldown.
- ///
- [DataField, AutoNetworkedField]
- public bool TransferUnbounded;
-}
-
-///
-/// The behaviour of the cargo order console
-///
-[Serializable, NetSerializable]
-public enum StationModificationConsoleMode : byte
-{
- ///
- /// Place orders directly
- ///
- DirectOrder,
- ///
- /// Print a slip to be inserted into a DirectOrder console
- ///
- PrintSlip,
- ///
- /// Transfers the order to the primary account
- ///
- SendToPrimary,
}
diff --git a/Content.Shared/CrewMetaRecords/Components/CrewMetaRecordsComponent.cs b/Content.Shared/CrewMetaRecords/Components/CrewMetaRecordsComponent.cs
index 7a385ac3c7f..5e9cb4c33e0 100644
--- a/Content.Shared/CrewMetaRecords/Components/CrewMetaRecordsComponent.cs
+++ b/Content.Shared/CrewMetaRecords/Components/CrewMetaRecordsComponent.cs
@@ -1,12 +1,13 @@
using Content.Shared.CrewAssignments.Prototypes;
using Content.Shared.CrewAssignments.Systems;
+using Content.Shared.MessageBoard.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.CrewMetaRecords;
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState]
+[RegisterComponent]
public sealed partial class CrewMetaRecordsComponent : Component
{
[DataField]
@@ -18,16 +19,19 @@ public sealed partial class CrewMetaRecordsComponent : Component
[DataField]
public int NextCodexID = 1;
[DataField]
+ public int NextMessageBoardEntryID = 1;
+ [DataField]
public List CurrentObjectives { get; set; } = new();
[DataField]
public List CompletedObjectives { get; set; } = new();
[DataField]
public List CodexEntries { get; set; } = new();
+
+ [DataField]
+ public List MessageBoardEntries { get; set; } = new();
[DataField]
- [AutoNetworkedField]
public Dictionary CrewMetaRecords { get; set; } = new();
[DataField]
- [AutoNetworkedField]
public Dictionary Stations { get; set; } = new();
public bool TryGetRecord(string name, out CrewMetaRecord? record)
{
@@ -70,6 +74,13 @@ public partial class CrewMetaRecord
public DateTime LatestIDTime;
[DataField]
public ProtoId Level = "NetworkLevel1";
+
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan NextMessageBoardEntry = TimeSpan.Zero;
+
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan NextMessageBoardComment = TimeSpan.Zero;
+
public CrewMetaRecord(string name)
{
Name = name;
diff --git a/Content.Shared/CrewRecords/Components/CrewRecordsComponent.cs b/Content.Shared/CrewRecords/Components/CrewRecordsComponent.cs
index 97896f2f7a4..338c159b844 100644
--- a/Content.Shared/CrewRecords/Components/CrewRecordsComponent.cs
+++ b/Content.Shared/CrewRecords/Components/CrewRecordsComponent.cs
@@ -2,12 +2,10 @@
namespace Content.Shared.CrewRecords.Components;
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState]
+[RegisterComponent]
public sealed partial class CrewRecordsComponent : Component
{
[DataField]
- [AutoNetworkedField]
public Dictionary CrewRecords { get; set; } = new();
public bool TryGetRecord(string name, out CrewRecord? record)
diff --git a/Content.Shared/MessageBoard/Components/MessageBoardComponent.cs b/Content.Shared/MessageBoard/Components/MessageBoardComponent.cs
new file mode 100644
index 00000000000..261cfcbcbfc
--- /dev/null
+++ b/Content.Shared/MessageBoard/Components/MessageBoardComponent.cs
@@ -0,0 +1,124 @@
+using Content.Shared.CrewAccesses.Components;
+using Content.Shared.CrewAssignments.Components;
+using Content.Shared.CrewAssignments.Prototypes;
+using Content.Shared.Radio;
+using Content.Shared.Station.Components;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.MessageBoard.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class MessageBoardComponent : Component
+{
+}
+
+
+[DataDefinition]
+[Serializable]
+[Virtual]
+public partial class MessageBoardEntry
+{
+ public int UID;
+ public string Title;
+ public string Author;
+ public string Body;
+ public DateTime CreationTime;
+ public List Comments = new();
+ public int NextCommentID = 0;
+
+ public MessageBoardEntry(int uid, string title, string author, string body)
+ {
+ UID = uid;
+ Title = title;
+ Author = author;
+ Body = body;
+ CreationTime = DateTime.Now;
+ }
+
+}
+
+[DataDefinition]
+[Serializable]
+[Virtual]
+public partial class MessageBoardComment
+{
+ public int UID;
+ public string Author;
+ public string Body;
+ public DateTime CreationTime;
+
+ public MessageBoardComment(int uid, string author, string body)
+ {
+ UID = uid;
+ Author = author;
+ Body = body;
+ CreationTime = DateTime.Now;
+ }
+
+}
+
+[NetSerializable, Serializable]
+public sealed class MessageBoardInterfaceState : BoundUserInterfaceState
+{
+ public List PublicEntries;
+
+ public MessageBoardInterfaceState(List publicEntries)
+ {
+ PublicEntries = publicEntries;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class MessageBoardCreateEntryPublicMessage : BoundUserInterfaceMessage
+{
+ public string Title;
+ public string Body;
+
+ public MessageBoardCreateEntryPublicMessage(string title, string body)
+ {
+ Title = title;
+ Body = body;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class MessageBoardPostCommentPublicMessage : BoundUserInterfaceMessage
+{
+ public int EntryId;
+ public string Body;
+
+ public MessageBoardPostCommentPublicMessage(int entryId, string body)
+ {
+ EntryId = entryId;
+ Body = body;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class MessageBoardDeleteCommentPublicMessage : BoundUserInterfaceMessage
+{
+ public int CommentId;
+ public int EntryId;
+
+
+ public MessageBoardDeleteCommentPublicMessage(int commentId, int entryId)
+ {
+ CommentId = commentId;
+ EntryId = entryId;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class MessageBoardDeleteEntryPublicMessage : BoundUserInterfaceMessage
+{
+ public int EntryId;
+ public MessageBoardDeleteEntryPublicMessage(int entryId)
+ {
+ EntryId = entryId;
+ }
+}
+
+
+
diff --git a/Content.Shared/MessageBoard/Systems/SharedMessageBoardSystem.cs b/Content.Shared/MessageBoard/Systems/SharedMessageBoardSystem.cs
new file mode 100644
index 00000000000..dc0bf6ce66e
--- /dev/null
+++ b/Content.Shared/MessageBoard/Systems/SharedMessageBoardSystem.cs
@@ -0,0 +1,16 @@
+using Robust.Shared.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Content.Shared.MessageBoard.Systems;
+
+public abstract partial class SharedMessageBoardSystem : EntitySystem
+{
+}
+
+[NetSerializable, Serializable]
+public enum MessageBoardUiKey : byte
+{
+ Main
+}
diff --git a/Resources/Prototypes/Body/Animals/animal.yml b/Resources/Prototypes/Body/Animals/animal.yml
index bf8d15c9211..1fe130c59d8 100644
--- a/Resources/Prototypes/Body/Animals/animal.yml
+++ b/Resources/Prototypes/Body/Animals/animal.yml
@@ -16,6 +16,13 @@
id: OrganAnimalInternal
abstract: true
components:
+ - type: SolutionContainerManager
+ solutions:
+ food:
+ maxVol: 2
+ reagents:
+ - ReagentId: Nutriment
+ Quantity: 2
- type: Sprite
sprite: Mobs/Species/Human/organs.rsi
diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_syndicate.yml b/Resources/Prototypes/Catalog/Cargo/cargo_syndicate.yml
index ec98abcc139..0d3546fceee 100644
--- a/Resources/Prototypes/Catalog/Cargo/cargo_syndicate.yml
+++ b/Resources/Prototypes/Catalog/Cargo/cargo_syndicate.yml
@@ -249,9 +249,9 @@
id: BriefcaseSyndieSniperBundleFilled
icon: { sprite: /Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi, state: base }
product: BriefcaseSyndieSniperBundleFilled
- cost: 11000
+ cost: 30000
category: cargoproduct-category-name-syndicate
- group: syndicatemarket3
+ group: syndicatemarket5
- type: cargoProduct
id: ChemicalSynthesisKit
@@ -259,7 +259,7 @@
product: ChemicalSynthesisKit
cost: 700
category: cargoproduct-category-name-syndicate
- group: syndicatemarket
+ group: syndicatemarket3
- type: cargoProduct
id: SlipocalypseClusterSoap
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
index 683b13a4edf..faba080a111 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
@@ -900,6 +900,40 @@
enum.WiresUiKey.Key:
type: WiresBoundUserInterface
+- type: entity
+ id: ComputerMessageBoard
+ parent: BaseComputerAiAccess
+ name: Message Board Console
+ description: Used to communicate across the Sector.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: explosive
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: MessageBoard
+ - type: ActivatableUI
+ key: enum.MessageBoardUiKey.Main
+ - type: UserInterface
+ interfaces:
+ enum.MessageBoardUiKey.Main:
+ type: MessageBoardBoundUserInterface
+ enum.WiresUiKey.Key:
+ type: WiresBoundUserInterface
+ - type: Computer
+ board: StationModComputerCircuitboard
+ - type: PointLight
+ radius: 1.5
+ energy: 1.6
+ color: "#00FF00"
+
- type: entity
id: ComputerStationModification
parent: BaseComputerAiAccess
diff --git a/Resources/Prototypes/Entities/World/Debris/asteroids.yml b/Resources/Prototypes/Entities/World/Debris/asteroids.yml
index 5b307057bf1..da712acf810 100644
--- a/Resources/Prototypes/Entities/World/Debris/asteroids.yml
+++ b/Resources/Prototypes/Entities/World/Debris/asteroids.yml
@@ -62,11 +62,7 @@
moles: # Maximum Moles per TILE, factors such as seed upper/lower bound also apply.
CarbonDioxide: 220
Nitrogen: 88
- Tritium: 100 # Test change for sanity
variableMixture:
- ChlorineTrifluoride: # Test change for sanity
- moles: 100
- prob: 1
Oxygen:
moles: 281
prob: 0.5
diff --git a/Resources/Prototypes/Hydroponics/randomChemicals.yml b/Resources/Prototypes/Hydroponics/randomChemicals.yml
index c832701182e..5a8de4e9100 100644
--- a/Resources/Prototypes/Hydroponics/randomChemicals.yml
+++ b/Resources/Prototypes/Hydroponics/randomChemicals.yml
@@ -103,10 +103,10 @@
- Nocturine
- MuteToxin
- Lead
- - quantity: 0.1
- weight: 5 # 99.9% chance of appearing at least once with 60u of vestine with an average of 6.5 occurances.
- reagents:
- - Vestine
+ # - quantity: 0.1
+ # weight: 5 # 99.9% chance of appearing at least once with 60u of vestine with an average of 6.5 occurances.
+ # reagents:
+ # - Vestine
- quantity: 1.0 # High quantity but on average only 1 per chemical kit
weight: 1
reagents: