PDA UI refactor and cartridges (#11335)
* Work on cartridges * Work on PDA UI * Work on PDA UIs program list * Work on PDA UI borders * Add DeviceNetworkingComponent to the pda base prototype * Fix submodule version * Fix cartridge loader ui key * Fix pda menu xaml * Implement relaying ui messages * Finish implementing the notekeeper cartridge * Fix submodule version * Fix errors from merging master * Fix test failing * Implement setting preinstalled programs * Add some documentation to CartridgeLoaderSystem * Add more doc comments * Add localization to program names * Implement review suggestions * Fix background programs receiving events twice when active
@@ -0,0 +1,136 @@
|
|||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager? _entityManager = default!;
|
||||||
|
|
||||||
|
private EntityUid? _activeProgram;
|
||||||
|
private CartridgeUI? _activeCartridgeUI;
|
||||||
|
private Control? _activeUiFragment;
|
||||||
|
|
||||||
|
protected CartridgeLoaderBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (state is not CartridgeLoaderUiState loaderUiState)
|
||||||
|
{
|
||||||
|
_activeCartridgeUI?.UpdateState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var programs = GetCartridgeComponents(loaderUiState.Programs);
|
||||||
|
UpdateAvailablePrograms(programs);
|
||||||
|
|
||||||
|
_activeProgram = loaderUiState.ActiveUI;
|
||||||
|
|
||||||
|
var ui = RetrieveCartridgeUI(loaderUiState.ActiveUI);
|
||||||
|
var comp = RetrieveCartridgeComponent(loaderUiState.ActiveUI);
|
||||||
|
var control = ui?.GetUIFragmentRoot();
|
||||||
|
|
||||||
|
//Prevent the same UI fragment from getting disposed and attached multiple times
|
||||||
|
if (_activeUiFragment?.GetType() == control?.GetType())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_activeUiFragment is not null)
|
||||||
|
DetachCartridgeUI(_activeUiFragment);
|
||||||
|
|
||||||
|
if (control is not null && _activeProgram.HasValue)
|
||||||
|
{
|
||||||
|
AttachCartridgeUI(control, Loc.GetString(comp?.ProgramName ?? "default-program-name"));
|
||||||
|
SendCartridgeUiReadyEvent(_activeProgram.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeCartridgeUI = ui;
|
||||||
|
_activeUiFragment?.Dispose();
|
||||||
|
_activeUiFragment = control;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ActivateCartridge(EntityUid cartridgeUid)
|
||||||
|
{
|
||||||
|
var message = new CartridgeLoaderUiMessage(cartridgeUid, CartridgeUiMessageAction.Activate);
|
||||||
|
SendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DeactivateActiveCartridge()
|
||||||
|
{
|
||||||
|
if (!_activeProgram.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = new CartridgeLoaderUiMessage(_activeProgram.Value, CartridgeUiMessageAction.Deactivate);
|
||||||
|
SendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void InstallCartridge(EntityUid cartridgeUid)
|
||||||
|
{
|
||||||
|
var message = new CartridgeLoaderUiMessage(cartridgeUid, CartridgeUiMessageAction.Install);
|
||||||
|
SendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UninstallCartridge(EntityUid cartridgeUid)
|
||||||
|
{
|
||||||
|
var message = new CartridgeLoaderUiMessage(cartridgeUid, CartridgeUiMessageAction.Uninstall);
|
||||||
|
SendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<(EntityUid, CartridgeComponent)> GetCartridgeComponents(List<EntityUid> programs)
|
||||||
|
{
|
||||||
|
var components = new List<(EntityUid, CartridgeComponent)>();
|
||||||
|
|
||||||
|
foreach (var program in programs)
|
||||||
|
{
|
||||||
|
var component = RetrieveCartridgeComponent(program);
|
||||||
|
if (component is not null)
|
||||||
|
components.Add((program, component));
|
||||||
|
}
|
||||||
|
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The implementing ui needs to add the passed ui fragment as a child to itself
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void AttachCartridgeUI(Control cartridgeUIFragment, string? title);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The implementing ui needs to remove the passed ui from itself
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void DetachCartridgeUI(Control cartridgeUIFragment);
|
||||||
|
|
||||||
|
protected abstract void UpdateAvailablePrograms(List<(EntityUid, CartridgeComponent)> programs);
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
_activeUiFragment?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CartridgeComponent? RetrieveCartridgeComponent(EntityUid? cartridgeUid)
|
||||||
|
{
|
||||||
|
return _entityManager?.GetComponentOrNull<CartridgeComponent>(cartridgeUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendCartridgeUiReadyEvent(EntityUid cartridgeUid)
|
||||||
|
{
|
||||||
|
var message = new CartridgeLoaderUiMessage(cartridgeUid, CartridgeUiMessageAction.UIReady);
|
||||||
|
SendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CartridgeUI? RetrieveCartridgeUI(EntityUid? cartridgeUid)
|
||||||
|
{
|
||||||
|
var component = _entityManager?.GetComponentOrNull<CartridgeUiComponent>(cartridgeUid);
|
||||||
|
component?.Ui?.Setup(this);
|
||||||
|
return component?.Ui;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Content.Client/CartridgeLoader/CartridgeLoaderSystem.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader;
|
||||||
|
|
||||||
|
public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||||
|
{
|
||||||
|
//Empty client system for component replication
|
||||||
|
}
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedCartridgeLoaderComponent))]
|
||||||
|
public sealed class CartridgeLoaderComponent : SharedCartridgeLoaderComponent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
25
Content.Client/CartridgeLoader/CartridgeUI.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cartridge ui fragments need to inherit this class. The subclass is then used in yaml to tell the cartridge loader ui to use it as the cartridges ui fragment.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// This is an example from the yaml definition from the notekeeper ui
|
||||||
|
/// <code>
|
||||||
|
/// - type: CartridgeUi
|
||||||
|
/// ui: !type:NotekeeperUi
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
public abstract class CartridgeUI
|
||||||
|
{
|
||||||
|
public abstract Control GetUIFragmentRoot();
|
||||||
|
|
||||||
|
public abstract void Setup(BoundUserInterface userInterface);
|
||||||
|
|
||||||
|
public abstract void UpdateState(BoundUserInterfaceState state);
|
||||||
|
|
||||||
|
}
|
||||||
44
Content.Client/CartridgeLoader/CartridgeUISerializer.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Robust.Shared.Serialization.Manager;
|
||||||
|
using Robust.Shared.Serialization.Markdown;
|
||||||
|
using Robust.Shared.Serialization.Markdown.Validation;
|
||||||
|
using Robust.Shared.Serialization.Markdown.Value;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Boilerplate serializer for defining the ui fragment used for a cartridge in yaml
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// This is an example from the yaml definition from the notekeeper ui
|
||||||
|
/// <code>
|
||||||
|
/// - type: CartridgeUi
|
||||||
|
/// ui: !type:NotekeeperUi
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public sealed class CartridgeUISerializer : ITypeSerializer<CartridgeUI, ValueDataNode>
|
||||||
|
{
|
||||||
|
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
|
||||||
|
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
return serializationManager.ValidateNode<CartridgeUI>(node, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CartridgeUI Read(ISerializationManager serializationManager, ValueDataNode node, IDependencyCollection dependencies,
|
||||||
|
bool skipHook, ISerializationContext? context = null, CartridgeUI? value = default)
|
||||||
|
{
|
||||||
|
return serializationManager.Read(node, context, skipHook, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CartridgeUI Copy(ISerializationManager serializationManager, CartridgeUI source, CartridgeUI target, bool skipHook,
|
||||||
|
ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
return serializationManager.Copy(source, context, skipHook);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataNode Write(ISerializationManager serializationManager, CartridgeUI value, IDependencyCollection dependencies,
|
||||||
|
bool alwaysWrite = false, ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
return serializationManager.WriteValue(value, alwaysWrite, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Content.Client/CartridgeLoader/CartridgeUiComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The component used for defining which ui fragment to use for a cartridge
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="CartridgeUI"/>
|
||||||
|
/// <seealso cref="CartridgeUISerializer"/>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CartridgeUiComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("ui", true, customTypeSerializer: typeof(CartridgeUISerializer))]
|
||||||
|
public CartridgeUI? Ui = default;
|
||||||
|
}
|
||||||
39
Content.Client/CartridgeLoader/Cartridges/NotekeeperUi.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
public sealed class NotekeeperUi : CartridgeUI
|
||||||
|
{
|
||||||
|
private NotekeeperUiFragment? _fragment;
|
||||||
|
|
||||||
|
|
||||||
|
public override Control GetUIFragmentRoot()
|
||||||
|
{
|
||||||
|
return _fragment!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Setup(BoundUserInterface userInterface)
|
||||||
|
{
|
||||||
|
_fragment = new NotekeeperUiFragment();
|
||||||
|
_fragment.OnNoteRemoved += note => SendNotekeeperMessage(NotekeeperUiAction.Remove, note, userInterface);
|
||||||
|
_fragment.OnNoteAdded += note => SendNotekeeperMessage(NotekeeperUiAction.Add, note, userInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
if (state is not NotekeeperUiState notekeepeerState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_fragment?.UpdateState(notekeepeerState.Notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendNotekeeperMessage(NotekeeperUiAction action, string note, BoundUserInterface userInterface)
|
||||||
|
{
|
||||||
|
var notekeeperMessage = new NotekeeperUiMessageEvent(action, note);
|
||||||
|
var message = new CartridgeUiMessage(notekeeperMessage);
|
||||||
|
userInterface.SendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<cartridges:NotekeeperUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||||
|
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" HScrollEnabled="True">
|
||||||
|
<BoxContainer Orientation="Vertical" Name="MessageContainer" HorizontalExpand="True" VerticalExpand="True"/>
|
||||||
|
</ScrollContainer>
|
||||||
|
<LineEdit Name="Input" HorizontalExpand="True" SetHeight="32"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</cartridges:NotekeeperUiFragment>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class NotekeeperUiFragment : BoxContainer
|
||||||
|
{
|
||||||
|
|
||||||
|
public event Action<string>? OnNoteAdded;
|
||||||
|
public event Action<string>? OnNoteRemoved;
|
||||||
|
|
||||||
|
public NotekeeperUiFragment()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
Orientation = LayoutOrientation.Vertical;
|
||||||
|
HorizontalExpand = true;
|
||||||
|
VerticalExpand = true;
|
||||||
|
|
||||||
|
Input.OnTextEntered += _ =>
|
||||||
|
{
|
||||||
|
AddNote(Input.Text);
|
||||||
|
OnNoteAdded?.Invoke(Input.Text);
|
||||||
|
Input.Clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
UpdateState(new List<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(List<string> notes)
|
||||||
|
{
|
||||||
|
MessageContainer.RemoveAllChildren();
|
||||||
|
|
||||||
|
foreach (var note in notes)
|
||||||
|
{
|
||||||
|
AddNote(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddNote(string note)
|
||||||
|
{
|
||||||
|
var row = new BoxContainer();
|
||||||
|
row.HorizontalExpand = true;
|
||||||
|
row.Orientation = LayoutOrientation.Horizontal;
|
||||||
|
row.Margin = new Thickness(4);
|
||||||
|
|
||||||
|
var label = new Label();
|
||||||
|
label.Text = note;
|
||||||
|
label.HorizontalExpand = true;
|
||||||
|
label.ClipText = true;
|
||||||
|
|
||||||
|
var removeButton = new TextureButton();
|
||||||
|
removeButton.AddStyleClass("windowCloseButton");
|
||||||
|
removeButton.OnPressed += _ => OnNoteRemoved?.Invoke(label.Text);
|
||||||
|
|
||||||
|
row.AddChild(label);
|
||||||
|
row.AddChild(removeButton);
|
||||||
|
|
||||||
|
MessageContainer.AddChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Content.Client/PDA/PDABorderColorComponent.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for specifying the pda windows border colors
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class PDABorderColorComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("borderColor", required: true)]
|
||||||
|
public string? BorderColor;
|
||||||
|
|
||||||
|
|
||||||
|
[DataField("accentHColor")]
|
||||||
|
public string? AccentHColor;
|
||||||
|
|
||||||
|
|
||||||
|
[DataField("accentVColor")]
|
||||||
|
public string? AccentVColor;
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
using Content.Client.Message;
|
using Content.Client.CartridgeLoader;
|
||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.CrewManifest;
|
using Content.Shared.CrewManifest;
|
||||||
using Content.Shared.PDA;
|
using Content.Shared.PDA;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
|
||||||
namespace Content.Client.PDA
|
namespace Content.Client.PDA
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class PDABoundUserInterface : BoundUserInterface
|
public sealed class PDABoundUserInterface : CartridgeLoaderBoundUserInterface
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntityManager? _entityManager = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||||
|
|
||||||
private PDAMenu? _menu;
|
private PDAMenu? _menu;
|
||||||
@@ -67,54 +70,52 @@ namespace Content.Client.PDA
|
|||||||
SendMessage(new PDAShowRingtoneMessage());
|
SendMessage(new PDAShowRingtoneMessage());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_menu.OnProgramItemPressed += ActivateCartridge;
|
||||||
|
_menu.OnInstallButtonPressed += InstallCartridge;
|
||||||
|
_menu.OnUninstallButtonPressed += UninstallCartridge;
|
||||||
|
_menu.ProgramCloseButton.OnPressed += _ => DeactivateActiveCartridge();
|
||||||
|
|
||||||
|
var borderColorComponent = GetBorderColorComponent();
|
||||||
|
if (borderColorComponent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_menu.BorderColor = borderColorComponent.BorderColor;
|
||||||
|
_menu.AccentHColor = borderColorComponent.AccentHColor;
|
||||||
|
_menu.AccentVColor = borderColorComponent.AccentVColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
base.UpdateState(state);
|
base.UpdateState(state);
|
||||||
|
|
||||||
if (_menu == null)
|
if (state is not PDAUpdateState updateState)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_menu?.UpdateState(updateState);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (state)
|
|
||||||
|
protected override void AttachCartridgeUI(Control cartridgeUIFragment, string? title)
|
||||||
{
|
{
|
||||||
case PDAUpdateState msg:
|
_menu?.ProgramView.AddChild(cartridgeUIFragment);
|
||||||
|
_menu?.ToProgramView(title ?? Loc.GetString("comp-pda-io-program-fallback-title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DetachCartridgeUI(Control cartridgeUIFragment)
|
||||||
{
|
{
|
||||||
_menu.FlashLightToggleButton.Pressed = msg.FlashlightEnabled;
|
if (_menu is null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (msg.PDAOwnerInfo.ActualOwnerName != null)
|
_menu.ToHomeScreen();
|
||||||
|
_menu.HideProgramHeader();
|
||||||
|
_menu.ProgramView.RemoveChild(cartridgeUIFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAvailablePrograms(List<(EntityUid, CartridgeComponent)> programs)
|
||||||
{
|
{
|
||||||
_menu.PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
|
_menu?.UpdateAvailablePrograms(programs);
|
||||||
("ActualOwnerName", msg.PDAOwnerInfo.ActualOwnerName)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (msg.PDAOwnerInfo.IdOwner != null || msg.PDAOwnerInfo.JobTitle != null)
|
|
||||||
{
|
|
||||||
_menu.IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
|
|
||||||
("Owner",msg.PDAOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")),
|
|
||||||
("JobTitle",msg.PDAOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned"))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_menu.IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui-blank"));
|
|
||||||
}
|
|
||||||
|
|
||||||
_menu.StationNameLabel.SetMarkup(Loc.GetString("comp-pda-ui-station", ("Station",msg.StationName ?? Loc.GetString("comp-pda-ui-unknown"))));
|
|
||||||
|
|
||||||
_menu.EjectIdButton.Visible = msg.PDAOwnerInfo.IdOwner != null || msg.PDAOwnerInfo.JobTitle != null;
|
|
||||||
_menu.EjectPenButton.Visible = msg.HasPen;
|
|
||||||
_menu.ActivateUplinkButton.Visible = msg.HasUplink;
|
|
||||||
_menu.ActivateMusicButton.Visible = msg.CanPlayMusic;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
@@ -123,5 +124,10 @@ namespace Content.Client.PDA
|
|||||||
|
|
||||||
_menu?.Dispose();
|
_menu?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PDABorderColorComponent? GetBorderColorComponent()
|
||||||
|
{
|
||||||
|
return _entityManager?.GetComponentOrNull<PDABorderColorComponent>(Owner.Owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
<DefaultWindow xmlns="https://spacestation14.io"
|
<pda:PDAWindow xmlns="https://spacestation14.io"
|
||||||
Title="{Loc 'comp-pda-ui-menu-title'}"
|
xmlns:pda="clr-namespace:Content.Client.PDA"
|
||||||
MinSize="576 256"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
SetSize="576 256">
|
MinSize="576 450"
|
||||||
<TabContainer Name="MasterTabContainer">
|
SetSize="576 450">
|
||||||
|
<!-- This: (Margin="1 1 3 0") is necessary so the navigation bar doesn't sticks into the black content border. -->
|
||||||
|
<BoxContainer Name="NavigationBar" HorizontalExpand="True" MinHeight="32" Margin="1 1 3 0">
|
||||||
|
<pda:PDANavigationButton Name="HomeButton" SetWidth="32" CurrentTabBorderThickness="0 0 2 0" IsCurrent="True"/>
|
||||||
|
<pda:PDANavigationButton Name="ProgramListButton" Access="Public" MinWidth="100" LabelText="{Loc 'comp-pda-io-program-list-button'}"/>
|
||||||
|
<pda:PDANavigationButton Name="SettingsButton" MinWidth="100" LabelText="{Loc 'comp-pda-io-settings-button'}"/>
|
||||||
|
|
||||||
|
<pda:PDANavigationButton Name="ProgramTitle" Access="Public" BorderThickness="0 0 0 2" CurrentTabBorderThickness="2 0 0 2"
|
||||||
|
ActiveBgColor="#202023" Visible="False"/>
|
||||||
|
|
||||||
|
<pda:PDANavigationButton HorizontalExpand="True"/>
|
||||||
|
|
||||||
|
<pda:PDANavigationButton Name="ProgramCloseButton" Access="Public" IconScale="0.5 0.5" BorderThickness="0 0 2 2"
|
||||||
|
Visible="False" IsActive="False" SetWidth="32"/>
|
||||||
|
|
||||||
|
<pda:PDANavigationButton Name="FlashLightToggleButton" Access="Public" ToggleMode="True" ActiveFgColor="#EAEFBB" SetWidth="32"/>
|
||||||
|
<pda:PDANavigationButton Name="EjectPenButton" Access="Public" SetWidth="32"/>
|
||||||
|
<pda:PDANavigationButton Name="EjectIdButton" Access="Public" SetWidth="32"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="ViewContainer" HorizontalExpand="True" VerticalExpand="True" Access="Public">
|
||||||
<BoxContainer Orientation="Vertical"
|
<BoxContainer Orientation="Vertical"
|
||||||
VerticalExpand="True"
|
VerticalExpand="True"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
MinSize="50 50">
|
MinSize="50 50"
|
||||||
|
Margin="8">
|
||||||
<RichTextLabel Name="PdaOwnerLabel" Access="Public" />
|
<RichTextLabel Name="PdaOwnerLabel" Access="Public" />
|
||||||
<RichTextLabel Name="IdInfoLabel"
|
<RichTextLabel Name="IdInfoLabel"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
@@ -16,37 +36,53 @@
|
|||||||
<RichTextLabel Name="StationNameLabel"
|
<RichTextLabel Name="StationNameLabel"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
HorizontalExpand="True" />
|
HorizontalExpand="True" />
|
||||||
<Button Name="EjectIdButton"
|
|
||||||
Access="Public"
|
|
||||||
Text="{Loc 'comp-pda-ui-eject-id-button'}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center" />
|
|
||||||
<Button Name="EjectPenButton"
|
|
||||||
Access="Public"
|
|
||||||
Text="{Loc 'comp-pda-ui-eject-pen-button'}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center" />
|
|
||||||
<Button Name="AccessRingtoneButton"
|
|
||||||
Access="Public"
|
|
||||||
Text="{Loc 'comp-pda-ui-ringtone-button'}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center" />
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
<Button Name="FlashLightToggleButton"
|
</BoxContainer>
|
||||||
|
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
VerticalExpand="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinSize="50 50"
|
||||||
|
Name="ProgramList"
|
||||||
|
Margin="4"/>
|
||||||
|
</ScrollContainer>
|
||||||
|
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
VerticalExpand="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinSize="50 50"
|
||||||
|
Name="Settings">
|
||||||
|
<pda:PDASettingsButton Name="AccessRingtoneButton"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
Text="{Loc 'comp-pda-ui-toggle-flashlight-button'}"
|
Text="{Loc 'comp-pda-ui-ringtone-button'}"
|
||||||
ToggleMode="True" />
|
Description="{Loc 'comp-pda-ui-ringtone-button-description'}"/>
|
||||||
<Button Name="CrewManifestButton"
|
<pda:PDASettingsButton Name="CrewManifestButton"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
Text="{Loc 'crew-manifest-button-label'}"
|
Text="{Loc 'crew-manifest-button-label'}"
|
||||||
|
Description="{Loc 'crew-manifest-button-description'}"
|
||||||
Visible="False" />
|
Visible="False" />
|
||||||
<Button Name="ActivateUplinkButton"
|
<pda:PDASettingsButton Name="ActivateUplinkButton"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}" />
|
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}"
|
||||||
<Button Name="ActivateMusicButton"
|
Description="{Loc 'pda-bound-user-interface-uplink-tab-description'}"/>
|
||||||
|
<pda:PDASettingsButton Name="ActivateMusicButton"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
Text="{Loc 'pda-bound-user-interface-music-button'}" />
|
Text="{Loc 'pda-bound-user-interface-music-button'}"
|
||||||
|
Description="{Loc 'pda-bound-user-interface-music-button-description'}"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</TabContainer>
|
</ScrollContainer>
|
||||||
</DefaultWindow>
|
<BoxContainer Orientation="Vertical"
|
||||||
|
VerticalExpand="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
Name="ProgramView"
|
||||||
|
Access="Public">
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="ContentFooter" HorizontalExpand="True" SetHeight="28" Margin="1 0 2 1">
|
||||||
|
<controls:StripeBack HasBottomEdge="False" HasMargins="False" HorizontalExpand="True">
|
||||||
|
<Label Text="Robust#OS ™" VerticalAlignment="Center" Margin="6 0" StyleClasses="PDAContentFooterText"/>
|
||||||
|
<Label Name="AddressLabel" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="6 0" StyleClasses="PDAContentFooterText"/>
|
||||||
|
</controls:StripeBack>
|
||||||
|
</BoxContainer>
|
||||||
|
</pda:PDAWindow>
|
||||||
|
|||||||
@@ -1,18 +1,252 @@
|
|||||||
using Robust.Client.AutoGenerated;
|
using Content.Client.Message;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Content.Shared.PDA;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.PDA
|
namespace Content.Client.PDA
|
||||||
{
|
{
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class PDAMenu : DefaultWindow
|
public sealed partial class PDAMenu : PDAWindow
|
||||||
{
|
{
|
||||||
|
public const int HomeView = 0;
|
||||||
|
public const int ProgramListView = 1;
|
||||||
|
public const int SettingsView = 2;
|
||||||
|
public const int ProgramContentView = 3;
|
||||||
|
|
||||||
|
private int _currentView = 0;
|
||||||
|
|
||||||
|
public event Action<EntityUid>? OnProgramItemPressed;
|
||||||
|
public event Action<EntityUid>? OnUninstallButtonPressed;
|
||||||
|
public event Action<EntityUid>? OnInstallButtonPressed;
|
||||||
public PDAMenu()
|
public PDAMenu()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("pda-bound-user-interface-main-menu-tab-title"));
|
ViewContainer.OnChildAdded += control => control.Visible = false;
|
||||||
|
|
||||||
|
HomeButton.IconTexture = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/home.png"));
|
||||||
|
FlashLightToggleButton.IconTexture = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/light.png"));
|
||||||
|
EjectPenButton.IconTexture = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/pencil.png"));
|
||||||
|
EjectIdButton.IconTexture = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/eject.png"));
|
||||||
|
ProgramCloseButton.IconTexture = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Nano/cross.svg.png"));
|
||||||
|
|
||||||
|
|
||||||
|
HomeButton.OnPressed += _ => ToHomeScreen();
|
||||||
|
|
||||||
|
ProgramListButton.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
HomeButton.IsCurrent = false;
|
||||||
|
ProgramListButton.IsCurrent = true;
|
||||||
|
SettingsButton.IsCurrent = false;
|
||||||
|
ProgramTitle.IsCurrent = false;
|
||||||
|
|
||||||
|
ChangeView(ProgramListView);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SettingsButton.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
HomeButton.IsCurrent = false;
|
||||||
|
ProgramListButton.IsCurrent = false;
|
||||||
|
SettingsButton.IsCurrent = true;
|
||||||
|
ProgramTitle.IsCurrent = false;
|
||||||
|
|
||||||
|
ChangeView(SettingsView);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgramTitle.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
HomeButton.IsCurrent = false;
|
||||||
|
ProgramListButton.IsCurrent = false;
|
||||||
|
SettingsButton.IsCurrent = false;
|
||||||
|
ProgramTitle.IsCurrent = true;
|
||||||
|
|
||||||
|
ChangeView(ProgramContentView);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgramCloseButton.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
HideProgramHeader();
|
||||||
|
ToHomeScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
HideAllViews();
|
||||||
|
ToHomeScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(PDAUpdateState state)
|
||||||
|
{
|
||||||
|
FlashLightToggleButton.IsActive = state.FlashlightEnabled;
|
||||||
|
|
||||||
|
if (state.PDAOwnerInfo.ActualOwnerName != null)
|
||||||
|
{
|
||||||
|
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
|
||||||
|
("ActualOwnerName", state.PDAOwnerInfo.ActualOwnerName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (state.PDAOwnerInfo.IdOwner != null || state.PDAOwnerInfo.JobTitle != null)
|
||||||
|
{
|
||||||
|
IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
|
||||||
|
("Owner",state.PDAOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")),
|
||||||
|
("JobTitle",state.PDAOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned"))));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui-blank"));
|
||||||
|
}
|
||||||
|
|
||||||
|
StationNameLabel.SetMarkup(Loc.GetString("comp-pda-ui-station", ("Station",state.StationName ?? Loc.GetString("comp-pda-ui-unknown"))));
|
||||||
|
AddressLabel.Text = state.Address?.ToUpper() ?? " - ";
|
||||||
|
|
||||||
|
EjectIdButton.IsActive = state.PDAOwnerInfo.IdOwner != null || state.PDAOwnerInfo.JobTitle != null;
|
||||||
|
EjectPenButton.IsActive = state.HasPen;
|
||||||
|
ActivateUplinkButton.Visible = state.HasUplink;
|
||||||
|
ActivateMusicButton.Visible = state.CanPlayMusic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAvailablePrograms(List<(EntityUid, CartridgeComponent)> programs)
|
||||||
|
{
|
||||||
|
ProgramList.RemoveAllChildren();
|
||||||
|
|
||||||
|
if (programs.Count == 0)
|
||||||
|
{
|
||||||
|
ProgramList.AddChild(new Label()
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("comp-pda-io-no-programs-available"),
|
||||||
|
HorizontalAlignment = HAlignment.Center,
|
||||||
|
VerticalAlignment = VAlignment.Center,
|
||||||
|
VerticalExpand = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var row = CreateProgramListRow();
|
||||||
|
var itemCount = 1;
|
||||||
|
ProgramList.AddChild(row);
|
||||||
|
|
||||||
|
foreach (var (uid, component) in programs)
|
||||||
|
{
|
||||||
|
//Create a new row every second program item starting from the first
|
||||||
|
if (itemCount % 2 != 0)
|
||||||
|
{
|
||||||
|
row = CreateProgramListRow();
|
||||||
|
ProgramList.AddChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = new PDAProgramItem();
|
||||||
|
|
||||||
|
if (component.Icon is not null)
|
||||||
|
item.Icon.SetFromSpriteSpecifier(component.Icon);
|
||||||
|
|
||||||
|
item.OnPressed += _ => OnProgramItemPressed?.Invoke(uid);
|
||||||
|
|
||||||
|
switch (component.InstallationStatus)
|
||||||
|
{
|
||||||
|
case InstallationStatus.Cartridge:
|
||||||
|
item.InstallButton.Visible = true;
|
||||||
|
item.InstallButton.Text = Loc.GetString("cartridge-bound-user-interface-install-button");
|
||||||
|
item.InstallButton.OnPressed += _ => OnInstallButtonPressed?.Invoke(uid);
|
||||||
|
break;
|
||||||
|
case InstallationStatus.Installed:
|
||||||
|
item.InstallButton.Visible = true;
|
||||||
|
item.InstallButton.Text = Loc.GetString("cartridge-bound-user-interface-uninstall-button");
|
||||||
|
item.InstallButton.OnPressed += _ => OnUninstallButtonPressed?.Invoke(uid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.ProgramName.Text = Loc.GetString(component.ProgramName);
|
||||||
|
item.SetHeight = 20;
|
||||||
|
row.AddChild(item);
|
||||||
|
|
||||||
|
itemCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add a filler item to the last row when it only contains one item
|
||||||
|
if (itemCount % 2 == 0)
|
||||||
|
row.AddChild(new Control() { HorizontalExpand = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the current view to the home screen (view 0) and sets the tabs `IsCurrent` flag accordingly
|
||||||
|
/// </summary>
|
||||||
|
public void ToHomeScreen()
|
||||||
|
{
|
||||||
|
HomeButton.IsCurrent = true;
|
||||||
|
ProgramListButton.IsCurrent = false;
|
||||||
|
SettingsButton.IsCurrent = false;
|
||||||
|
ProgramTitle.IsCurrent = false;
|
||||||
|
|
||||||
|
ChangeView(HomeView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the program title and close button.
|
||||||
|
/// </summary>
|
||||||
|
public void HideProgramHeader()
|
||||||
|
{
|
||||||
|
ProgramTitle.IsCurrent = false;
|
||||||
|
ProgramTitle.Visible = false;
|
||||||
|
ProgramCloseButton.Visible = false;
|
||||||
|
ProgramListButton.Visible = true;
|
||||||
|
SettingsButton.Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the current view to the program content view (view 3), sets the program title and sets the tabs `IsCurrent` flag accordingly
|
||||||
|
/// </summary>
|
||||||
|
public void ToProgramView(string title)
|
||||||
|
{
|
||||||
|
HomeButton.IsCurrent = false;
|
||||||
|
ProgramListButton.IsCurrent = false;
|
||||||
|
SettingsButton.IsCurrent = false;
|
||||||
|
ProgramTitle.IsCurrent = false;
|
||||||
|
ProgramTitle.IsCurrent = true;
|
||||||
|
ProgramTitle.Visible = true;
|
||||||
|
ProgramCloseButton.Visible = true;
|
||||||
|
ProgramListButton.Visible = false;
|
||||||
|
SettingsButton.Visible = false;
|
||||||
|
|
||||||
|
ProgramTitle.LabelText = title;
|
||||||
|
ChangeView(ProgramContentView);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the current view to the given view number
|
||||||
|
/// </summary>
|
||||||
|
public void ChangeView(int view)
|
||||||
|
{
|
||||||
|
if (ViewContainer.ChildCount <= view)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ViewContainer.GetChild(_currentView).Visible = false;
|
||||||
|
ViewContainer.GetChild(view).Visible = true;
|
||||||
|
_currentView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoxContainer CreateProgramListRow()
|
||||||
|
{
|
||||||
|
return new BoxContainer()
|
||||||
|
{
|
||||||
|
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||||
|
HorizontalExpand = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideAllViews()
|
||||||
|
{
|
||||||
|
var views = ViewContainer.Children;
|
||||||
|
foreach (var view in views)
|
||||||
|
{
|
||||||
|
view.Visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
Content.Client/PDA/PDANavigationButton.xaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<pda:PDANavigationButton xmlns="https://spacestation14.io" xmlns:pda="clr-namespace:Content.Client.PDA">
|
||||||
|
<PanelContainer Name="Background"/>
|
||||||
|
<AnimatedTextureRect Margin="0 0 0 2" Visible="False" Name="Icon" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
<Label Visible="True" Name="Label" Margin="8 0 8 2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</pda:PDANavigationButton>
|
||||||
108
Content.Client/PDA/PDANavigationButton.xaml.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class PDANavigationButton : ContainerButton
|
||||||
|
{
|
||||||
|
|
||||||
|
private bool _isCurrent;
|
||||||
|
private bool _isActive = true;
|
||||||
|
|
||||||
|
private Thickness _borderThickness = new(0, 0, 0, 2);
|
||||||
|
private Thickness _currentTabBorderThickness = new(2, 0, 2, 0);
|
||||||
|
|
||||||
|
private readonly StyleBoxFlat _styleBox = new()
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromHex("#202023"),
|
||||||
|
BorderColor = Color.FromHex("#5a5a5a"),
|
||||||
|
BorderThickness = new Thickness(0, 0, 0, 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
public string InactiveBgColor { get; set; } = "#202023";
|
||||||
|
public string ActiveBgColor { get; set; } = "#25252a";
|
||||||
|
public string InactiveFgColor { get; set; } = "#5a5a5a";
|
||||||
|
public string ActiveFgColor { get; set; } = "#FFFFFF";
|
||||||
|
|
||||||
|
public SpriteSpecifier? IconTexture
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Icon.Visible = value != null;
|
||||||
|
Label.Visible = value == null;
|
||||||
|
|
||||||
|
if (value is not null)
|
||||||
|
Icon.SetFromSpriteSpecifier(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 IconScale
|
||||||
|
{
|
||||||
|
get => Icon.DisplayRect.TextureScale;
|
||||||
|
set => Icon.DisplayRect.TextureScale = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? LabelText
|
||||||
|
{
|
||||||
|
get => Label.Text;
|
||||||
|
set => Label.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the border thickness when the tab is not the currently active one
|
||||||
|
/// </summary>
|
||||||
|
public Thickness BorderThickness
|
||||||
|
{
|
||||||
|
get => _borderThickness;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_borderThickness = value;
|
||||||
|
_styleBox.BorderThickness = _isCurrent ? _currentTabBorderThickness : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the border thickness when this tab is the currently active tab
|
||||||
|
/// </summary>
|
||||||
|
public Thickness CurrentTabBorderThickness
|
||||||
|
{
|
||||||
|
get => _currentTabBorderThickness;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_currentTabBorderThickness = value;
|
||||||
|
_styleBox.BorderThickness = _isCurrent ? value : _borderThickness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCurrent
|
||||||
|
{
|
||||||
|
get => _isCurrent;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isCurrent = value;
|
||||||
|
_styleBox.BackgroundColor = Color.FromHex(value ? ActiveBgColor : InactiveBgColor);
|
||||||
|
_styleBox.BorderThickness = value ? CurrentTabBorderThickness : BorderThickness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsActive
|
||||||
|
{
|
||||||
|
get => _isActive;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isActive = value;
|
||||||
|
Icon.Modulate = Color.FromHex(value ? ActiveFgColor : InactiveFgColor);
|
||||||
|
Label.FontColorOverride = Color.FromHex(value ? ActiveFgColor : InactiveFgColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PDANavigationButton()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
Background.PanelOverride = _styleBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Content.Client/PDA/PDAProgramItem.xaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<pda:PDAProgramItem HorizontalExpand="True" MinHeight="60" Margin="4"
|
||||||
|
xmlns:pda="clr-namespace:Content.Client.PDA"
|
||||||
|
xmlns="https://spacestation14.io">
|
||||||
|
<PanelContainer Name="Panel"/>
|
||||||
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalExpand="True" MinWidth="60">
|
||||||
|
<AnimatedTextureRect HorizontalAlignment="Center" VerticalAlignment="Center" VerticalExpand="True" Name="Icon" Access="Public"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
|
||||||
|
<Label Name="ProgramName" Access="Public" VerticalExpand="True"/>
|
||||||
|
<BoxContainer HorizontalExpand="True" SetHeight="28" Margin="0 0 4 4">
|
||||||
|
<BoxContainer HorizontalExpand="True"/>
|
||||||
|
<Button Name="InstallButton" Access="Public" Visible="False" SetWidth="90" HorizontalAlignment="Right" VerticalExpand="True"></Button>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</pda:PDAProgramItem>
|
||||||
42
Content.Client/PDA/PDAProgramItem.xaml.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
|
||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class PDAProgramItem : ContainerButton
|
||||||
|
{
|
||||||
|
public const string StylePropertyBgColor = "backgroundColor";
|
||||||
|
public const string NormalBgColor = "#313138";
|
||||||
|
public const string HoverColor = "#3E6C45";
|
||||||
|
|
||||||
|
private readonly StyleBoxFlat _styleBox = new()
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromHex("#25252a"),
|
||||||
|
};
|
||||||
|
|
||||||
|
public Color BackgroundColor
|
||||||
|
{
|
||||||
|
get => _styleBox.BackgroundColor;
|
||||||
|
set => _styleBox.BackgroundColor = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PDAProgramItem()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
Panel.PanelOverride = _styleBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
|
{
|
||||||
|
base.Draw(handle);
|
||||||
|
|
||||||
|
if (TryGetStyleProperty<Color>(StylePropertyBgColor, out var bgColor))
|
||||||
|
BackgroundColor = bgColor;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Content.Client/PDA/PDASettingsButton.xaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<pda:PDASettingsButton xmlns="https://spacestation14.io"
|
||||||
|
xmlns:pda="clr-namespace:Content.Client.PDA"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinHeight="48"
|
||||||
|
Margin="5 4 6 0">
|
||||||
|
<PanelContainer Name="Panel"/>
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="8 4 0 4">
|
||||||
|
<Label Name="OptionName"/>
|
||||||
|
<Label Name="OptionDescription"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</pda:PDASettingsButton>
|
||||||
69
Content.Client/PDA/PDASettingsButton.xaml.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class PDASettingsButton : ContainerButton
|
||||||
|
{
|
||||||
|
public const string StylePropertyFgColor = "foregroundColor";
|
||||||
|
public const string StylePropertyBgColor = "backgroundColor";
|
||||||
|
public const string NormalBgColor = "#313138";
|
||||||
|
public const string HoverColor = "#3E6C45";
|
||||||
|
public const string PressedColor = "#3E6C45";
|
||||||
|
public const string DisabledFgColor = "#5a5a5a";
|
||||||
|
public const string EnabledFgColor = "#FFFFFF";
|
||||||
|
|
||||||
|
private readonly StyleBoxFlat _styleBox = new()
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromHex("#25252a")
|
||||||
|
};
|
||||||
|
|
||||||
|
public string? Text
|
||||||
|
{
|
||||||
|
get => OptionName.Text;
|
||||||
|
set => OptionName.Text = value;
|
||||||
|
}
|
||||||
|
public string? Description
|
||||||
|
{
|
||||||
|
get => OptionDescription.Text;
|
||||||
|
set => OptionDescription.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color BackgroundColor
|
||||||
|
{
|
||||||
|
get => _styleBox.BackgroundColor;
|
||||||
|
set => _styleBox.BackgroundColor = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color? ForegroundColor
|
||||||
|
{
|
||||||
|
get => OptionName.FontColorOverride;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
OptionName.FontColorOverride = value;
|
||||||
|
OptionDescription.FontColorOverride = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PDASettingsButton()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
Panel.PanelOverride = _styleBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
|
{
|
||||||
|
base.Draw(handle);
|
||||||
|
|
||||||
|
if (TryGetStyleProperty<Color>(StylePropertyBgColor, out var bgColor))
|
||||||
|
BackgroundColor = bgColor;
|
||||||
|
|
||||||
|
if (TryGetStyleProperty<Color>(StylePropertyFgColor, out var fgColor))
|
||||||
|
ForegroundColor = fgColor;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Content.Client/PDA/PDAWindow.xaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<pda:PDAWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:pda="clr-namespace:Content.Client.PDA"
|
||||||
|
MouseFilter="Stop">
|
||||||
|
<PanelContainer Name="Background" Access="Public" StyleClasses="PDABackgroundRect" />
|
||||||
|
<!-- The negative markin fixes a gap between the window edges and the decorative panel -->
|
||||||
|
<PanelContainer Name="AccentH" Margin="-1 170 -2 170" Access="Public" StyleClasses="PDABackground" />
|
||||||
|
<PanelContainer Name="AccentV" Margin="220 -1 220 -1" Access="Public" StyleClasses="PDABackground" />
|
||||||
|
<PanelContainer Name="Border" StyleClasses="PDABorderRect" />
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
|
<!--Heading-->
|
||||||
|
<BoxContainer SetHeight="26" Margin="4 2 8 0" Orientation="Horizontal">
|
||||||
|
<Control HorizontalExpand="True"/>
|
||||||
|
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton"
|
||||||
|
VerticalAlignment="Center" Margin="0 4 4 0"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<!--Content-->
|
||||||
|
<Control Margin="18 0" RectClipContent="True" VerticalExpand="true"
|
||||||
|
HorizontalExpand="True">
|
||||||
|
<PanelContainer Name="ContentBorder" StyleClasses="PDABackground"/>
|
||||||
|
<Control Margin="3 3">
|
||||||
|
<PanelContainer Name="ContentBackground" StyleClasses="PDAContentBackground"/>
|
||||||
|
<BoxContainer Access="Public" Name="ContentsContainer" Orientation="Vertical" StyleClasses="PDAContent"/>
|
||||||
|
|
||||||
|
</Control>
|
||||||
|
</Control>
|
||||||
|
<!--Footer-->
|
||||||
|
<BoxContainer Orientation="Horizontal" SetHeight="28">
|
||||||
|
<Label Text="Personal Digital Assistant" StyleClasses="PDAWindowFooterText" Margin="32 0 0 6"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</pda:PDAWindow>
|
||||||
56
Content.Client/PDA/PDAWindow.xaml.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public partial class PDAWindow : BaseWindow
|
||||||
|
{
|
||||||
|
|
||||||
|
public string? BorderColor
|
||||||
|
{
|
||||||
|
get => Background.ActualModulateSelf.ToHex();
|
||||||
|
|
||||||
|
set => Background.ModulateSelfOverride = Color.FromHex(value, Color.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? AccentHColor
|
||||||
|
{
|
||||||
|
get => AccentH.ActualModulateSelf.ToHex();
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
AccentH.ModulateSelfOverride = Color. FromHex(value, Color.White);
|
||||||
|
AccentH.Visible = value != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? AccentVColor
|
||||||
|
{
|
||||||
|
get => AccentV.ActualModulateSelf.ToHex();
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
AccentV.ModulateSelfOverride = Color. FromHex(value, Color.White);
|
||||||
|
AccentV.Visible = value != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PDAWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
CloseButton.OnPressed += _ => Close();
|
||||||
|
XamlChildren = ContentsContainer.Children;
|
||||||
|
|
||||||
|
AccentH.Visible = false;
|
||||||
|
AccentV.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||||
|
{
|
||||||
|
return DragMode.Move;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ namespace Content.Client.Stylesheets
|
|||||||
protected StyleBoxTexture BaseButtonSquare { get; }
|
protected StyleBoxTexture BaseButtonSquare { get; }
|
||||||
|
|
||||||
protected StyleBoxTexture BaseAngleRect { get; }
|
protected StyleBoxTexture BaseAngleRect { get; }
|
||||||
|
protected StyleBoxTexture AngleBorderRect { get; }
|
||||||
|
|
||||||
protected StyleBase(IResourceCache resCache)
|
protected StyleBase(IResourceCache resCache)
|
||||||
{
|
{
|
||||||
@@ -114,6 +115,12 @@ namespace Content.Client.Stylesheets
|
|||||||
};
|
};
|
||||||
BaseAngleRect.SetPatchMargin(StyleBox.Margin.All, 10);
|
BaseAngleRect.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||||
|
|
||||||
|
AngleBorderRect = new StyleBoxTexture
|
||||||
|
{
|
||||||
|
Texture = resCache.GetTexture("/Textures/Interface/Nano/geometric_panel_border.svg.96dpi.png"),
|
||||||
|
};
|
||||||
|
AngleBorderRect.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||||
|
|
||||||
var vScrollBarGrabberNormal = new StyleBoxFlat
|
var vScrollBarGrabberNormal = new StyleBoxFlat
|
||||||
{
|
{
|
||||||
BackgroundColor = Color.Gray.WithAlpha(0.35f), ContentMarginLeftOverride = DefaultGrabberSize,
|
BackgroundColor = Color.Gray.WithAlpha(0.35f), ContentMarginLeftOverride = DefaultGrabberSize,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Actions.UI;
|
|
||||||
using Content.Client.ContextMenu.UI;
|
using Content.Client.ContextMenu.UI;
|
||||||
using Content.Client.Examine;
|
using Content.Client.Examine;
|
||||||
|
using Content.Client.PDA;
|
||||||
using Content.Client.Resources;
|
using Content.Client.Resources;
|
||||||
using Content.Client.Targeting;
|
|
||||||
using Content.Client.Targeting.UI;
|
using Content.Client.Targeting.UI;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Verbs.UI;
|
using Content.Client.Verbs.UI;
|
||||||
@@ -13,7 +12,6 @@ using Robust.Client.ResourceManagement;
|
|||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using static Robust.Client.UserInterface.StylesheetHelpers;
|
using static Robust.Client.UserInterface.StylesheetHelpers;
|
||||||
|
|
||||||
namespace Content.Client.Stylesheets
|
namespace Content.Client.Stylesheets
|
||||||
@@ -1395,6 +1393,60 @@ namespace Content.Client.Stylesheets
|
|||||||
Element<Label>().Class("Disabled")
|
Element<Label>().Class("Disabled")
|
||||||
.Prop("font-color", DisabledFore),
|
.Prop("font-color", DisabledFore),
|
||||||
|
|
||||||
|
//PDA - Backgrounds
|
||||||
|
Element<PanelContainer>().Class("PDAContentBackground")
|
||||||
|
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenBoth)
|
||||||
|
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252a")),
|
||||||
|
|
||||||
|
Element<PanelContainer>().Class("PDABackground")
|
||||||
|
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenBoth)
|
||||||
|
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#000000")),
|
||||||
|
|
||||||
|
Element<PanelContainer>().Class("PDABackgroundRect")
|
||||||
|
.Prop(PanelContainer.StylePropertyPanel, BaseAngleRect)
|
||||||
|
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#717059")),
|
||||||
|
|
||||||
|
Element<PanelContainer>().Class("PDABorderRect")
|
||||||
|
.Prop(PanelContainer.StylePropertyPanel, AngleBorderRect),
|
||||||
|
|
||||||
|
Element<PanelContainer>().Class("BackgroundDark")
|
||||||
|
.Prop(PanelContainer.StylePropertyPanel, new StyleBoxFlat(Color.FromHex("#25252A"))),
|
||||||
|
|
||||||
|
//PDA - Buttons
|
||||||
|
Element<PDASettingsButton>().Pseudo(ContainerButton.StylePseudoClassNormal)
|
||||||
|
.Prop(PDASettingsButton.StylePropertyBgColor, Color.FromHex(PDASettingsButton.NormalBgColor))
|
||||||
|
.Prop(PDASettingsButton.StylePropertyFgColor, Color.FromHex(PDASettingsButton.EnabledFgColor)),
|
||||||
|
|
||||||
|
Element<PDASettingsButton>().Pseudo(ContainerButton.StylePseudoClassHover)
|
||||||
|
.Prop(PDASettingsButton.StylePropertyBgColor, Color.FromHex(PDASettingsButton.HoverColor))
|
||||||
|
.Prop(PDASettingsButton.StylePropertyFgColor, Color.FromHex(PDASettingsButton.EnabledFgColor)),
|
||||||
|
|
||||||
|
Element<PDASettingsButton>().Pseudo(ContainerButton.StylePseudoClassPressed)
|
||||||
|
.Prop(PDASettingsButton.StylePropertyBgColor, Color.FromHex(PDASettingsButton.PressedColor))
|
||||||
|
.Prop(PDASettingsButton.StylePropertyFgColor, Color.FromHex(PDASettingsButton.EnabledFgColor)),
|
||||||
|
|
||||||
|
Element<PDASettingsButton>().Pseudo(ContainerButton.StylePseudoClassDisabled)
|
||||||
|
.Prop(PDASettingsButton.StylePropertyBgColor, Color.FromHex(PDASettingsButton.NormalBgColor))
|
||||||
|
.Prop(PDASettingsButton.StylePropertyFgColor, Color.FromHex(PDASettingsButton.DisabledFgColor)),
|
||||||
|
|
||||||
|
Element<PDAProgramItem>().Pseudo(ContainerButton.StylePseudoClassNormal)
|
||||||
|
.Prop(PDAProgramItem.StylePropertyBgColor, Color.FromHex(PDAProgramItem.NormalBgColor)),
|
||||||
|
|
||||||
|
Element<PDAProgramItem>().Pseudo(ContainerButton.StylePseudoClassHover)
|
||||||
|
.Prop(PDAProgramItem.StylePropertyBgColor, Color.FromHex(PDAProgramItem.HoverColor)),
|
||||||
|
|
||||||
|
Element<PDAProgramItem>().Pseudo(ContainerButton.StylePseudoClassPressed)
|
||||||
|
.Prop(PDAProgramItem.StylePropertyBgColor, Color.FromHex(PDAProgramItem.HoverColor)),
|
||||||
|
|
||||||
|
//PDA - Text
|
||||||
|
Element<Label>().Class("PDAContentFooterText")
|
||||||
|
.Prop(Label.StylePropertyFont, notoSans10)
|
||||||
|
.Prop(Label.StylePropertyFontColor, Color.FromHex("#757575")),
|
||||||
|
|
||||||
|
Element<Label>().Class("PDAWindowFooterText")
|
||||||
|
.Prop(Label.StylePropertyFont, notoSans10)
|
||||||
|
.Prop(Label.StylePropertyFontColor, Color.FromHex("#333d3b")),
|
||||||
|
|
||||||
}).ToList());
|
}).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Content.Server/CartridgeLoader/CartridgeLoaderComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||||
|
|
||||||
|
namespace Content.Server.CartridgeLoader;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedCartridgeLoaderComponent))]
|
||||||
|
public sealed class CartridgeLoaderComponent : SharedCartridgeLoaderComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum amount of programs that can be installed on the cartridge loader entity
|
||||||
|
/// </summary>
|
||||||
|
[DataField("diskSpace")]
|
||||||
|
public int DiskSpace = 5;
|
||||||
|
|
||||||
|
[DataField("uiKey", readOnly: true, required: true, customTypeSerializer: typeof(EnumSerializer))]
|
||||||
|
public Enum UiKey = default!;
|
||||||
|
}
|
||||||
417
Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
using Content.Server.DeviceNetwork.Systems;
|
||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Server.Containers;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.CartridgeLoader;
|
||||||
|
|
||||||
|
public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ContainerSystem _containerSystem = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||||
|
|
||||||
|
private const string ContainerName = "program-container";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CartridgeLoaderComponent, MapInitEvent>(OnMapInit);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CartridgeLoaderComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||||
|
SubscribeLocalEvent<CartridgeLoaderComponent, AfterInteractEvent>(OnUsed);
|
||||||
|
SubscribeLocalEvent<CartridgeLoaderComponent, CartridgeLoaderUiMessage>(OnLoaderUiMessage);
|
||||||
|
SubscribeLocalEvent<CartridgeLoaderComponent, CartridgeUiMessage>(OnUiMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the cartridge loaders ui state.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Because the cartridge loader integrates with the ui of the entity using it, the entities ui state needs to inherit from <see cref="CartridgeLoaderUiState"/>
|
||||||
|
/// and use this method to update its state so the cartridge loaders state can be added to it.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="PDA.PDASystem.UpdatePDAUserInterface"/>
|
||||||
|
public void UpdateUiState(EntityUid loaderUid, CartridgeLoaderUiState state, IPlayerSession? session = default!, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
state.ActiveUI = loader.ActiveProgram;
|
||||||
|
state.Programs = GetAvailablePrograms(loaderUid, loader);
|
||||||
|
|
||||||
|
var ui = _userInterfaceSystem.GetUiOrNull(loader.Owner, loader.UiKey);
|
||||||
|
if (ui != null)
|
||||||
|
_userInterfaceSystem.SetUiState(ui, state, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the programs ui state
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loaderUid">The cartridge loaders entity uid</param>
|
||||||
|
/// <param name="state">The programs ui state. Programs should use their own ui state class inheriting from <see cref="BoundUserInterfaceState"/></param>
|
||||||
|
/// <param name="session">The players session</param>
|
||||||
|
/// <param name="loader">The cartridge loader component</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is called "UpdateCartridgeUiState" but cartridges and a programs are the same. A cartridge is just a program as a visible item.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="Cartridges.NotekeeperCartridgeSystem.UpdateUiState"/>
|
||||||
|
public void UpdateCartridgeUiState(EntityUid loaderUid, BoundUserInterfaceState state, IPlayerSession? session = default!, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ui = _userInterfaceSystem.GetUiOrNull(loader.Owner, loader.UiKey);
|
||||||
|
if (ui != null)
|
||||||
|
_userInterfaceSystem.SetUiState(ui, state, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all installed programs and the inserted cartridge if it isn't already installed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The cartridge loaders uid</param>
|
||||||
|
/// <param name="loader">The cartridge loader component</param>
|
||||||
|
/// <returns>A list of all the available program entity ids</returns>
|
||||||
|
public List<EntityUid> GetAvailablePrograms(EntityUid uid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref loader))
|
||||||
|
return new List<EntityUid>();
|
||||||
|
|
||||||
|
//Don't count a cartridge that has already been installed as available to avoid confusion
|
||||||
|
if (loader.CartridgeSlot.HasItem && IsInstalled(Prototype(loader.CartridgeSlot.Item!.Value)?.ID, loader))
|
||||||
|
return loader.InstalledPrograms;
|
||||||
|
|
||||||
|
var available = new List<EntityUid>();
|
||||||
|
available.AddRange(loader.InstalledPrograms);
|
||||||
|
|
||||||
|
if (loader.CartridgeSlot.HasItem)
|
||||||
|
available.Add(loader.CartridgeSlot.Item!.Value);
|
||||||
|
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Installs a cartridge by spawning an invisible version of the cartridges prototype into the cartridge loaders program container program container
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loaderUid">The cartridge loader uid</param>
|
||||||
|
/// <param name="cartridgeUid">The uid of the cartridge to be installed</param>
|
||||||
|
/// <param name="loader">The cartridge loader component</param>
|
||||||
|
/// <returns>Whether installing the cartridge was successful</returns>
|
||||||
|
public bool InstallCartridge(EntityUid loaderUid, EntityUid cartridgeUid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader) || loader.InstalledPrograms.Count >= loader.DiskSpace)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//This will eventually be replaced by serializing and deserializing the cartridge to copy it when something needs
|
||||||
|
//the data on the cartridge to carry over when installing
|
||||||
|
var prototypeId = Prototype(cartridgeUid)?.ID;
|
||||||
|
return prototypeId != null && InstallProgram(loaderUid, prototypeId, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Installs a program by its prototype
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loaderUid">The cartridge loader uid</param>
|
||||||
|
/// <param name="prototype">The prototype name</param>
|
||||||
|
/// <param name="loader">The cartridge loader component</param>
|
||||||
|
/// <returns>Whether installing the cartridge was successful</returns>
|
||||||
|
public bool InstallProgram(EntityUid loaderUid, string prototype, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader) || loader.InstalledPrograms.Count >= loader.DiskSpace)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_containerSystem.TryGetContainer(loaderUid, ContainerName, out var container))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Prevent installing cartridges that have already been installed
|
||||||
|
if (IsInstalled(prototype, loader))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var installedProgram = Spawn(prototype, new EntityCoordinates(loaderUid, 0, 0));
|
||||||
|
container?.Insert(installedProgram);
|
||||||
|
|
||||||
|
UpdateCartridgeInstallationStatus(installedProgram, InstallationStatus.Installed);
|
||||||
|
loader.InstalledPrograms.Add(installedProgram);
|
||||||
|
|
||||||
|
RaiseLocalEvent(installedProgram, new CartridgeAddedEvent(loaderUid));
|
||||||
|
UpdateUserInterfaceState(loaderUid, loader);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uninstalls a program using its uid
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loaderUid">The cartridge loader uid</param>
|
||||||
|
/// <param name="programUid">The uid of the program to be uninstalled</param>
|
||||||
|
/// <param name="loader">The cartridge loader component</param>
|
||||||
|
/// <returns>Whether uninstalling the program was successful</returns>
|
||||||
|
public bool UninstallProgram(EntityUid loaderUid, EntityUid programUid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader) || !ContainsCartridge(programUid, loader, true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (loader.ActiveProgram == programUid)
|
||||||
|
loader.ActiveProgram = null;
|
||||||
|
|
||||||
|
loader.BackgroundPrograms.Remove(programUid);
|
||||||
|
loader.InstalledPrograms.Remove(programUid);
|
||||||
|
EntityManager.QueueDeleteEntity(programUid);
|
||||||
|
UpdateUserInterfaceState(loaderUid, loader);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates a program or cartridge and displays its ui fragment. Deactivates any previously active program.
|
||||||
|
/// </summary>
|
||||||
|
public void ActivateProgram(EntityUid loaderUid, EntityUid programUid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ContainsCartridge(programUid, loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (loader.ActiveProgram.HasValue)
|
||||||
|
DeactivateProgram(loaderUid, programUid, loader);
|
||||||
|
|
||||||
|
if (!loader.BackgroundPrograms.Contains(programUid))
|
||||||
|
RaiseLocalEvent(programUid, new CartridgeActivatedEvent(loaderUid));
|
||||||
|
|
||||||
|
loader.ActiveProgram = programUid;
|
||||||
|
UpdateUserInterfaceState(loaderUid, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deactivates the currently active program or cartridge.
|
||||||
|
/// </summary>
|
||||||
|
public void DeactivateProgram(EntityUid loaderUid, EntityUid programUid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ContainsCartridge(programUid, loader) || loader.ActiveProgram != programUid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!loader.BackgroundPrograms.Contains(programUid))
|
||||||
|
RaiseLocalEvent(programUid, new CartridgeDeactivatedEvent(programUid));
|
||||||
|
|
||||||
|
loader.ActiveProgram = default;
|
||||||
|
UpdateUserInterfaceState(loaderUid, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the given program as a running in the background. Programs running in the background will receive certain events like device net packets but not ui messages
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Programs wanting to use this functionality will have to provide a way to register and unregister themselves as background programs through their ui fragment.
|
||||||
|
/// </remarks>
|
||||||
|
public void RegisterBackgroundProgram(EntityUid loaderUid, EntityUid cartridgeUid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ContainsCartridge(cartridgeUid, loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (loader.ActiveProgram != cartridgeUid)
|
||||||
|
RaiseLocalEvent(cartridgeUid, new CartridgeActivatedEvent(loaderUid));
|
||||||
|
|
||||||
|
loader.BackgroundPrograms.Add(cartridgeUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters the given program as running in the background
|
||||||
|
/// </summary>
|
||||||
|
public void UnregisterBackgroundProgram(EntityUid loaderUid, EntityUid cartridgeUid, CartridgeLoaderComponent? loader = default!)
|
||||||
|
{
|
||||||
|
if (!Resolve(loaderUid, ref loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ContainsCartridge(cartridgeUid, loader))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (loader.ActiveProgram != cartridgeUid)
|
||||||
|
RaiseLocalEvent(cartridgeUid, new CartridgeDeactivatedEvent(loaderUid));
|
||||||
|
|
||||||
|
loader.BackgroundPrograms.Remove(cartridgeUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnItemInserted(EntityUid uid, SharedCartridgeLoaderComponent loader, EntInsertedIntoContainerMessage args)
|
||||||
|
{
|
||||||
|
RaiseLocalEvent(args.Entity, new CartridgeAddedEvent(uid));
|
||||||
|
base.OnItemInserted(uid, loader, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnItemRemoved(EntityUid uid, SharedCartridgeLoaderComponent loader, EntRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
var deactivate = loader.BackgroundPrograms.Remove(args.Entity);
|
||||||
|
|
||||||
|
if (loader.ActiveProgram == args.Entity)
|
||||||
|
{
|
||||||
|
loader.ActiveProgram = default;
|
||||||
|
deactivate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deactivate)
|
||||||
|
RaiseLocalEvent(args.Entity, new CartridgeDeactivatedEvent(uid));
|
||||||
|
|
||||||
|
RaiseLocalEvent(args.Entity, new CartridgeRemovedEvent(uid));
|
||||||
|
base.OnItemRemoved(uid, loader, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Installs programs from the list of preinstalled programs
|
||||||
|
/// </summary>
|
||||||
|
private void OnMapInit(EntityUid uid, CartridgeLoaderComponent component, MapInitEvent args)
|
||||||
|
{
|
||||||
|
foreach (var prototype in component.PreinstalledPrograms)
|
||||||
|
{
|
||||||
|
InstallProgram(uid, prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUsed(EntityUid uid, CartridgeLoaderComponent component, AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
RelayEvent(component, new CartridgeAfterInteractEvent(uid, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPacketReceived(EntityUid uid, CartridgeLoaderComponent component, DeviceNetworkPacketEvent args)
|
||||||
|
{
|
||||||
|
RelayEvent(component, new CartridgeDeviceNetPacketEvent(uid, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaderUiMessage(EntityUid loaderUid, CartridgeLoaderComponent component, CartridgeLoaderUiMessage message)
|
||||||
|
{
|
||||||
|
switch (message.Action)
|
||||||
|
{
|
||||||
|
case CartridgeUiMessageAction.Activate:
|
||||||
|
ActivateProgram(loaderUid, message.CartridgeUid, component);
|
||||||
|
break;
|
||||||
|
case CartridgeUiMessageAction.Deactivate:
|
||||||
|
DeactivateProgram(loaderUid, message.CartridgeUid, component);
|
||||||
|
break;
|
||||||
|
case CartridgeUiMessageAction.Install:
|
||||||
|
InstallCartridge(loaderUid, message.CartridgeUid, component);
|
||||||
|
break;
|
||||||
|
case CartridgeUiMessageAction.Uninstall:
|
||||||
|
UninstallProgram(loaderUid, message.CartridgeUid, component);
|
||||||
|
break;
|
||||||
|
case CartridgeUiMessageAction.UIReady:
|
||||||
|
if (component.ActiveProgram.HasValue)
|
||||||
|
RaiseLocalEvent(component.ActiveProgram.Value, new CartridgeUiReadyEvent(loaderUid));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relays ui messages meant for cartridges to the currently active cartridge
|
||||||
|
/// </summary>
|
||||||
|
private void OnUiMessage(EntityUid uid, CartridgeLoaderComponent component, CartridgeUiMessage args)
|
||||||
|
{
|
||||||
|
var cartridgeEvent = args.MessageEvent;
|
||||||
|
cartridgeEvent.LoaderUid = uid;
|
||||||
|
|
||||||
|
RelayEvent(component, cartridgeEvent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relays events to the currently active program and and programs running in the background.
|
||||||
|
/// Skips background programs if "skipBackgroundPrograms" is set to true
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loader">The cartritge loader component</param>
|
||||||
|
/// <param name="args">The event to be relayed</param>
|
||||||
|
/// <param name="skipBackgroundPrograms">Whether to skip relaying the event to programs running in the background</param>
|
||||||
|
private void RelayEvent<TEvent>(CartridgeLoaderComponent loader, TEvent args, bool skipBackgroundPrograms = false) where TEvent : notnull
|
||||||
|
{
|
||||||
|
if (loader.ActiveProgram.HasValue)
|
||||||
|
RaiseLocalEvent(loader.ActiveProgram.Value, args);
|
||||||
|
|
||||||
|
if (skipBackgroundPrograms)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var program in loader.BackgroundPrograms)
|
||||||
|
{
|
||||||
|
//Prevent programs registered as running in the background receiving events twice if they are active
|
||||||
|
if (loader.ActiveProgram.HasValue && loader.ActiveProgram.Value.Equals(program))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
RaiseLocalEvent(program, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a program is already installed by searching for its prototype name in the list of installed programs
|
||||||
|
/// </summary>
|
||||||
|
private bool IsInstalled(string? prototype, CartridgeLoaderComponent loader)
|
||||||
|
{
|
||||||
|
foreach (var program in loader.InstalledPrograms)
|
||||||
|
{
|
||||||
|
if (Prototype(program)?.ID == prototype)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shortcut for updating the loaders user interface state without passing in a subtype of <see cref="CartridgeLoaderUiState"/>
|
||||||
|
/// like the <see cref="PDA.PDASystem"/> does when updating its ui state
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="PDA.PDASystem.UpdatePDAUserInterface"/>
|
||||||
|
private void UpdateUserInterfaceState(EntityUid loaderUid, CartridgeLoaderComponent loader)
|
||||||
|
{
|
||||||
|
UpdateUiState(loaderUid, new CartridgeLoaderUiState(), null, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCartridgeInstallationStatus(EntityUid cartridgeUid, InstallationStatus installationStatus, CartridgeComponent? cartridgeComponent = default!)
|
||||||
|
{
|
||||||
|
if (Resolve(cartridgeUid, ref cartridgeComponent))
|
||||||
|
{
|
||||||
|
cartridgeComponent.InstallationStatus = installationStatus;
|
||||||
|
Dirty(cartridgeComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ContainsCartridge(EntityUid cartridgeUid, CartridgeLoaderComponent loader , bool onlyInstalled = false)
|
||||||
|
{
|
||||||
|
return !onlyInstalled && loader.CartridgeSlot.Item?.Equals(cartridgeUid) == true || loader.InstalledPrograms.Contains(cartridgeUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to running programs when the cartridge loader receives a device net package
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="DeviceNetworkPacketEvent"/>
|
||||||
|
public sealed class CartridgeDeviceNetPacketEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
public readonly DeviceNetworkPacketEvent PacketEvent;
|
||||||
|
|
||||||
|
public CartridgeDeviceNetPacketEvent(EntityUid loader, DeviceNetworkPacketEvent packetEvent)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
PacketEvent = packetEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to running programs when the cartridge loader receives an after interact event
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="AfterInteractEvent"/>
|
||||||
|
public sealed class CartridgeAfterInteractEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
public readonly AfterInteractEvent InteractEvent;
|
||||||
|
|
||||||
|
public CartridgeAfterInteractEvent(EntityUid loader, AfterInteractEvent interactEvent)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
InteractEvent = interactEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class NotekeeperCartridgeComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of notes that got written down
|
||||||
|
/// </summary>
|
||||||
|
[DataField("notes")]
|
||||||
|
public List<string> Notes = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
public sealed class NotekeeperCartridgeSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<NotekeeperCartridgeComponent, CartridgeMessageEvent>(OnUiMessage);
|
||||||
|
SubscribeLocalEvent<NotekeeperCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This gets called when the ui fragment needs to be updated for the first time after activating
|
||||||
|
/// </summary>
|
||||||
|
private void OnUiReady(EntityUid uid, NotekeeperCartridgeComponent component, CartridgeUiReadyEvent args)
|
||||||
|
{
|
||||||
|
UpdateUiState(uid, args.Loader, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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(EntityUid uid, NotekeeperCartridgeComponent component, CartridgeMessageEvent args)
|
||||||
|
{
|
||||||
|
if (args is not NotekeeperUiMessageEvent message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (message.Action == NotekeeperUiAction.Add)
|
||||||
|
{
|
||||||
|
component.Notes.Add(message.Note);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
component.Notes.Remove(message.Note);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUiState(uid, args.LoaderUid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void UpdateUiState(EntityUid uid, EntityUid loaderUid, NotekeeperCartridgeComponent? component)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var state = new NotekeeperUiState(component.Notes);
|
||||||
|
_cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ namespace Content.Server.Entry
|
|||||||
"ClientEntitySpawner",
|
"ClientEntitySpawner",
|
||||||
"HandheldGPS",
|
"HandheldGPS",
|
||||||
"CableVisualizer",
|
"CableVisualizer",
|
||||||
|
"CartridgeUi",
|
||||||
|
"PDABorderColor",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Content.Server.CartridgeLoader;
|
||||||
|
using Content.Server.DeviceNetwork.Components;
|
||||||
using Content.Server.Instruments;
|
using Content.Server.Instruments;
|
||||||
using Content.Server.Light.Components;
|
using Content.Server.Light.Components;
|
||||||
using Content.Server.Light.EntitySystems;
|
using Content.Server.Light.EntitySystems;
|
||||||
@@ -23,6 +25,7 @@ namespace Content.Server.PDA
|
|||||||
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
|
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
|
||||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||||
|
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!;
|
||||||
[Dependency] private readonly StoreSystem _storeSystem = default!;
|
[Dependency] private readonly StoreSystem _storeSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -101,10 +104,11 @@ namespace Content.Server.PDA
|
|||||||
if (!_uiSystem.TryGetUi(pda.Owner, PDAUiKey.Key, out var ui))
|
if (!_uiSystem.TryGetUi(pda.Owner, PDAUiKey.Key, out var ui))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var address = GetDeviceNetAddress(pda.Owner);
|
||||||
var hasInstrument = HasComp<InstrumentComponent>(pda.Owner);
|
var hasInstrument = HasComp<InstrumentComponent>(pda.Owner);
|
||||||
var state = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, false, hasInstrument);
|
var state = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, false, hasInstrument, address);
|
||||||
|
|
||||||
ui.SetState(state);
|
_cartridgeLoaderSystem?.UpdateUiState(pda.Owner, state);
|
||||||
|
|
||||||
// TODO UPLINK RINGTONES/SECRETS This is just a janky placeholder way of hiding uplinks from non syndicate
|
// TODO UPLINK RINGTONES/SECRETS This is just a janky placeholder way of hiding uplinks from non syndicate
|
||||||
// players. This should really use a sort of key-code entry system that selects an account which is not directly tied to
|
// players. This should really use a sort of key-code entry system that selects an account which is not directly tied to
|
||||||
@@ -113,7 +117,7 @@ namespace Content.Server.PDA
|
|||||||
if (!TryComp<StoreComponent>(pda.Owner, out var storeComponent))
|
if (!TryComp<StoreComponent>(pda.Owner, out var storeComponent))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var uplinkState = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, true, hasInstrument);
|
var uplinkState = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, true, hasInstrument, address);
|
||||||
|
|
||||||
foreach (var session in ui.SubscribedSessions)
|
foreach (var session in ui.SubscribedSessions)
|
||||||
{
|
{
|
||||||
@@ -122,7 +126,7 @@ namespace Content.Server.PDA
|
|||||||
|
|
||||||
if (storeComponent.AccountOwner == user || (TryComp<MindComponent>(session.AttachedEntity, out var mindcomp) && mindcomp.Mind != null &&
|
if (storeComponent.AccountOwner == user || (TryComp<MindComponent>(session.AttachedEntity, out var mindcomp) && mindcomp.Mind != null &&
|
||||||
mindcomp.Mind.HasRole<TraitorRole>()))
|
mindcomp.Mind.HasRole<TraitorRole>()))
|
||||||
ui.SetState(uplinkState, session);
|
_cartridgeLoaderSystem?.UpdateUiState(pda.Owner, uplinkState, session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,9 +194,21 @@ namespace Content.Server.PDA
|
|||||||
JobTitle = pda.ContainedID?.JobTitle
|
JobTitle = pda.ContainedID?.JobTitle
|
||||||
};
|
};
|
||||||
|
|
||||||
var state = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, true, HasComp<InstrumentComponent>(pda.Owner));
|
var state = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, true, HasComp<InstrumentComponent>(pda.Owner), GetDeviceNetAddress(pda.Owner));
|
||||||
|
|
||||||
ui.SetState(state, args.Session);
|
_cartridgeLoaderSystem?.UpdateUiState(uid, state, args.Session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetDeviceNetAddress(EntityUid uid)
|
||||||
|
{
|
||||||
|
string? address = null;
|
||||||
|
|
||||||
|
if (TryComp(uid, out DeviceNetworkComponent? deviceNetworkComponent))
|
||||||
|
{
|
||||||
|
address = deviceNetworkComponent?.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
Content.Shared/CartridgeLoader/CartridgeComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for defining values used for displaying in the program ui in yaml
|
||||||
|
/// </summary>
|
||||||
|
[NetworkedComponent]
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CartridgeComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("programName", required: true)]
|
||||||
|
public string ProgramName = "default-program-name";
|
||||||
|
|
||||||
|
[DataField("icon")]
|
||||||
|
public SpriteSpecifier? Icon;
|
||||||
|
|
||||||
|
public InstallationStatus InstallationStatus = InstallationStatus.Cartridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class CartridgeComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public InstallationStatus InstallationStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum InstallationStatus
|
||||||
|
{
|
||||||
|
Cartridge,
|
||||||
|
Installed,
|
||||||
|
Readonly
|
||||||
|
}
|
||||||
26
Content.Shared/CartridgeLoader/CartridgeLoaderUiMessage.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class CartridgeLoaderUiMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public readonly EntityUid CartridgeUid;
|
||||||
|
public readonly CartridgeUiMessageAction Action;
|
||||||
|
|
||||||
|
public CartridgeLoaderUiMessage(EntityUid cartridgeUid, CartridgeUiMessageAction action)
|
||||||
|
{
|
||||||
|
CartridgeUid = cartridgeUid;
|
||||||
|
Action = action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum CartridgeUiMessageAction
|
||||||
|
{
|
||||||
|
Activate,
|
||||||
|
Deactivate,
|
||||||
|
Install,
|
||||||
|
Uninstall,
|
||||||
|
UIReady
|
||||||
|
}
|
||||||
12
Content.Shared/CartridgeLoader/CartridgeLoaderUiState.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class CartridgeLoaderUiState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public EntityUid? ActiveUI;
|
||||||
|
public List<EntityUid> Programs = new();
|
||||||
|
}
|
||||||
9
Content.Shared/CartridgeLoader/CartridgeLoaderVisuals.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum CartridgeLoaderVisuals
|
||||||
|
{
|
||||||
|
CartridgeInserted
|
||||||
|
}
|
||||||
20
Content.Shared/CartridgeLoader/CartridgeUiMessage.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class CartridgeUiMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public CartridgeMessageEvent MessageEvent;
|
||||||
|
|
||||||
|
public CartridgeUiMessage(CartridgeMessageEvent messageEvent)
|
||||||
|
{
|
||||||
|
MessageEvent = messageEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public abstract class CartridgeMessageEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public EntityUid LoaderUid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class NotekeeperUiMessageEvent : CartridgeMessageEvent
|
||||||
|
{
|
||||||
|
public readonly NotekeeperUiAction Action;
|
||||||
|
public readonly string Note;
|
||||||
|
|
||||||
|
public NotekeeperUiMessageEvent(NotekeeperUiAction action, string note)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
Note = note;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum NotekeeperUiAction
|
||||||
|
{
|
||||||
|
Add,
|
||||||
|
Remove
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class NotekeeperUiState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public List<String> Notes;
|
||||||
|
|
||||||
|
public NotekeeperUiState(List<string> notes)
|
||||||
|
{
|
||||||
|
Notes = notes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
[Access(typeof(SharedCartridgeLoaderSystem))]
|
||||||
|
public abstract class SharedCartridgeLoaderComponent : Component
|
||||||
|
{
|
||||||
|
public const string CartridgeSlotId = "Cartridge-Slot";
|
||||||
|
|
||||||
|
[DataField("cartridgeSlot")]
|
||||||
|
public ItemSlot CartridgeSlot = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of programs that come preinstalled with this cartridge loader
|
||||||
|
/// </summary>
|
||||||
|
[DataField("preinstalled")]
|
||||||
|
public List<string> PreinstalledPrograms = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently running program that has its ui showing
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public EntityUid? ActiveProgram = default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of programs running in the background, listening to certain events
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public readonly List<EntityUid> BackgroundPrograms = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of program entities that are spawned into the cartridge loaders program container
|
||||||
|
/// </summary>
|
||||||
|
[DataField("installedCartridges")]
|
||||||
|
public List<EntityUid> InstalledPrograms = new();
|
||||||
|
}
|
||||||
147
Content.Shared/CartridgeLoader/SharedCartridgeLoaderSystem.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader;
|
||||||
|
|
||||||
|
public abstract class SharedCartridgeLoaderSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SharedCartridgeLoaderComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<SharedCartridgeLoaderComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SharedCartridgeLoaderComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
|
||||||
|
SubscribeLocalEvent<SharedCartridgeLoaderComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CartridgeComponent, ComponentGetState>(OnGetState);
|
||||||
|
SubscribeLocalEvent<CartridgeComponent, ComponentHandleState>(OnHandleState);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnComponentInit(EntityUid uid, SharedCartridgeLoaderComponent loader, ComponentInit args)
|
||||||
|
{
|
||||||
|
_itemSlotsSystem.AddItemSlot(uid, SharedCartridgeLoaderComponent.CartridgeSlotId, loader.CartridgeSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks installed program entities for deletion when the component gets removed
|
||||||
|
/// </summary>
|
||||||
|
private void OnComponentRemove(EntityUid uid, SharedCartridgeLoaderComponent loader, ComponentRemove args)
|
||||||
|
{
|
||||||
|
_itemSlotsSystem.RemoveItemSlot(uid, loader.CartridgeSlot);
|
||||||
|
|
||||||
|
foreach (var program in loader.InstalledPrograms)
|
||||||
|
{
|
||||||
|
EntityManager.QueueDeleteEntity(program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnItemInserted(EntityUid uid, SharedCartridgeLoaderComponent loader, EntInsertedIntoContainerMessage args)
|
||||||
|
{
|
||||||
|
UpdateAppearanceData(uid, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnItemRemoved(EntityUid uid, SharedCartridgeLoaderComponent loader, EntRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
UpdateAppearanceData(uid, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetState(EntityUid uid, CartridgeComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
var state = new CartridgeComponentState();
|
||||||
|
state.InstallationStatus = component.InstallationStatus;
|
||||||
|
|
||||||
|
args.State = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHandleState(EntityUid uid, CartridgeComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not CartridgeComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.InstallationStatus = state.InstallationStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearanceData(EntityUid uid, SharedCartridgeLoaderComponent loader)
|
||||||
|
{
|
||||||
|
_appearanceSystem.SetData(uid, CartridgeLoaderVisuals.CartridgeInserted, loader.CartridgeSlot.HasItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to program / cartridge entities when they get inserted or installed
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CartridgeAddedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
|
||||||
|
public CartridgeAddedEvent(EntityUid loader)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to cartridge entities when they get ejected
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CartridgeRemovedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
|
||||||
|
public CartridgeRemovedEvent(EntityUid loader)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to program / cartridge entities when they get activated
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Don't update the programs ui state in this events listener
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class CartridgeActivatedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
|
||||||
|
public CartridgeActivatedEvent(EntityUid loader)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to program / cartridge entities when they get deactivated
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CartridgeDeactivatedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
|
||||||
|
public CartridgeDeactivatedEvent(EntityUid loader)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sent to program / cartridge entities when the ui is ready to be updated by the cartridge.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is used for the initial ui state update because updating the ui in the activate event doesn't work
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class CartridgeUiReadyEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Loader;
|
||||||
|
|
||||||
|
public CartridgeUiReadyEvent(EntityUid loader)
|
||||||
|
{
|
||||||
|
Loader = loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Shared.PDA
|
namespace Content.Shared.PDA
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class PDAUpdateState : BoundUserInterfaceState
|
public sealed class PDAUpdateState : CartridgeLoaderUiState
|
||||||
{
|
{
|
||||||
public bool FlashlightEnabled;
|
public bool FlashlightEnabled;
|
||||||
public bool HasPen;
|
public bool HasPen;
|
||||||
@@ -12,15 +13,17 @@ namespace Content.Shared.PDA
|
|||||||
public string? StationName;
|
public string? StationName;
|
||||||
public bool HasUplink;
|
public bool HasUplink;
|
||||||
public bool CanPlayMusic;
|
public bool CanPlayMusic;
|
||||||
|
public string? Address;
|
||||||
|
|
||||||
public PDAUpdateState(bool flashlightEnabled, bool hasPen, PDAIdInfoText pDAOwnerInfo, string? stationName, bool hasUplink = false, bool canPlayMusic = false)
|
public PDAUpdateState(bool flashlightEnabled, bool hasPen, PDAIdInfoText pdaOwnerInfo, string? stationName, bool hasUplink = false, bool canPlayMusic = false, string? address = null)
|
||||||
{
|
{
|
||||||
FlashlightEnabled = flashlightEnabled;
|
FlashlightEnabled = flashlightEnabled;
|
||||||
HasPen = hasPen;
|
HasPen = hasPen;
|
||||||
PDAOwnerInfo = pDAOwnerInfo;
|
PDAOwnerInfo = pdaOwnerInfo;
|
||||||
HasUplink = hasUplink;
|
HasUplink = hasUplink;
|
||||||
CanPlayMusic = canPlayMusic;
|
CanPlayMusic = canPlayMusic;
|
||||||
StationName = stationName;
|
StationName = stationName;
|
||||||
|
Address = address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
cartridge-bound-user-interface-install-button = Install
|
||||||
|
cartridge-bound-user-interface-uninstall-button = Remove
|
||||||
6
Resources/Locale/en-US/cartridge-loader/cartridges.ftl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
default-program-name = Program
|
||||||
|
|
||||||
|
ent-notekeeper-cartridge = notekeeper cartridge
|
||||||
|
.desc = A program for keeping notes
|
||||||
|
|
||||||
|
notekeeper-program-name = Notekeeper
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
crew-manifest-window-title = Crew Manifest
|
crew-manifest-window-title = Crew Manifest
|
||||||
crew-manifest-button-label = Crew Manifest
|
crew-manifest-button-label = Crew Manifest
|
||||||
|
crew-manifest-button-description = Show a list of your fellow crewmembers
|
||||||
crew-manifest-no-valid-station = Invalid station, or empty manifest!
|
crew-manifest-no-valid-station = Invalid station, or empty manifest!
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ device-frequency-prototype-name-atmos = Atmospheric Devices
|
|||||||
device-frequency-prototype-name-suit-sensors = Suit Sensors
|
device-frequency-prototype-name-suit-sensors = Suit Sensors
|
||||||
device-frequency-prototype-name-lights = Smart Lights
|
device-frequency-prototype-name-lights = Smart Lights
|
||||||
device-frequency-prototype-name-mailing-units = Mailing Units
|
device-frequency-prototype-name-mailing-units = Mailing Units
|
||||||
|
device-frequency-prototype-name-pdas = PDAs
|
||||||
|
|
||||||
## camera frequencies
|
## camera frequencies
|
||||||
device-frequency-prototype-name-surveillance-camera-test = Subnet Test
|
device-frequency-prototype-name-surveillance-camera-test = Subnet Test
|
||||||
@@ -20,6 +21,9 @@ device-frequency-prototype-name-surveillance-camera-entertainment = Entertainmen
|
|||||||
device-address-prefix-vent = Vnt-
|
device-address-prefix-vent = Vnt-
|
||||||
device-address-prefix-scrubber = Scr-
|
device-address-prefix-scrubber = Scr-
|
||||||
device-address-prefix-sensor = Sns-
|
device-address-prefix-sensor = Sns-
|
||||||
|
|
||||||
|
#PDAs and terminals
|
||||||
|
device-address-prefix-console = Cls-
|
||||||
device-address-prefix-fire-alarm = Fir-
|
device-address-prefix-fire-alarm = Fir-
|
||||||
device-address-prefix-air-alarm = Air-
|
device-address-prefix-air-alarm = Air-
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,18 @@ comp-pda-ui-blank = ID:
|
|||||||
|
|
||||||
comp-pda-ui-owner = Owner: [color=white]{$ActualOwnerName}[/color]
|
comp-pda-ui-owner = Owner: [color=white]{$ActualOwnerName}[/color]
|
||||||
|
|
||||||
pda-bound-user-interface-main-menu-tab-title = Main Menu
|
comp-pda-io-program-list-button = Programs
|
||||||
|
|
||||||
|
comp-pda-io-settings-button = Settings
|
||||||
|
|
||||||
|
comp-pda-io-program-fallback-title = Program
|
||||||
|
|
||||||
|
comp-pda-io-no-programs-available = No Programs Available
|
||||||
|
|
||||||
pda-bound-user-interface-uplink-tab-title = Uplink
|
pda-bound-user-interface-uplink-tab-title = Uplink
|
||||||
|
|
||||||
|
pda-bound-user-interface-uplink-tab-description = Access your uplink
|
||||||
|
|
||||||
comp-pda-ui-menu-title = PDA
|
comp-pda-ui-menu-title = PDA
|
||||||
|
|
||||||
comp-pda-ui-station = Station: [color=white]{$Station}[/color]
|
comp-pda-ui-station = Station: [color=white]{$Station}[/color]
|
||||||
@@ -22,10 +30,14 @@ comp-pda-ui-eject-pen-button = Eject Pen
|
|||||||
|
|
||||||
comp-pda-ui-ringtone-button = Ringtone
|
comp-pda-ui-ringtone-button = Ringtone
|
||||||
|
|
||||||
|
comp-pda-ui-ringtone-button-description = Change your PDA's ringtone
|
||||||
|
|
||||||
comp-pda-ui-toggle-flashlight-button = Toggle Flashlight
|
comp-pda-ui-toggle-flashlight-button = Toggle Flashlight
|
||||||
|
|
||||||
pda-bound-user-interface-music-button = Music Instrument
|
pda-bound-user-interface-music-button = Music Instrument
|
||||||
|
|
||||||
|
pda-bound-user-interface-music-button-description = Play music on your PDA
|
||||||
|
|
||||||
comp-pda-ui-unknown = Unknown
|
comp-pda-ui-unknown = Unknown
|
||||||
|
|
||||||
comp-pda-ui-unassigned = Unassigned
|
comp-pda-ui-unassigned = Unassigned
|
||||||
|
|||||||
@@ -69,3 +69,8 @@
|
|||||||
id: MailingUnit
|
id: MailingUnit
|
||||||
name: device-frequency-prototype-name-mailing-units
|
name: device-frequency-prototype-name-mailing-units
|
||||||
frequency: 2300
|
frequency: 2300
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: PDA
|
||||||
|
name: device-frequency-prototype-name-pdas
|
||||||
|
frequency: 2202
|
||||||
|
|||||||
21
Resources/Prototypes/Entities/Objects/Devices/cartridges.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: NotekeeperCartridge
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Devices/cartridge.rsi
|
||||||
|
state: cart-y
|
||||||
|
netsync: false
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/Devices/cartridge.rsi
|
||||||
|
state: cart-y
|
||||||
|
- type: CartridgeUi
|
||||||
|
ui: !type:NotekeeperUi
|
||||||
|
- type: Cartridge
|
||||||
|
programName: notekeeper-program-name
|
||||||
|
icon:
|
||||||
|
sprite: Objects/Misc/books.rsi
|
||||||
|
state: book6
|
||||||
|
- type: NotekeeperCartridge
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +21,8 @@
|
|||||||
containers:
|
containers:
|
||||||
PDA-id: !type:ContainerSlot {}
|
PDA-id: !type:ContainerSlot {}
|
||||||
PDA-pen: !type:ContainerSlot {}
|
PDA-pen: !type:ContainerSlot {}
|
||||||
|
Cartridge-Slot: !type:ContainerSlot {}
|
||||||
|
program-container: !type:Container
|
||||||
- type: ItemSlots
|
- type: ItemSlots
|
||||||
- type: Clothing
|
- type: Clothing
|
||||||
quickEquip: false
|
quickEquip: false
|
||||||
@@ -41,6 +43,24 @@
|
|||||||
mask: /Textures/Effects/LightMasks/cone.png
|
mask: /Textures/Effects/LightMasks/cone.png
|
||||||
autoRot: true
|
autoRot: true
|
||||||
- type: Ringer
|
- type: Ringer
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: PDA
|
||||||
|
prefix: device-address-prefix-console
|
||||||
|
- type: WirelessNetworkConnection
|
||||||
|
range: 500
|
||||||
|
- type: CartridgeLoader
|
||||||
|
uiKey: enum.PDAUiKey.Key
|
||||||
|
preinstalled:
|
||||||
|
- NotekeeperCartridge
|
||||||
|
cartridgeSlot:
|
||||||
|
priority: -1
|
||||||
|
name: Cartridge
|
||||||
|
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||||
|
insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- Cartridge
|
||||||
- type: ActivatableUI
|
- type: ActivatableUI
|
||||||
key: enum.PDAUiKey.Key
|
key: enum.PDAUiKey.Key
|
||||||
singleUser: true
|
singleUser: true
|
||||||
@@ -87,6 +107,8 @@
|
|||||||
components:
|
components:
|
||||||
- type: PDA
|
- type: PDA
|
||||||
id: PassengerIDCard
|
id: PassengerIDCard
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#717059"
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BasePDA
|
parent: BasePDA
|
||||||
@@ -162,6 +184,8 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-cook
|
state: pda-cook
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#d7d7d0"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-cook
|
state: pda-cook
|
||||||
|
|
||||||
@@ -200,6 +224,8 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-clown
|
state: pda-clown
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#C18199"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-clown
|
state: pda-clown
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
@@ -258,6 +284,8 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-chaplain
|
state: pda-chaplain
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#333333"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-chaplain
|
state: pda-chaplain
|
||||||
|
|
||||||
@@ -363,6 +391,8 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-janitor
|
state: pda-janitor
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#5D2D56"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-janitor
|
state: pda-janitor
|
||||||
|
|
||||||
@@ -384,6 +414,8 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-captain
|
state: pda-captain
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#7C5D00"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-captain
|
state: pda-captain
|
||||||
|
|
||||||
@@ -416,6 +448,9 @@
|
|||||||
components:
|
components:
|
||||||
- type: PDA
|
- type: PDA
|
||||||
id: CEIDCard
|
id: CEIDCard
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#949137"
|
||||||
|
accentHColor: "#447987"
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
@@ -450,6 +485,10 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-cmo
|
state: pda-cmo
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#d7d7d0"
|
||||||
|
accentHColor: "#447987"
|
||||||
|
accentVColor: "#447987"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-cmo
|
state: pda-cmo
|
||||||
- type: HealthAnalyzer
|
- type: HealthAnalyzer
|
||||||
@@ -467,6 +506,9 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-medical
|
state: pda-medical
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#d7d7d0"
|
||||||
|
accentVColor: "#447987"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-medical
|
state: pda-medical
|
||||||
- type: HealthAnalyzer
|
- type: HealthAnalyzer
|
||||||
@@ -484,6 +526,9 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-chemistry
|
state: pda-chemistry
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#d7d7d0"
|
||||||
|
accentVColor: "#B34200"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-chemistry
|
state: pda-chemistry
|
||||||
|
|
||||||
@@ -529,6 +574,9 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-hos
|
state: pda-hos
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#A32D26"
|
||||||
|
accentHColor: "#447987"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-hos
|
state: pda-hos
|
||||||
|
|
||||||
@@ -642,6 +690,9 @@
|
|||||||
components:
|
components:
|
||||||
- type: PDA
|
- type: PDA
|
||||||
id: ERTLeaderIDCard
|
id: ERTLeaderIDCard
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#A32D26"
|
||||||
|
accentHColor: "#447987"
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
@@ -657,6 +708,9 @@
|
|||||||
components:
|
components:
|
||||||
- type: PDA
|
- type: PDA
|
||||||
id: CBURNIDcard
|
id: CBURNIDcard
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#A32D26"
|
||||||
|
accentHColor: "#447987"
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BasePDA
|
parent: BasePDA
|
||||||
@@ -670,6 +724,9 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-medical
|
state: pda-medical
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#d7d7d0"
|
||||||
|
accentVColor: "#447987"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-medical
|
state: pda-medical
|
||||||
|
|
||||||
@@ -715,6 +772,9 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-boxer
|
state: pda-boxer
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#333333"
|
||||||
|
borderVColor: "#390504"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-boxer
|
state: pda-boxer
|
||||||
|
|
||||||
@@ -730,5 +790,7 @@
|
|||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
state: pda-detective
|
state: pda-detective
|
||||||
|
- type: PDABorderColor
|
||||||
|
borderColor: "#774705"
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: pda-detective
|
state: pda-detective
|
||||||
|
|||||||
144
Resources/Textures/Interface/Nano/geometric_panel_border.svg
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 6.3499998 6.3499999"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1055"
|
||||||
|
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||||
|
sodipodi:docname="geometric_panel_border.svg"
|
||||||
|
inkscape:export-filename="geometric_panel_border.svg.96dpi.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<defs
|
||||||
|
id="defs1049" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="32"
|
||||||
|
inkscape:cx="6.140625"
|
||||||
|
inkscape:cy="12.15625"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1057"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
units="px"
|
||||||
|
inkscape:snap-page="true"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showguides="true">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4206"
|
||||||
|
originx="0"
|
||||||
|
originy="0"
|
||||||
|
color="#3f3fff"
|
||||||
|
opacity="0.57254902"
|
||||||
|
empcolor="#3f3fff"
|
||||||
|
empopacity="0.75686275"
|
||||||
|
enabled="true"
|
||||||
|
dotted="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="0.26458333,2.6458333"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide14206"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="0,2.6458333"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide14208"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="axonomgrid"
|
||||||
|
id="grid16025"
|
||||||
|
spacingy="0.37438541"
|
||||||
|
gridanglex="45"
|
||||||
|
gridanglez="45"
|
||||||
|
units="px"
|
||||||
|
enabled="true"
|
||||||
|
color="#ff2439"
|
||||||
|
opacity="0.4745098"
|
||||||
|
empcolor="#870011"
|
||||||
|
empopacity="1"
|
||||||
|
originx="-0.039687499"
|
||||||
|
originy="0"
|
||||||
|
snapvisiblegridlinesonly="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="0.79374998,2.9752396"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide454"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="2.9752396,0.79374998"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide456"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="3.3747603,5.5562499"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide458"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="5.5562499,3.3747603"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide460"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="axonomgrid"
|
||||||
|
id="grid1070"
|
||||||
|
gridanglex="45"
|
||||||
|
gridanglez="45"
|
||||||
|
spacingy="0.37438541"
|
||||||
|
color="#288e00"
|
||||||
|
opacity="0.96078431"
|
||||||
|
empcolor="#3fa600"
|
||||||
|
empopacity="0.97647059"
|
||||||
|
originy="-0.039687499"
|
||||||
|
enabled="true"
|
||||||
|
originx="0"
|
||||||
|
snapvisiblegridlinesonly="false"
|
||||||
|
empspacing="9"
|
||||||
|
units="px" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata1052">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-79.848503,-133.93878)">
|
||||||
|
<path
|
||||||
|
id="rect1684"
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:0.25;fill-rule:nonzero;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.247312;paint-order:normal"
|
||||||
|
d="m 83.55267,133.93878 2.645833,2.64583 v 3.70417 h -3.704167 l -2.645833,-2.64583 v -3.70417 z m -2.910417,3.37476 2.18149,2.18149 2.58101,0 v -2.58101 l -2.18149,-2.18149 -2.58101,0 z"
|
||||||
|
sodipodi:nodetypes="cccccccccccccc"
|
||||||
|
inkscape:label="rect1684" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 298 B |
BIN
Resources/Textures/Interface/eject.png
Normal file
|
After Width: | Height: | Size: 259 B |
BIN
Resources/Textures/Interface/home.png
Normal file
|
After Width: | Height: | Size: 384 B |
4
Resources/Textures/Interface/home.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" fill="none" viewBox="0 0 21 20">
|
||||||
|
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m3.224 7.5 7.258-5.833L17.739 7.5v9.167c0 .442-.17.866-.473 1.178a1.586 1.586 0 0 1-1.14.488H4.837c-.428 0-.838-.175-1.14-.488a1.695 1.695 0 0 1-.473-1.178V7.5Z"/>
|
||||||
|
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.063 18.333V10H12.9v8.333"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 476 B |
BIN
Resources/Textures/Interface/light.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
Resources/Textures/Interface/pencil.png
Normal file
|
After Width: | Height: | Size: 415 B |
4
Resources/Textures/Interface/pencil.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="18" fill="none" viewBox="0 0 19 18">
|
||||||
|
<path fill="#FFFBFB" d="M15.167 2.455 1 15.545V17h1.417L16.583 3.91l-1.416-1.455Z"/>
|
||||||
|
<path stroke="#fff" d="m18 5.364-5.667 5.818M16.583 1 18 2.455M1 15.545l14.167-13.09 1.416 1.454L2.417 17H1v-1.454Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 311 B |
BIN
Resources/Textures/Objects/Devices/cartridge.rsi/cart-y.png
Normal file
|
After Width: | Height: | Size: 336 B |
@@ -69,6 +69,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cart-tox"
|
"name": "cart-tox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-y"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-a.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-b.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-c.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-ce.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-chem.png
Normal file
|
After Width: | Height: | Size: 264 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-clown.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-cmo.png
Normal file
|
After Width: | Height: | Size: 322 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-e.png
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-eye.png
Normal file
|
After Width: | Height: | Size: 307 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-h.png
Normal file
|
After Width: | Height: | Size: 309 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-hos.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-j.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-lib.png
Normal file
|
After Width: | Height: | Size: 272 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-m.png
Normal file
|
After Width: | Height: | Size: 281 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-mi.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-ord.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-q.png
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-rd.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-s.png
Normal file
|
After Width: | Height: | Size: 299 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart-tear.png
Normal file
|
After Width: | Height: | Size: 359 B |
BIN
Resources/Textures/Objects/Devices/cartridges.rsi/cart.png
Normal file
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 155 B |
77
Resources/Textures/Objects/Devices/cartridges.rsi/meta.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "insert_overlay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-chem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-clown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-j"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-q"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-ord"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-tear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-lib"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-eye"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-mi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-hos"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-ce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-cmo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-rd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||