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.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.PDA;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.PDA
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PDABoundUserInterface : BoundUserInterface
|
||||
public sealed class PDABoundUserInterface : CartridgeLoaderBoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IEntityManager? _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private PDAMenu? _menu;
|
||||
@@ -67,54 +70,52 @@ namespace Content.Client.PDA
|
||||
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)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (_menu == null)
|
||||
{
|
||||
if (state is not PDAUpdateState updateState)
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PDAUpdateState msg:
|
||||
{
|
||||
_menu.FlashLightToggleButton.Pressed = msg.FlashlightEnabled;
|
||||
|
||||
if (msg.PDAOwnerInfo.ActualOwnerName != null)
|
||||
{
|
||||
_menu.PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
|
||||
("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;
|
||||
}
|
||||
}
|
||||
_menu?.UpdateState(updateState);
|
||||
}
|
||||
|
||||
|
||||
protected override void AttachCartridgeUI(Control cartridgeUIFragment, string? title)
|
||||
{
|
||||
_menu?.ProgramView.AddChild(cartridgeUIFragment);
|
||||
_menu?.ToProgramView(title ?? Loc.GetString("comp-pda-io-program-fallback-title"));
|
||||
}
|
||||
|
||||
protected override void DetachCartridgeUI(Control cartridgeUIFragment)
|
||||
{
|
||||
if (_menu is null)
|
||||
return;
|
||||
|
||||
_menu.ToHomeScreen();
|
||||
_menu.HideProgramHeader();
|
||||
_menu.ProgramView.RemoveChild(cartridgeUIFragment);
|
||||
}
|
||||
|
||||
protected override void UpdateAvailablePrograms(List<(EntityUid, CartridgeComponent)> programs)
|
||||
{
|
||||
_menu?.UpdateAvailablePrograms(programs);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
@@ -123,5 +124,10 @@ namespace Content.Client.PDA
|
||||
|
||||
_menu?.Dispose();
|
||||
}
|
||||
|
||||
private PDABorderColorComponent? GetBorderColorComponent()
|
||||
{
|
||||
return _entityManager?.GetComponentOrNull<PDABorderColorComponent>(Owner.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'comp-pda-ui-menu-title'}"
|
||||
MinSize="576 256"
|
||||
SetSize="576 256">
|
||||
<TabContainer Name="MasterTabContainer">
|
||||
<pda:PDAWindow xmlns="https://spacestation14.io"
|
||||
xmlns:pda="clr-namespace:Content.Client.PDA"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="576 450"
|
||||
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"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="50 50">
|
||||
MinSize="50 50"
|
||||
Margin="8">
|
||||
<RichTextLabel Name="PdaOwnerLabel" Access="Public" />
|
||||
<RichTextLabel Name="IdInfoLabel"
|
||||
Access="Public"
|
||||
@@ -16,37 +36,53 @@
|
||||
<RichTextLabel Name="StationNameLabel"
|
||||
Access="Public"
|
||||
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>
|
||||
</PanelContainer>
|
||||
<Button Name="FlashLightToggleButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'comp-pda-ui-toggle-flashlight-button'}"
|
||||
ToggleMode="True" />
|
||||
<Button Name="CrewManifestButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'crew-manifest-button-label'}"
|
||||
Visible="False" />
|
||||
<Button Name="ActivateUplinkButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}" />
|
||||
<Button Name="ActivateMusicButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'pda-bound-user-interface-music-button'}" />
|
||||
</BoxContainer>
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
<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"
|
||||
Text="{Loc 'comp-pda-ui-ringtone-button'}"
|
||||
Description="{Loc 'comp-pda-ui-ringtone-button-description'}"/>
|
||||
<pda:PDASettingsButton Name="CrewManifestButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'crew-manifest-button-label'}"
|
||||
Description="{Loc 'crew-manifest-button-description'}"
|
||||
Visible="False" />
|
||||
<pda:PDASettingsButton Name="ActivateUplinkButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}"
|
||||
Description="{Loc 'pda-bound-user-interface-uplink-tab-description'}"/>
|
||||
<pda:PDASettingsButton Name="ActivateMusicButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'pda-bound-user-interface-music-button'}"
|
||||
Description="{Loc 'pda-bound-user-interface-music-button-description'}"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<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 Robust.Client.UserInterface.CustomControls;
|
||||
using Content.Client.Message;
|
||||
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.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.PDA
|
||||
{
|
||||
[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()
|
||||
{
|
||||
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 BaseAngleRect { get; }
|
||||
protected StyleBoxTexture AngleBorderRect { get; }
|
||||
|
||||
protected StyleBase(IResourceCache resCache)
|
||||
{
|
||||
@@ -114,6 +115,12 @@ namespace Content.Client.Stylesheets
|
||||
};
|
||||
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
|
||||
{
|
||||
BackgroundColor = Color.Gray.WithAlpha(0.35f), ContentMarginLeftOverride = DefaultGrabberSize,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Actions.UI;
|
||||
using Content.Client.ContextMenu.UI;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.PDA;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Targeting;
|
||||
using Content.Client.Targeting.UI;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Verbs.UI;
|
||||
@@ -13,7 +12,6 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.StylesheetHelpers;
|
||||
|
||||
namespace Content.Client.Stylesheets
|
||||
@@ -1395,6 +1393,60 @@ namespace Content.Client.Stylesheets
|
||||
Element<Label>().Class("Disabled")
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
"HandheldGPS",
|
||||
"CableVisualizer",
|
||||
"CartridgeUi",
|
||||
"PDABorderColor",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Instruments;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
@@ -23,6 +25,7 @@ namespace Content.Server.PDA
|
||||
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!;
|
||||
[Dependency] private readonly StoreSystem _storeSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -101,10 +104,11 @@ namespace Content.Server.PDA
|
||||
if (!_uiSystem.TryGetUi(pda.Owner, PDAUiKey.Key, out var ui))
|
||||
return;
|
||||
|
||||
var address = GetDeviceNetAddress(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
|
||||
// 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))
|
||||
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)
|
||||
{
|
||||
@@ -122,7 +126,7 @@ namespace Content.Server.PDA
|
||||
|
||||
if (storeComponent.AccountOwner == user || (TryComp<MindComponent>(session.AttachedEntity, out var mindcomp) && mindcomp.Mind != null &&
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
|
||||
namespace Content.Shared.PDA
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PDAUpdateState : BoundUserInterfaceState
|
||||
public sealed class PDAUpdateState : CartridgeLoaderUiState
|
||||
{
|
||||
public bool FlashlightEnabled;
|
||||
public bool HasPen;
|
||||
@@ -12,15 +13,17 @@ namespace Content.Shared.PDA
|
||||
public string? StationName;
|
||||
public bool HasUplink;
|
||||
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;
|
||||
HasPen = hasPen;
|
||||
PDAOwnerInfo = pDAOwnerInfo;
|
||||
PDAOwnerInfo = pdaOwnerInfo;
|
||||
HasUplink = hasUplink;
|
||||
CanPlayMusic = canPlayMusic;
|
||||
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-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!
|
||||
|
||||
@@ -3,6 +3,7 @@ device-frequency-prototype-name-atmos = Atmospheric Devices
|
||||
device-frequency-prototype-name-suit-sensors = Suit Sensors
|
||||
device-frequency-prototype-name-lights = Smart Lights
|
||||
device-frequency-prototype-name-mailing-units = Mailing Units
|
||||
device-frequency-prototype-name-pdas = PDAs
|
||||
|
||||
## camera frequencies
|
||||
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-scrubber = Scr-
|
||||
device-address-prefix-sensor = Sns-
|
||||
|
||||
#PDAs and terminals
|
||||
device-address-prefix-console = Cls-
|
||||
device-address-prefix-fire-alarm = Fir-
|
||||
device-address-prefix-air-alarm = Air-
|
||||
|
||||
|
||||
@@ -8,10 +8,18 @@ comp-pda-ui-blank = ID:
|
||||
|
||||
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-description = Access your uplink
|
||||
|
||||
comp-pda-ui-menu-title = PDA
|
||||
|
||||
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-description = Change your PDA's ringtone
|
||||
|
||||
comp-pda-ui-toggle-flashlight-button = Toggle Flashlight
|
||||
|
||||
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-unassigned = Unassigned
|
||||
|
||||
@@ -69,3 +69,8 @@
|
||||
id: MailingUnit
|
||||
name: device-frequency-prototype-name-mailing-units
|
||||
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:
|
||||
PDA-id: !type:ContainerSlot {}
|
||||
PDA-pen: !type:ContainerSlot {}
|
||||
Cartridge-Slot: !type:ContainerSlot {}
|
||||
program-container: !type:Container
|
||||
- type: ItemSlots
|
||||
- type: Clothing
|
||||
quickEquip: false
|
||||
@@ -41,6 +43,24 @@
|
||||
mask: /Textures/Effects/LightMasks/cone.png
|
||||
autoRot: true
|
||||
- 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
|
||||
key: enum.PDAUiKey.Key
|
||||
singleUser: true
|
||||
@@ -87,6 +107,8 @@
|
||||
components:
|
||||
- type: PDA
|
||||
id: PassengerIDCard
|
||||
- type: PDABorderColor
|
||||
borderColor: "#717059"
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
@@ -162,6 +184,8 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-cook
|
||||
- type: PDABorderColor
|
||||
borderColor: "#d7d7d0"
|
||||
- type: Icon
|
||||
state: pda-cook
|
||||
|
||||
@@ -200,6 +224,8 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-clown
|
||||
- type: PDABorderColor
|
||||
borderColor: "#C18199"
|
||||
- type: Icon
|
||||
state: pda-clown
|
||||
- type: Slippery
|
||||
@@ -258,6 +284,8 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-chaplain
|
||||
- type: PDABorderColor
|
||||
borderColor: "#333333"
|
||||
- type: Icon
|
||||
state: pda-chaplain
|
||||
|
||||
@@ -363,6 +391,8 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-janitor
|
||||
- type: PDABorderColor
|
||||
borderColor: "#5D2D56"
|
||||
- type: Icon
|
||||
state: pda-janitor
|
||||
|
||||
@@ -384,6 +414,8 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-captain
|
||||
- type: PDABorderColor
|
||||
borderColor: "#7C5D00"
|
||||
- type: Icon
|
||||
state: pda-captain
|
||||
|
||||
@@ -416,6 +448,9 @@
|
||||
components:
|
||||
- type: PDA
|
||||
id: CEIDCard
|
||||
- type: PDABorderColor
|
||||
borderColor: "#949137"
|
||||
accentHColor: "#447987"
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
@@ -450,6 +485,10 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-cmo
|
||||
- type: PDABorderColor
|
||||
borderColor: "#d7d7d0"
|
||||
accentHColor: "#447987"
|
||||
accentVColor: "#447987"
|
||||
- type: Icon
|
||||
state: pda-cmo
|
||||
- type: HealthAnalyzer
|
||||
@@ -467,6 +506,9 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-medical
|
||||
- type: PDABorderColor
|
||||
borderColor: "#d7d7d0"
|
||||
accentVColor: "#447987"
|
||||
- type: Icon
|
||||
state: pda-medical
|
||||
- type: HealthAnalyzer
|
||||
@@ -484,6 +526,9 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-chemistry
|
||||
- type: PDABorderColor
|
||||
borderColor: "#d7d7d0"
|
||||
accentVColor: "#B34200"
|
||||
- type: Icon
|
||||
state: pda-chemistry
|
||||
|
||||
@@ -529,6 +574,9 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-hos
|
||||
- type: PDABorderColor
|
||||
borderColor: "#A32D26"
|
||||
accentHColor: "#447987"
|
||||
- type: Icon
|
||||
state: pda-hos
|
||||
|
||||
@@ -642,6 +690,9 @@
|
||||
components:
|
||||
- type: PDA
|
||||
id: ERTLeaderIDCard
|
||||
- type: PDABorderColor
|
||||
borderColor: "#A32D26"
|
||||
accentHColor: "#447987"
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
@@ -657,6 +708,9 @@
|
||||
components:
|
||||
- type: PDA
|
||||
id: CBURNIDcard
|
||||
- type: PDABorderColor
|
||||
borderColor: "#A32D26"
|
||||
accentHColor: "#447987"
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
@@ -670,6 +724,9 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-medical
|
||||
- type: PDABorderColor
|
||||
borderColor: "#d7d7d0"
|
||||
accentVColor: "#447987"
|
||||
- type: Icon
|
||||
state: pda-medical
|
||||
|
||||
@@ -715,6 +772,9 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-boxer
|
||||
- type: PDABorderColor
|
||||
borderColor: "#333333"
|
||||
borderVColor: "#390504"
|
||||
- type: Icon
|
||||
state: pda-boxer
|
||||
|
||||
@@ -730,5 +790,7 @@
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
state: pda-detective
|
||||
- type: PDABorderColor
|
||||
borderColor: "#774705"
|
||||
- type: Icon
|
||||
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-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"
|
||||
}
|
||||
]
|
||||
}
|
||||