diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs
new file mode 100644
index 0000000000..85a5659c4b
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+public sealed class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem;
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemControl.xaml b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemControl.xaml
new file mode 100644
index 0000000000..7d7c635de8
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemControl.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemControl.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemControl.xaml.cs
new file mode 100644
index 0000000000..5edbee9d44
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemControl.xaml.cs
@@ -0,0 +1,33 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Maths;
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+///
+/// Represents a single control for a single NanoTask item
+///
+[GenerateTypedNameReferences]
+public sealed partial class NanoTaskItemControl : Control
+{
+ public Action? OnMainPressed;
+ public Action? OnDonePressed;
+
+ public NanoTaskItemControl(NanoTaskItemAndId item)
+ {
+ RobustXamlLoader.Load(this);
+
+ TaskLabel.Text = item.Data.Description;
+ TaskLabel.FontColorOverride = Color.White;
+ TaskForLabel.Text = item.Data.TaskIsFor;
+
+ MainButton.OnPressed += _ => OnMainPressed?.Invoke(item.Id);
+ DoneButton.OnPressed += _ => OnDonePressed?.Invoke(item.Id);
+
+ MainButton.Disabled = item.Data.IsTaskDone;
+ DoneButton.Text = item.Data.IsTaskDone ? Loc.GetString("nano-task-ui-revert-done") : Loc.GetString("nano-task-ui-done");
+ }
+}
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml
new file mode 100644
index 0000000000..ad72df05d9
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs
new file mode 100644
index 0000000000..124b7b7eaa
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs
@@ -0,0 +1,109 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface.Controls;
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+///
+/// Popup displayed to edit a NanoTask item
+///
+[GenerateTypedNameReferences]
+public sealed partial class NanoTaskItemPopup : DefaultWindow
+{
+ private readonly ButtonGroup _priorityGroup = new();
+ private int? _editingTaskId = null;
+
+ public Action? TaskSaved;
+ public Action? TaskDeleted;
+ public Action? TaskCreated;
+ public Action? TaskPrinted;
+
+ private NanoTaskItem MakeItem()
+ {
+ return new(
+ description: DescriptionInput.Text,
+ taskIsFor: RequesterInput.Text,
+ isTaskDone: false,
+ priority: _priorityGroup.Pressed switch {
+ var item when item == LowButton => NanoTaskPriority.Low,
+ var item when item == MediumButton => NanoTaskPriority.Medium,
+ var item when item == HighButton => NanoTaskPriority.High,
+ _ => NanoTaskPriority.Medium,
+ }
+ );
+ }
+
+ public NanoTaskItemPopup()
+ {
+ RobustXamlLoader.Load(this);
+
+ LowButton.Group = _priorityGroup;
+ MediumButton.Group = _priorityGroup;
+ HighButton.Group = _priorityGroup;
+
+ CancelButton.OnPressed += _ => Close();
+ DeleteButton.OnPressed += _ =>
+ {
+ if (_editingTaskId is int id)
+ {
+ TaskDeleted?.Invoke(id);
+ }
+ };
+ PrintButton.OnPressed += _ =>
+ {
+ TaskPrinted?.Invoke(MakeItem());
+ };
+ SaveButton.OnPressed += _ =>
+ {
+ if (_editingTaskId is int id)
+ {
+ TaskSaved?.Invoke(id, MakeItem());
+ }
+ else
+ {
+ TaskCreated?.Invoke(MakeItem());
+ }
+ };
+
+ DescriptionInput.OnTextChanged += args =>
+ {
+ if (args.Text.Length > NanoTaskItem.MaximumStringLength)
+ DescriptionInput.Text = args.Text[..NanoTaskItem.MaximumStringLength];
+ };
+ RequesterInput.OnTextChanged += args =>
+ {
+ if (args.Text.Length > NanoTaskItem.MaximumStringLength)
+ RequesterInput.Text = args.Text[..NanoTaskItem.MaximumStringLength];
+ };
+ }
+
+ public void SetEditingTaskId(int? id)
+ {
+ _editingTaskId = id;
+ DeleteButton.Visible = id is not null;
+ }
+
+ public void ResetInputs(NanoTaskItem? item)
+ {
+ if (item is NanoTaskItem task)
+ {
+ var button = task.Priority switch {
+ NanoTaskPriority.High => HighButton,
+ NanoTaskPriority.Medium => MediumButton,
+ NanoTaskPriority.Low => LowButton,
+ };
+ button.Pressed = true;
+ DescriptionInput.Text = task.Description;
+ RequesterInput.Text = task.TaskIsFor;
+ }
+ else
+ {
+ MediumButton.Pressed = true;
+ DescriptionInput.Text = "";
+ RequesterInput.Text = "";
+ }
+ }
+}
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs
new file mode 100644
index 0000000000..ac08051c89
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs
@@ -0,0 +1,82 @@
+using System.Linq;
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+///
+/// UI fragment responsible for displaying NanoTask controls in a PDA and coordinating with the NanoTaskCartridgeSystem for state
+///
+public sealed partial class NanoTaskUi : UIFragment
+{
+ private NanoTaskUiFragment? _fragment;
+ private NanoTaskItemPopup? _popup;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ {
+ _fragment = new NanoTaskUiFragment();
+ _popup = new NanoTaskItemPopup();
+ _fragment.NewTask += () =>
+ {
+ _popup.ResetInputs(null);
+ _popup.SetEditingTaskId(null);
+ _popup.OpenCentered();
+ };
+ _fragment.OpenTask += id =>
+ {
+ if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task)
+ return;
+
+ _popup.ResetInputs(task.Data);
+ _popup.SetEditingTaskId(task.Id);
+ _popup.OpenCentered();
+ };
+ _fragment.ToggleTaskCompletion += id =>
+ {
+ if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task)
+ return;
+
+ userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, new(
+ description: task.Data.Description,
+ taskIsFor: task.Data.TaskIsFor,
+ isTaskDone: !task.Data.IsTaskDone,
+ priority: task.Data.Priority
+ ))))));
+ };
+ _popup.TaskSaved += (id, data) =>
+ {
+ userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, data)))));
+ _popup.Close();
+ };
+ _popup.TaskDeleted += id =>
+ {
+ userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskDeleteTask(id))));
+ _popup.Close();
+ };
+ _popup.TaskCreated += data =>
+ {
+ userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskAddTask(data))));
+ _popup.Close();
+ };
+ _popup.TaskPrinted += data =>
+ {
+ userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskPrintTask(data))));
+ };
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is not NanoTaskUiState nanoTaskState)
+ return;
+
+ _fragment?.UpdateState(nanoTaskState.Tasks);
+ }
+}
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml
new file mode 100644
index 0000000000..f3620802f0
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs
new file mode 100644
index 0000000000..38897d6205
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs
@@ -0,0 +1,52 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+///
+/// Class displaying the main UI of NanoTask
+///
+[GenerateTypedNameReferences]
+public sealed partial class NanoTaskUiFragment : BoxContainer
+{
+ public Action? OpenTask;
+ public Action? ToggleTaskCompletion;
+ public Action? NewTask;
+ public List Tasks = new();
+
+ public NanoTaskUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ Orientation = LayoutOrientation.Vertical;
+ HorizontalExpand = true;
+ VerticalExpand = true;
+ NewTaskButton.OnPressed += _ => NewTask?.Invoke();
+ }
+ public void UpdateState(List tasks)
+ {
+ Tasks = tasks;
+ HighContainer.RemoveAllChildren();
+ MediumContainer.RemoveAllChildren();
+ LowContainer.RemoveAllChildren();
+
+ HighPriority.Text = Loc.GetString("nano-task-ui-heading-high-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.High)));
+ MediumPriority.Text = Loc.GetString("nano-task-ui-heading-medium-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.Medium)));
+ LowPriority.Text = Loc.GetString("nano-task-ui-heading-low-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.Low)));
+
+ foreach (var task in tasks)
+ {
+ var container = task.Data.Priority switch {
+ NanoTaskPriority.High => HighContainer,
+ NanoTaskPriority.Medium => MediumContainer,
+ NanoTaskPriority.Low => LowContainer,
+ };
+ var control = new NanoTaskItemControl(task);
+ container.AddChild(control);
+ control.OnMainPressed += id => OpenTask?.Invoke(id);
+ control.OnDonePressed += id => ToggleTaskCompletion?.Invoke(id);
+ }
+ }
+}
diff --git a/Content.Server/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs
new file mode 100644
index 0000000000..8118ca8555
--- /dev/null
+++ b/Content.Server/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs
@@ -0,0 +1,151 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Paper;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Server.CartridgeLoader.Cartridges;
+
+///
+/// Server-side class implementing the core UI logic of NanoTask
+///
+public sealed class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem
+{
+ [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly PaperSystem _paper = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUiMessage);
+ SubscribeLocalEvent(OnUiReady);
+
+ SubscribeLocalEvent(OnCartridgeRemoved);
+
+ SubscribeLocalEvent(OnInteractUsing);
+ }
+
+ private void OnCartridgeRemoved(Entity ent, ref CartridgeRemovedEvent args)
+ {
+ if (!_cartridgeLoader.HasProgram(args.Loader))
+ {
+ RemComp(args.Loader);
+ }
+ }
+
+ private void OnInteractUsing(Entity ent, ref InteractUsingEvent args)
+ {
+ if (!_cartridgeLoader.TryGetProgram(ent.Owner, out var uid, out var program))
+ {
+ return;
+ }
+ if (!EntityManager.TryGetComponent(args.Used, out var printed))
+ {
+ return;
+ }
+ if (printed.Task is NanoTaskItem item)
+ {
+ program.Tasks.Add(new(program.Counter++, printed.Task));
+ args.Handled = true;
+ EntityManager.DeleteEntity(args.Used);
+ UpdateUiState(new Entity(uid.Value, program), ent.Owner);
+ }
+ }
+
+ ///
+ /// This gets called when the ui fragment needs to be updated for the first time after activating
+ ///
+ private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args)
+ {
+ UpdateUiState(ent, args.Loader);
+ }
+
+ private void SetupPrintedTask(EntityUid uid, NanoTaskItem item)
+ {
+ PaperComponent? paper = null;
+ NanoTaskPrintedComponent? printed = null;
+ if (!Resolve(uid, ref paper, ref printed))
+ return;
+
+ printed.Task = item;
+ var msg = new FormattedMessage();
+ msg.AddText(Loc.GetString("nano-task-printed-description", ("description", item.Description)));
+ msg.PushNewline();
+ msg.AddText(Loc.GetString("nano-task-printed-requester", ("requester", item.TaskIsFor)));
+ msg.PushNewline();
+ msg.AddText(item.Priority switch {
+ NanoTaskPriority.High => Loc.GetString("nano-task-printed-high-priority"),
+ NanoTaskPriority.Medium => Loc.GetString("nano-task-printed-medium-priority"),
+ NanoTaskPriority.Low => Loc.GetString("nano-task-printed-low-priority"),
+ _ => "",
+ });
+
+ _paper.SetContent((uid, paper), msg.ToMarkup());
+ }
+
+ ///
+ /// The ui messages received here get wrapped by a CartridgeMessageEvent and are relayed from the
+ ///
+ ///
+ /// The cartridge specific ui message event needs to inherit from the CartridgeMessageEvent
+ ///
+ private void OnUiMessage(Entity ent, ref CartridgeMessageEvent args)
+ {
+ if (args is not NanoTaskUiMessageEvent message)
+ return;
+
+ switch (message.Payload)
+ {
+ case NanoTaskAddTask task:
+ if (!task.Item.Validate())
+ return;
+
+ ent.Comp.Tasks.Add(new(ent.Comp.Counter++, task.Item));
+ break;
+ case NanoTaskUpdateTask task:
+ {
+ if (!task.Item.Data.Validate())
+ return;
+
+ var idx = ent.Comp.Tasks.FindIndex(t => t.Id == task.Item.Id);
+ if (idx != -1)
+ ent.Comp.Tasks[idx] = task.Item;
+ break;
+ }
+ case NanoTaskDeleteTask task:
+ ent.Comp.Tasks.RemoveAll(t => t.Id == task.Id);
+ break;
+ case NanoTaskPrintTask task:
+ {
+ if (!task.Item.Validate())
+ return;
+ if (_timing.CurTime < ent.Comp.NextPrintAllowedAfter)
+ return;
+
+ ent.Comp.NextPrintAllowedAfter = _timing.CurTime + ent.Comp.PrintDelay;
+ var printed = Spawn("PaperNanoTaskItem", Transform(message.Actor).Coordinates);
+ _hands.PickupOrDrop(message.Actor, printed);
+ _audio.PlayPvs(new SoundPathSpecifier("/Audio/Machines/printer.ogg"), ent.Owner);
+ SetupPrintedTask(printed, task.Item);
+ break;
+ }
+ }
+
+ UpdateUiState(ent, GetEntity(args.LoaderUid));
+ }
+
+
+ private void UpdateUiState(Entity ent, EntityUid loaderUid)
+ {
+ var state = new NanoTaskUiState(ent.Comp.Tasks);
+ _cartridgeLoader.UpdateCartridgeUiState(loaderUid, state);
+ }
+}
diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs
new file mode 100644
index 0000000000..31169ae022
--- /dev/null
+++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs
@@ -0,0 +1,42 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+///
+/// Component that indicates a PDA cartridge as containing the NanoTask program
+///
+[RegisterComponent, AutoGenerateComponentPause]
+public sealed partial class NanoTaskCartridgeComponent : Component
+{
+ ///
+ /// The list of tasks
+ ///
+ [DataField]
+ public List Tasks = new();
+
+ ///
+ /// counter for generating task IDs
+ ///
+ [DataField]
+ public int Counter = 1;
+
+ ///
+ /// When the user can print again
+ ///
+ [DataField, AutoPausedField]
+ public TimeSpan NextPrintAllowedAfter = TimeSpan.Zero;
+
+ ///
+ /// How long in between each time the user can print out a task
+ ///
+ [DataField]
+ public TimeSpan PrintDelay = TimeSpan.FromSeconds(5);
+}
+
+///
+/// Component attached to the PDA a NanoTask cartridge is inserted into for interaction handling
+///
+[RegisterComponent]
+public sealed partial class NanoTaskInteractionComponent : Component
+{
+}
diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs
new file mode 100644
index 0000000000..a020e46521
--- /dev/null
+++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs
@@ -0,0 +1,16 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+///
+/// Component attached to a piece of paper to indicate that it was printed from NanoTask and can be inserted back into it
+///
+[RegisterComponent]
+public sealed partial class NanoTaskPrintedComponent : Component
+{
+ ///
+ /// The task that this item holds
+ ///
+ [DataField]
+ public NanoTaskItem? Task;
+}
diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiMessageEvent.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiMessageEvent.cs
new file mode 100644
index 0000000000..51a34aaa75
--- /dev/null
+++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiMessageEvent.cs
@@ -0,0 +1,91 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+///
+/// Base UI message for NanoTask interactions
+///
+public interface INanoTaskUiMessagePayload
+{
+}
+
+///
+/// Dispatched when a new task is created
+///
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskAddTask : INanoTaskUiMessagePayload
+{
+ ///
+ /// The newly created task
+ ///
+ public readonly NanoTaskItem Item;
+
+ public NanoTaskAddTask(NanoTaskItem item)
+ {
+ Item = item;
+ }
+}
+
+///
+/// Dispatched when an existing task is modified
+///
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskUpdateTask : INanoTaskUiMessagePayload
+{
+ ///
+ /// The task that was updated and its ID
+ ///
+ public readonly NanoTaskItemAndId Item;
+
+ public NanoTaskUpdateTask(NanoTaskItemAndId item)
+ {
+ Item = item;
+ }
+}
+
+///
+/// Dispatched when an existing task is deleted
+///
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskDeleteTask : INanoTaskUiMessagePayload
+{
+ ///
+ /// The ID of the task to delete
+ ///
+ public readonly int Id;
+
+ public NanoTaskDeleteTask(int id)
+ {
+ Id = id;
+ }
+}
+
+///
+/// Dispatched when a task is requested to be printed
+///
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskPrintTask : INanoTaskUiMessagePayload
+{
+ ///
+ /// The NanoTask to print
+ ///
+ public readonly NanoTaskItem Item;
+
+ public NanoTaskPrintTask(NanoTaskItem item)
+ {
+ Item = item;
+ }
+}
+
+///
+/// Cartridge message event carrying the NanoTask UI messages
+///
+[Serializable, NetSerializable]
+public sealed class NanoTaskUiMessageEvent : CartridgeMessageEvent
+{
+ public readonly INanoTaskUiMessagePayload Payload;
+ public NanoTaskUiMessageEvent(INanoTaskUiMessagePayload payload)
+ {
+ Payload = payload;
+ }
+}
diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs
new file mode 100644
index 0000000000..9f270671e3
--- /dev/null
+++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs
@@ -0,0 +1,88 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+///
+/// The priority assigned to a NanoTask item
+///
+[Serializable, NetSerializable]
+public enum NanoTaskPriority : byte
+{
+ High,
+ Medium,
+ Low,
+};
+
+///
+/// The data relating to a single NanoTask item, but not its identifier
+///
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskItem
+{
+ ///
+ /// The maximum length of the Description and TaskIsFor fields
+ ///
+ public static int MaximumStringLength = 30;
+
+ ///
+ /// The task description, i.e. "Bake a cake"
+ ///
+ public readonly string Description;
+
+ ///
+ /// Who the task is for, i.e. "Cargo"
+ ///
+ public readonly string TaskIsFor;
+
+ ///
+ /// If the task is marked as done or not
+ ///
+ public readonly bool IsTaskDone;
+
+ ///
+ /// The task's marked priority
+ ///
+ public readonly NanoTaskPriority Priority;
+
+ public NanoTaskItem(string description, string taskIsFor, bool isTaskDone, NanoTaskPriority priority)
+ {
+ Description = description;
+ TaskIsFor = taskIsFor;
+ IsTaskDone = isTaskDone;
+ Priority = priority;
+ }
+ public bool Validate()
+ {
+ return Description.Length <= MaximumStringLength && TaskIsFor.Length <= MaximumStringLength;
+ }
+};
+
+///
+/// Pairs a NanoTask item and its identifier
+///
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskItemAndId
+{
+ public readonly int Id;
+ public readonly NanoTaskItem Data;
+
+ public NanoTaskItemAndId(int id, NanoTaskItem data)
+ {
+ Id = id;
+ Data = data;
+ }
+};
+
+///
+/// The UI state of the NanoTask
+///
+[Serializable, NetSerializable]
+public sealed class NanoTaskUiState : BoundUserInterfaceState
+{
+ public List Tasks;
+
+ public NanoTaskUiState(List tasks)
+ {
+ Tasks = tasks;
+ }
+}
diff --git a/Content.Shared/CartridgeLoader/Cartridges/SharedNanoTaskCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/SharedNanoTaskCartridgeSystem.cs
new file mode 100644
index 0000000000..444e377f6a
--- /dev/null
+++ b/Content.Shared/CartridgeLoader/Cartridges/SharedNanoTaskCartridgeSystem.cs
@@ -0,0 +1,19 @@
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+public abstract class SharedNanoTaskCartridgeSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnCartridgeAdded);
+ }
+
+ private void OnCartridgeAdded(Entity ent, ref CartridgeAddedEvent args)
+ {
+ EnsureComp(args.Loader);
+ }
+}
diff --git a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl
index 278e70286e..5da0af441b 100644
--- a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl
+++ b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl
@@ -2,6 +2,7 @@ device-pda-slot-component-slot-name-cartridge = Cartridge
default-program-name = Program
notekeeper-program-name = Notekeeper
+nano-task-program-name = NanoTask
news-read-program-name = Station news
crew-manifest-program-name = Crew manifest
@@ -28,6 +29,47 @@ astro-nav-program-name = AstroNav
med-tek-program-name = MedTek
+# NanoTask cartridge
+
+nano-task-ui-heading-high-priority-tasks =
+ { $amount ->
+ [zero] No High Priority Tasks
+ [one] 1 High Priority Task
+ *[other] {$amount} High Priority Tasks
+ }
+nano-task-ui-heading-medium-priority-tasks =
+ { $amount ->
+ [zero] No Medium Priority Tasks
+ [one] 1 Medium Priority Task
+ *[other] {$amount} Medium Priority Tasks
+ }
+nano-task-ui-heading-low-priority-tasks =
+ { $amount ->
+ [zero] No Low Priority Tasks
+ [one] 1 Low Priority Task
+ *[other] {$amount} Low Priority Tasks
+ }
+nano-task-ui-done = Done
+nano-task-ui-revert-done = Undo
+nano-task-ui-priority-low = Low
+nano-task-ui-priority-medium = Medium
+nano-task-ui-priority-high = High
+nano-task-ui-cancel = Cancel
+nano-task-ui-print = Print
+nano-task-ui-delete = Delete
+nano-task-ui-save = Save
+nano-task-ui-new-task = New Task
+nano-task-ui-description-label = Description:
+nano-task-ui-description-placeholder = Get something important
+nano-task-ui-requester-label = Requester:
+nano-task-ui-requester-placeholder = John Nanotrasen
+nano-task-ui-item-title = Edit Task
+nano-task-printed-description = Description: {$description}
+nano-task-printed-requester = Requester: {$requester}
+nano-task-printed-high-priority = Priority: High
+nano-task-printed-medium-priority = Priority: Medium
+nano-task-printed-low-priority = Priority: Low
+
# Wanted list cartridge
wanted-list-program-name = Wanted list
wanted-list-label-no-records = It's all right, cowboy
diff --git a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml
index 0ab0b687dc..494bda50d1 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml
@@ -16,6 +16,25 @@
state: book_icon
- type: NotekeeperCartridge
+
+- type: entity
+ parent: BaseItem
+ id: NanoTaskCartridge
+ name: NanoTask cartridge
+ description: A program that allows you to keep a list of tasks to do.
+ components:
+ - type: Sprite
+ sprite: Objects/Devices/cartridge.rsi
+ state: cart-nav
+ - type: Cartridge
+ programName: nano-task-program-name
+ icon:
+ sprite: Interface/Misc/program_icons.rsi
+ state: nano_task
+ - type: UIFragment
+ ui: !type:NanoTaskUi
+ - type: NanoTaskCartridge
+
- type: entity
parent: BaseItem
id: NewsReaderCartridge
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index 3ef18d1b2e..fa9b313590 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -80,6 +80,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
cartridgeSlot:
priority: -1
@@ -130,6 +131,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
@@ -143,6 +145,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- MedTekCartridge
@@ -492,6 +495,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- AstroNavCartridge
@@ -840,6 +844,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
- LogProbeCartridge
@@ -908,6 +913,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- MedTekCartridge
- WantedListCartridge
@@ -932,6 +938,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- LogProbeCartridge
- WantedListCartridge
@@ -1054,6 +1061,7 @@
uiKey: enum.PdaUiKey.Key
preinstalled:
- NotekeeperCartridge
+ - NanoTaskCartridge
- type: entity
parent: BaseSecurityPDA
@@ -1080,6 +1088,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- MedTekCartridge
- WantedListCartridge
@@ -1250,6 +1259,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
- LogProbeCartridge
@@ -1277,6 +1287,7 @@
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
- MedTekCartridge
@@ -1423,6 +1434,7 @@
uiKey: enum.PdaUiKey.Key
preinstalled:
- NotekeeperCartridge
+ - NanoTaskCartridge
- MedTekCartridge
- type: entity
diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml
index a8b5bf0cab..de4e38cc7a 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml
@@ -196,6 +196,21 @@
headerImagePath: "/Textures/Interface/Paper/paper_heading_cargo_invoice.svg.96dpi.png"
headerMargin: 0.0, 12.0, 0.0, 0.0
+- type: entity
+ id: PaperNanoTaskItem
+ parent: Paper
+ name: NanoTask item
+ description: A printed NanoTask item. Can be inserted into your PDA to add it to your tasks.
+ components:
+ - type: Tag
+ tags:
+ - Document
+ - Trash
+ - Paper
+ - type: StaticPrice
+ price: 0
+ - type: NanoTaskPrinted
+
- type: entity
id: PaperCargoBountyManifest
parent: PaperCargoInvoice
diff --git a/Resources/Textures/Interface/Misc/program_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/program_icons.rsi/meta.json
index 4c2a45ad76..f31c73710c 100644
--- a/Resources/Textures/Interface/Misc/program_icons.rsi/meta.json
+++ b/Resources/Textures/Interface/Misc/program_icons.rsi/meta.json
@@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "news_read by Misha_Unity, crew_manifest by Phill101",
+ "copyright": "news_read by Misha_Unity, crew_manifest by Phill101, nano_task by Janet Blackquill ",
"size": {
"x": 32,
"y": 32
@@ -12,6 +12,9 @@
},
{
"name": "crew_manifest"
+ },
+ {
+ "name": "nano_task"
}
]
}
diff --git a/Resources/Textures/Interface/Misc/program_icons.rsi/nano_task.png b/Resources/Textures/Interface/Misc/program_icons.rsi/nano_task.png
new file mode 100644
index 0000000000..bcbfbfc941
Binary files /dev/null and b/Resources/Textures/Interface/Misc/program_icons.rsi/nano_task.png differ