--- /dev/null
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+public sealed class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem;
--- /dev/null
+<Control xmlns="https://spacestation14.io" xmlns:system="clr-namespace:System;assembly=System.Runtime">
+ <BoxContainer Name="MainContainer"
+ Orientation="Horizontal"
+ SetWidth="250">
+ <Button Name="MainButton"
+ HorizontalExpand="True"
+ VerticalExpand="True"
+ StyleClasses="ButtonSquare"
+ Margin="-1 0 0 0">
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+ <BoxContainer Orientation="Vertical"
+ VerticalExpand="True"
+ HorizontalExpand="True"
+ Margin="-5 0 0 0">
+ <Label Name="TaskLabel"
+ StyleClasses="LabelSubText" />
+ <Label Name="TaskForLabel"
+ StyleClasses="LabelSubText"
+ Margin="0 -5 0 0" />
+ </BoxContainer>
+ </BoxContainer>
+ </Button>
+ <Button Name="DoneButton"
+ VerticalExpand="True"
+ Text="{Loc 'nano-task-ui-done'}">
+ <Button.StyleClasses>
+ <system:String>ButtonSmall</system:String>
+ <system:String>OpenLeft</system:String>
+ </Button.StyleClasses>
+ </Button>
+ </BoxContainer>
+</Control>
--- /dev/null
+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;
+
+/// <summary>
+/// Represents a single control for a single NanoTask item
+/// </summary>
+[GenerateTypedNameReferences]
+public sealed partial class NanoTaskItemControl : Control
+{
+ public Action<int>? OnMainPressed;
+ public Action<int>? 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");
+ }
+}
--- /dev/null
+<DefaultWindow xmlns="https://spacestation14.io"
+ Title="{Loc nano-task-ui-item-title}"
+ MinSize="300 300">
+ <PanelContainer StyleClasses="AngleRect">
+ <BoxContainer Orientation="Vertical" Margin="4">
+ <!-- Task Description Input -->
+ <BoxContainer Orientation="Vertical" Margin="0 4">
+ <Label Text="{Loc nano-task-ui-description-label}"
+ StyleClasses="LabelHeading" />
+ <PanelContainer StyleClasses="ButtonSquare">
+ <LineEdit Name="DescriptionInput"
+ PlaceHolder="{Loc nano-task-ui-description-placeholder}" />
+ </PanelContainer>
+ </BoxContainer>
+
+ <!-- Task Requester Input -->
+ <BoxContainer Orientation="Vertical" Margin="0 4">
+ <Label Text="{Loc nano-task-ui-requester-label}"
+ StyleClasses="LabelHeading" />
+ <PanelContainer StyleClasses="ButtonSquare">
+ <LineEdit Name="RequesterInput"
+ PlaceHolder="{Loc nano-task-ui-requester-placeholder}" />
+ </PanelContainer>
+ </BoxContainer>
+
+ <!-- Severity Buttons -->
+ <BoxContainer Orientation="Horizontal"
+ HorizontalAlignment="Center"
+ Margin="0 8 0 0">
+ <Button Name="LowButton"
+ Text="{Loc nano-task-ui-priority-low}"
+ StyleClasses="OpenRight"
+ MinSize="60 0" />
+ <Button Name="MediumButton"
+ Text="{Loc nano-task-ui-priority-medium}"
+ StyleClasses="ButtonSquare"
+ MinSize="60 0" />
+ <Button Name="HighButton"
+ Text="{Loc nano-task-ui-priority-high}"
+ StyleClasses="OpenLeft"
+ MinSize="60 0" />
+ </BoxContainer>
+
+ <!-- Verb Buttons -->
+ <BoxContainer Orientation="Horizontal"
+ HorizontalAlignment="Right"
+ Margin="0 8 0 0">
+ <Button Name="CancelButton"
+ Text="{Loc nano-task-ui-cancel}"
+ StyleClasses="OpenRight"
+ MinSize="60 0" />
+ <Button Name="DeleteButton"
+ Text="{Loc nano-task-ui-delete}"
+ StyleClasses="ButtonSquare"
+ MinSize="60 0" />
+ <Button Name="PrintButton"
+ Text="{Loc nano-task-ui-print}"
+ StyleClasses="ButtonSquare"
+ MinSize="60 0" />
+ <Button Name="SaveButton"
+ Text="{Loc nano-task-ui-save}"
+ StyleClasses="OpenLeft"
+ MinSize="60 0" />
+ </BoxContainer>
+ </BoxContainer>
+ </PanelContainer>
+</DefaultWindow>
--- /dev/null
+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;
+
+/// <summary>
+/// Popup displayed to edit a NanoTask item
+/// </summary>
+[GenerateTypedNameReferences]
+public sealed partial class NanoTaskItemPopup : DefaultWindow
+{
+ private readonly ButtonGroup _priorityGroup = new();
+ private int? _editingTaskId = null;
+
+ public Action<int, NanoTaskItem>? TaskSaved;
+ public Action<int>? TaskDeleted;
+ public Action<NanoTaskItem>? TaskCreated;
+ public Action<NanoTaskItem>? 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 = "";
+ }
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// UI fragment responsible for displaying NanoTask controls in a PDA and coordinating with the NanoTaskCartridgeSystem for state
+/// </summary>
+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);
+ }
+}
--- /dev/null
+<cartridges:NanoTaskUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
+ xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+ xmlns="https://spacestation14.io" Margin="1 0 2 0">
+
+ <PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
+ <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+ <ScrollContainer HorizontalExpand="True" VerticalExpand="True">
+ <BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical" Margin="8" SeparationOverride="8">
+ <!-- Heading for High Priority Items -->
+ <BoxContainer Orientation="Horizontal">
+ <PanelContainer SetWidth="7" Margin="0 0 8 0">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxFlat BackgroundColor="#e93d58"/>
+ </PanelContainer.PanelOverride>
+ </PanelContainer>
+ <Label Name="HighPriority" StyleClasses="LabelHeading"/>
+ </BoxContainer>
+ <!-- Location for High Priority Items -->
+ <GridContainer Name="HighContainer"
+ HorizontalExpand="True"
+ Access="Public"
+ Columns="2" />
+
+ <!-- Heading for Medium Priority Items -->
+ <BoxContainer Orientation="Horizontal">
+ <PanelContainer SetWidth="7" Margin="0 0 8 0">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxFlat BackgroundColor="#ef973c"/>
+ </PanelContainer.PanelOverride>
+ </PanelContainer>
+ <Label Name="MediumPriority" StyleClasses="LabelHeading"/>
+ </BoxContainer>
+ <!-- Location for Medium Priority Items -->
+ <GridContainer Name="MediumContainer"
+ HorizontalExpand="True"
+ Access="Public"
+ Columns="2" />
+
+ <!-- Location for Low Priority Items -->
+ <BoxContainer Orientation="Horizontal">
+ <PanelContainer SetWidth="7" Margin="0 0 8 0">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxFlat BackgroundColor="#3dd425"/>
+ </PanelContainer.PanelOverride>
+ </PanelContainer>
+ <Label Name="LowPriority" StyleClasses="LabelHeading"/>
+ </BoxContainer>
+ <!-- Location for Low Priority Items -->
+ <GridContainer Name="LowContainer"
+ HorizontalExpand="True"
+ Access="Public"
+ Columns="2" />
+
+ <Button Name="NewTaskButton" Text="{Loc 'nano-task-ui-new-task'}" HorizontalAlignment="Right"/>
+ </BoxContainer>
+ </ScrollContainer>
+ </BoxContainer>
+</cartridges:NanoTaskUiFragment>
--- /dev/null
+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;
+
+/// <summary>
+/// Class displaying the main UI of NanoTask
+/// </summary>
+[GenerateTypedNameReferences]
+public sealed partial class NanoTaskUiFragment : BoxContainer
+{
+ public Action<int>? OpenTask;
+ public Action<int>? ToggleTaskCompletion;
+ public Action? NewTask;
+ public List<NanoTaskItemAndId> Tasks = new();
+
+ public NanoTaskUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ Orientation = LayoutOrientation.Vertical;
+ HorizontalExpand = true;
+ VerticalExpand = true;
+ NewTaskButton.OnPressed += _ => NewTask?.Invoke();
+ }
+ public void UpdateState(List<NanoTaskItemAndId> 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);
+ }
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// Server-side class implementing the core UI logic of NanoTask
+/// </summary>
+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<NanoTaskCartridgeComponent, CartridgeMessageEvent>(OnUiMessage);
+ SubscribeLocalEvent<NanoTaskCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
+
+ SubscribeLocalEvent<NanoTaskCartridgeComponent, CartridgeRemovedEvent>(OnCartridgeRemoved);
+
+ SubscribeLocalEvent<NanoTaskInteractionComponent, InteractUsingEvent>(OnInteractUsing);
+ }
+
+ private void OnCartridgeRemoved(Entity<NanoTaskCartridgeComponent> ent, ref CartridgeRemovedEvent args)
+ {
+ if (!_cartridgeLoader.HasProgram<NanoTaskCartridgeComponent>(args.Loader))
+ {
+ RemComp<NanoTaskInteractionComponent>(args.Loader);
+ }
+ }
+
+ private void OnInteractUsing(Entity<NanoTaskInteractionComponent> ent, ref InteractUsingEvent args)
+ {
+ if (!_cartridgeLoader.TryGetProgram<NanoTaskCartridgeComponent>(ent.Owner, out var uid, out var program))
+ {
+ return;
+ }
+ if (!EntityManager.TryGetComponent<NanoTaskPrintedComponent>(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<NanoTaskCartridgeComponent>(uid.Value, program), ent.Owner);
+ }
+ }
+
+ /// <summary>
+ /// This gets called when the ui fragment needs to be updated for the first time after activating
+ /// </summary>
+ private void OnUiReady(Entity<NanoTaskCartridgeComponent> 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());
+ }
+
+ /// <summary>
+ /// The ui messages received here get wrapped by a CartridgeMessageEvent and are relayed from the <see cref="CartridgeLoaderSystem"/>
+ /// </summary>
+ /// <remarks>
+ /// The cartridge specific ui message event needs to inherit from the CartridgeMessageEvent
+ /// </remarks>
+ private void OnUiMessage(Entity<NanoTaskCartridgeComponent> 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<NanoTaskCartridgeComponent> ent, EntityUid loaderUid)
+ {
+ var state = new NanoTaskUiState(ent.Comp.Tasks);
+ _cartridgeLoader.UpdateCartridgeUiState(loaderUid, state);
+ }
+}
--- /dev/null
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+/// <summary>
+/// Component that indicates a PDA cartridge as containing the NanoTask program
+/// </summary>
+[RegisterComponent, AutoGenerateComponentPause]
+public sealed partial class NanoTaskCartridgeComponent : Component
+{
+ /// <summary>
+ /// The list of tasks
+ /// </summary>
+ [DataField]
+ public List<NanoTaskItemAndId> Tasks = new();
+
+ /// <summary>
+ /// counter for generating task IDs
+ /// </summary>
+ [DataField]
+ public int Counter = 1;
+
+ /// <summary>
+ /// When the user can print again
+ /// </summary>
+ [DataField, AutoPausedField]
+ public TimeSpan NextPrintAllowedAfter = TimeSpan.Zero;
+
+ /// <summary>
+ /// How long in between each time the user can print out a task
+ /// </summary>
+ [DataField]
+ public TimeSpan PrintDelay = TimeSpan.FromSeconds(5);
+}
+
+/// <summary>
+/// Component attached to the PDA a NanoTask cartridge is inserted into for interaction handling
+/// </summary>
+[RegisterComponent]
+public sealed partial class NanoTaskInteractionComponent : Component
+{
+}
--- /dev/null
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+/// <summary>
+/// Component attached to a piece of paper to indicate that it was printed from NanoTask and can be inserted back into it
+/// </summary>
+[RegisterComponent]
+public sealed partial class NanoTaskPrintedComponent : Component
+{
+ /// <summary>
+ /// The task that this item holds
+ /// </summary>
+ [DataField]
+ public NanoTaskItem? Task;
+}
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+/// <summary>
+/// Base UI message for NanoTask interactions
+/// </summary>
+public interface INanoTaskUiMessagePayload
+{
+}
+
+/// <summary>
+/// Dispatched when a new task is created
+/// </summary>
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskAddTask : INanoTaskUiMessagePayload
+{
+ /// <summary>
+ /// The newly created task
+ /// </summary>
+ public readonly NanoTaskItem Item;
+
+ public NanoTaskAddTask(NanoTaskItem item)
+ {
+ Item = item;
+ }
+}
+
+/// <summary>
+/// Dispatched when an existing task is modified
+/// </summary>
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskUpdateTask : INanoTaskUiMessagePayload
+{
+ /// <summary>
+ /// The task that was updated and its ID
+ /// </summary>
+ public readonly NanoTaskItemAndId Item;
+
+ public NanoTaskUpdateTask(NanoTaskItemAndId item)
+ {
+ Item = item;
+ }
+}
+
+/// <summary>
+/// Dispatched when an existing task is deleted
+/// </summary>
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskDeleteTask : INanoTaskUiMessagePayload
+{
+ /// <summary>
+ /// The ID of the task to delete
+ /// </summary>
+ public readonly int Id;
+
+ public NanoTaskDeleteTask(int id)
+ {
+ Id = id;
+ }
+}
+
+/// <summary>
+/// Dispatched when a task is requested to be printed
+/// </summary>
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskPrintTask : INanoTaskUiMessagePayload
+{
+ /// <summary>
+ /// The NanoTask to print
+ /// </summary>
+ public readonly NanoTaskItem Item;
+
+ public NanoTaskPrintTask(NanoTaskItem item)
+ {
+ Item = item;
+ }
+}
+
+/// <summary>
+/// Cartridge message event carrying the NanoTask UI messages
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class NanoTaskUiMessageEvent : CartridgeMessageEvent
+{
+ public readonly INanoTaskUiMessagePayload Payload;
+ public NanoTaskUiMessageEvent(INanoTaskUiMessagePayload payload)
+ {
+ Payload = payload;
+ }
+}
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+/// <summary>
+/// The priority assigned to a NanoTask item
+/// </summary>
+[Serializable, NetSerializable]
+public enum NanoTaskPriority : byte
+{
+ High,
+ Medium,
+ Low,
+};
+
+/// <summary>
+/// The data relating to a single NanoTask item, but not its identifier
+/// </summary>
+[Serializable, NetSerializable, DataRecord]
+public sealed class NanoTaskItem
+{
+ /// <summary>
+ /// The maximum length of the Description and TaskIsFor fields
+ /// </summary>
+ public static int MaximumStringLength = 30;
+
+ /// <summary>
+ /// The task description, i.e. "Bake a cake"
+ /// </summary>
+ public readonly string Description;
+
+ /// <summary>
+ /// Who the task is for, i.e. "Cargo"
+ /// </summary>
+ public readonly string TaskIsFor;
+
+ /// <summary>
+ /// If the task is marked as done or not
+ /// </summary>
+ public readonly bool IsTaskDone;
+
+ /// <summary>
+ /// The task's marked priority
+ /// </summary>
+ 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;
+ }
+};
+
+/// <summary>
+/// Pairs a NanoTask item and its identifier
+/// </summary>
+[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;
+ }
+};
+
+/// <summary>
+/// The UI state of the NanoTask
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class NanoTaskUiState : BoundUserInterfaceState
+{
+ public List<NanoTaskItemAndId> Tasks;
+
+ public NanoTaskUiState(List<NanoTaskItemAndId> tasks)
+ {
+ Tasks = tasks;
+ }
+}
--- /dev/null
+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<NanoTaskCartridgeComponent, CartridgeAddedEvent>(OnCartridgeAdded);
+ }
+
+ private void OnCartridgeAdded(Entity<NanoTaskCartridgeComponent> ent, ref CartridgeAddedEvent args)
+ {
+ EnsureComp<NanoTaskInteractionComponent>(args.Loader);
+ }
+}
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
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
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
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
cartridgeSlot:
priority: -1
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- MedTekCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- AstroNavCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
- LogProbeCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- MedTekCartridge
- WantedListCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- LogProbeCartridge
- WantedListCartridge
uiKey: enum.PdaUiKey.Key
preinstalled:
- NotekeeperCartridge
+ - NanoTaskCartridge
- type: entity
parent: BaseSecurityPDA
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- MedTekCartridge
- WantedListCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
- LogProbeCartridge
preinstalled:
- CrewManifestCartridge
- NotekeeperCartridge
+ - NanoTaskCartridge
- NewsReaderCartridge
- WantedListCartridge
- MedTekCartridge
uiKey: enum.PdaUiKey.Key
preinstalled:
- NotekeeperCartridge
+ - NanoTaskCartridge
- MedTekCartridge
- type: entity
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
{
"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 <uhhadd@gmail.com>",
"size": {
"x": 32,
"y": 32
},
{
"name": "crew_manifest"
+ },
+ {
+ "name": "nano_task"
}
]
}