[Feat] War crime console (#144)

* ADD: Icons, New component. Надо будет рефакторнуть худы в одну систему

* Some govno ebanoe

* ui

* Some govno

* UI and UI lol

* Dermo again

* ы

* Добавлена система консоли. Надо добавить манипуляцию с рекордами и сохранение крим. записей на сервер. Я пометил в туду

* Added functional for Criminal Records UI

* Дропаю это говно

* Рабочая версия крим консоли

* Fuull functional

* Added radio

* Arrest info feature

* improve ui

* another names

* New texturem Sprite viewer

* fix small names

* Added login menu

* Final fix.

* ох

* Убрал логгеры

* fix Comments and Access to proto

* moved dummy code, removed qustyions

* added disposer() when window was close

* Small fixes

* Removed comments. Added DNA check for CriminalityHud

* Small lol

---------

Co-authored-by: DocNITE <docnite0530@gmail.com>
This commit is contained in:
RavMorgan
2023-06-15 13:17:04 +03:00
committed by Aviu00
parent 13c0d15601
commit 5f51cb81de
41 changed files with 1922 additions and 28 deletions

View File

@@ -1,10 +1,13 @@
using Content.Client.White.EntityCrimeRecords;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Inventory;
using Content.Shared.Mindshield.Components;
using Content.Shared.Overlays;
using Content.Shared.PDA;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.White.CriminalRecords;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
@@ -13,6 +16,11 @@ public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIco
{
[Dependency] private readonly IPrototypeManager _prototypeMan = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
// WD EDIT START
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly ShowCrimeRecordsSystem _parentSystem = default!;
// WD EDIT END
[ValidatePrototypeId<StatusIconPrototype>]
private const string JobIconForNoId = "JobIconNoId";
@@ -74,8 +82,82 @@ public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIco
result.Add(icon);
}
// Add arrest icons here, WYCI.
// WD EDIT START
if (!GetRecord(uid, out var type))
return result;
var protoId = type switch
{
EnumCriminalRecordType.Discharged => "CriminalRecordIconDischarged",
EnumCriminalRecordType.Incarcerated => "CriminalRecordIconIncarcerated",
EnumCriminalRecordType.Parolled => "CriminalRecordIconParolled",
EnumCriminalRecordType.Suspected => "CriminalRecordIconSuspected",
EnumCriminalRecordType.Wanted => "CriminalRecordIconWanted",
_ => "CriminalRecordIconReleased"
};
if (_prototypeMan.TryIndex<StatusIconPrototype>(protoId, out var recordIcon))
result.Add(recordIcon);
// WD EDIT END
return result;
}
// WD EDIT START
private bool GetRecord(EntityUid uid, out EnumCriminalRecordType type)
{
if (!_entManager.TryGetComponent(uid, out MetaDataComponent? meta))
{
type = EnumCriminalRecordType.Released;
return false;
}
var serverList = _entManager.EntityQuery<CriminalRecordsServerComponent>();
foreach (var server in serverList)
{
// if all good - check avaible records
foreach (var (key, info) in server.Cache)
{
// Check id
if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid))
{
// PDA
if (_entManager.TryGetComponent(idUid, out PdaComponent? pda) &&
_entManager.TryGetComponent(pda.ContainedId, out IdCardComponent? idCard))
{
if (idCard.FullName == info.StationRecord.Name &&
idCard.JobTitle == info.StationRecord.JobTitle)
{
type = info.CriminalType;
return true;
}
}
// ID Card
if (_entManager.TryGetComponent(idUid, out IdCardComponent? id))
{
idCard = id;
if (idCard.FullName == info.StationRecord.Name &&
idCard.JobTitle == info.StationRecord.JobTitle)
{
type = info.CriminalType;
return true;
}
}
}
// Check DNA (Dirty Nanotrasen tehnology lol)
// And yeah, he can't check - is pulled mask or not
// it's only Content.Server logic, idk hot it impl to Content.Client
if (_parentSystem.CanIdentityName(uid) != meta.EntityName)
continue;
if (meta.EntityName != info.StationRecord.Name)
continue;
type = info.CriminalType;
return true;
}
}
type = EnumCriminalRecordType.Released;
return false;
}
// WD EDIT END
}

View File

@@ -0,0 +1,74 @@
<controls:RecordCard xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.White.CriminalRecords.UI.Controls"
x:Class="Content.Client.White.CriminalRecords.UI.Controls.RecordCard">
<BoxContainer Name="MainContainer" Orientation="Vertical" Margin="10 10 10 10">
<!-- Header line -->
<PanelContainer
Name="SideLineElement"
Access="Public"
StyleClasses="PDABackground"
MinHeight="25"
VerticalExpand="False"
HorizontalExpand="True"
Margin="0 0 0 -5"/>
<!-- Header info -->
<PanelContainer VerticalExpand="True" HorizontalExpand="True" >
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1e1c29" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
Margin="5">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<SpriteView Name="ViewIcon"
Access="Public"
Scale="2 2"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center" Margin="10 0 0 0">
<BoxContainer Orientation="Horizontal">
<Label Access="Public" Name="CharacterNameLabel" Text="empty-element" StyleClasses="LabelKeyText" HorizontalExpand="True"/>
<TextureRect Access="Public" Name="JobIcon" TextureScale="3 3" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</BoxContainer>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 5 0 5"/>
<BoxContainer Orientation="Horizontal">
<!-- <RichTextLabel Access="Public" Name="StatusLabel" HorizontalAlignment="Left" StyleClasses="LabelSubText" HorizontalExpand="True"/> -->
<OptionButton Access="Public" Name="StatusOption" MinWidth="120"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Main Info -->
<Control MinHeight="5"></Control>
<RichTextLabel Access="Public" Name="DetailLabel" HorizontalExpand="True" StyleClasses="LabelSubText" Margin="10 0 0 0"></RichTextLabel>
<Control MinHeight="5"></Control>
<BoxContainer Orientation="Vertical" Margin="10 0 0 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<PanelContainer VerticalExpand="True" HorizontalExpand="True">
<Label Text="{Loc 'criminal-detail-info'}" HorizontalAlignment="Left"/>
<TextureButton Access="Public" Name="EditReason"
TexturePath="/Textures/Interface/pencil.png"
HorizontalAlignment="Right"></TextureButton>
</PanelContainer>
</BoxContainer>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
</BoxContainer>
<!-- Reason info -->
<RichTextLabel Visible="True" Access="Public" Name="ReasonWritten" HorizontalExpand="True" VerticalExpand="True"
MinHeight="100" Margin="10 0 0 0"/>
<PanelContainer Visible="False" Access="Public" Name="InputContainer"
StyleClasses="TransparentBorderedWindowPanel" MinHeight="100" Margin="10 0 0 0"
VerticalAlignment="Stretch" VerticalExpand="True" HorizontalExpand="True">
<TextEdit Name="Input" Access="Public" VerticalExpand="True" HorizontalExpand="True" />
</PanelContainer>
<BoxContainer Orientation="Vertical" Margin="10 0 0 0">
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</controls:RecordCard>

View File

@@ -0,0 +1,88 @@
using Content.Shared.StationRecords;
using Content.Shared.White.CriminalRecords;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client.White.CriminalRecords.UI.Controls;
[GenerateTypedNameReferences, Virtual]
public partial class RecordCard : Control
{
private FormattedMessage _description = new FormattedMessage();
public bool _canDoEvent = false;
public Action<string>? OnTextBindDown;
public RecordCard()
{
RobustXamlLoader.Load(this);
StatusOption.AddItem(Loc.GetString("criminal-status-released"), (int)EnumCriminalRecordType.Released);
StatusOption.AddItem(Loc.GetString("criminal-status-discharged"), (int)EnumCriminalRecordType.Discharged);
StatusOption.AddItem(Loc.GetString("criminal-status-parolled"), (int)EnumCriminalRecordType.Parolled);
StatusOption.AddItem(Loc.GetString("criminal-status-suspected"), (int)EnumCriminalRecordType.Suspected);
StatusOption.AddItem(Loc.GetString("criminal-status-wanted"), (int)EnumCriminalRecordType.Wanted);
StatusOption.AddItem(Loc.GetString("criminal-status-incarcerated"), (int)EnumCriminalRecordType.Incarcerated);
Input.OnKeyBindDown += args =>
{
if (args.Function == EngineKeyFunctions.TextSubmit)
{
UpdateReasonController();
args.Handle();
}
};
EditReason.OnPressed += args =>
{
UpdateReasonController();
};
}
public void InitializeStatusOption(StationRecordKey key, Dictionary<StationRecordKey, CriminalRecordInfo> cache)
{
if (cache != null)
{
foreach (var (kkey, info) in cache)
{
if (kkey.Id == key.Id)
{
StatusOption.SelectId((int) info.CriminalType);
break;
}
}
}
}
private void UpdateReasonController()
{
if (ReasonWritten.Visible)
{
var text = ReasonWritten.GetMessage();
ReasonWritten.SetMessage("");
ReasonWritten.Visible = false;
if (text != null)
{
Input.CursorPosition = new TextEdit.CursorPos();
Input.InsertAtCursor(text);
}
InputContainer.Visible = true;
}
else
{
var text = Rope.Collapse(Input.TextRope);
OnTextBindDown?.Invoke(text);
Input.TextRope = Rope.Leaf.Empty;
Input.CursorPosition = new TextEdit.CursorPos(0, TextEdit.LineBreakBias.Top);
// close input and open label
InputContainer.Visible = false;
// for label
ReasonWritten.SetMessage(text);
ReasonWritten.Visible = true;
}
}
}

View File

@@ -0,0 +1,28 @@
<!-- close.svg.192dpi -->
<controls:RecordIconButton xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.White.CriminalRecords.UI.Controls"
x:Class="Content.Client.White.CriminalRecords.UI.Controls.RecordIconButton">
<BoxContainer Orientation="Horizontal">
<Button
Name="Controller"
Access="Public"
HorizontalExpand="True"
VerticalExpand="False"
ToolTip="foobar"
TooltipDelay="0.25">
<BoxContainer Orientation="Horizontal" >
<TextureRect Access="Public" Name="LIcon"
MaxWidth="24"
MaxHeight="24"
VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="KeepAspectCentered" />
<RichTextLabel Access="Public"
HorizontalExpand="True"
Name="LLabel"
VerticalAlignment="Center"
HorizontalAlignment="Left"/>
</BoxContainer>
</Button>
</BoxContainer>
</controls:RecordIconButton>

View File

@@ -0,0 +1,36 @@
using Robust.Client.AutoGenerated;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Utility;
namespace Content.Client.White.CriminalRecords.UI.Controls;
[GenerateTypedNameReferences, Virtual]
public partial class RecordIconButton : Control
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
public string Icon
{
set
{
var path = new ResPath(value); // /Textures/Interface/VerbIcons/close.svg.192dpi.png
var specifier = new SpriteSpecifier.Texture(path);
LIcon.Texture = specifier.Frame0();
}
}
public string Label
{
set => LLabel.SetMessage(value);
}
public RecordIconButton()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
}

View File

@@ -0,0 +1,31 @@
<controls:RecordItem xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.White.CriminalRecords.UI.Controls"
x:Class="Content.Client.White.CriminalRecords.UI.Controls.RecordItem">
<BoxContainer MaxHeight="25" Orientation="Horizontal">
<PanelContainer
Name="SideLineElement"
Access="Public"
StyleClasses="PDABackground"
MaxWidth="10"
VerticalExpand="False"
HorizontalExpand="False"
Margin="0 0 -5 0"/>
<Button
Name="ButtonElement"
Access="Public"
HorizontalExpand="True"
VerticalExpand="False"
ToolTip="foobar"
TooltipDelay="0.25">
<BoxContainer Orientation="Horizontal" Margin="0">
<RichTextLabel Access="Public"
HorizontalExpand="True"
Name="NameLabel"
StyleClasses="LabelSubText"
VerticalAlignment="Center"/>
</BoxContainer>
</Button>
</BoxContainer>
</controls:RecordItem>

View File

@@ -0,0 +1,14 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.White.CriminalRecords.UI.Controls;
[GenerateTypedNameReferences, Virtual]
public partial class RecordItem : Control
{
public RecordItem()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -0,0 +1,75 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.StationRecords;
using Content.Shared.White.CriminalRecords;
using Robust.Client.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client.White.CriminalRecords.UI;
public sealed class CriminalRecordsBoundUserInterface : BoundUserInterface
{
private CriminalRecordsWindow? _window = default!;
public CriminalRecordsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{}
protected override void Open()
{
base.Open();
_window = new();
_window.OnKeySelected += OnKeySelected;
_window.OnStatusSelected += OnStatusSelected;
_window.OnTextBindDown += OnTextEntered;
_window.LogOutButton.Controller.OnPressed += _ =>
{
SendMessage(new ItemSlotButtonPressedEvent(CriminalRecordsConsoleComponent.IdSlotId));
};
_window.LogInButton.Controller.OnPressed += _ =>
{
SendMessage(new ItemSlotButtonPressedEvent(CriminalRecordsConsoleComponent.IdSlotId));
};
_window.OnClose += Close;
_window.OpenCentered();
}
private void OnKeySelected(StationRecordKey key)
{
SendMessage(new SelectCriminalRecord(key));
}
private void OnStatusSelected(StationRecordKey key, CriminalRecordInfo status)
{
SendMessage(new SelectCriminalStatus(key, status));
}
private void OnTextEntered(StationRecordKey key, string text)
{
if (!string.IsNullOrEmpty(text))
{
SendMessage(new SelectCriminalReason(key, text));
}
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not CriminalRecordsConsoleBuiState cast)
{
return;
}
_window?.UpdateState(cast);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_window?.Close();
_window?.Dispose();
}
}

View File

@@ -0,0 +1,99 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.White.CriminalRecords.UI.Controls"
xmlns:uicontrols="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'criminal-console-name'}"
MinSize="625 500"
Resizable="False">
<BoxContainer Access="Public" Name="NonAccessContent" Orientation="Vertical" VerticalAlignment="Center" VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<RichTextLabel Access="Public" HorizontalAlignment="Center" Name="LoginHint" HorizontalExpand="True" VerticalExpand="True"
MaxWidth="400" Margin="10 0 0 0"/>
<controls:RecordIconButton
Name="LogInButton"
Access="Public"
HorizontalAlignment="Center"
Icon="/Textures/Interface/VerbIcons/close.svg.192dpi.png"
Label="{Loc 'criminal-login-in'}"
Margin="10 0 10 0">
</controls:RecordIconButton>
</BoxContainer>
</BoxContainer>
<BoxContainer Access="Public" Name="MainContent" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<uicontrols:StripeBack HasBottomEdge="True" HasMargins="False" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal">
<RichTextLabel Access="Public"
HorizontalExpand="True"
Name="UserLabel"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10 0 10 0"/>
<controls:RecordIconButton
Name="LogOutButton"
Access="Public"
HorizontalAlignment="Right"
Icon="/Textures/Interface/VerbIcons/close.svg.192dpi.png"
Label="{Loc 'criminal-login-out'}"
Margin="10 0 10 0">
</controls:RecordIconButton>
</BoxContainer>
</uicontrols:StripeBack>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<!-- Persons list (chars) -->
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
HorizontalAlignment="Left"
SizeFlagsStretchRatio="2"
Margin="10 0 10 10"
MinWidth="175"
MaxWidth="175">
<Label Text="{Loc 'criminal-console-list'}" HorizontalAlignment="Center"/>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer
HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer
Name="RecordsListContainer"
Access="Public"
Orientation="Vertical"
VerticalExpand="True">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
<!-- Character info -->
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
SizeFlagsStretchRatio="3"
Margin="0 0 10 10">
<PanelContainer VerticalExpand="True" MinSize="0 200">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer
HScrollEnabled="False"
HorizontalExpand="True"
SizeFlagsStretchRatio="2"
VerticalExpand="True">
<BoxContainer
Name="RecordCardContainer"
Access="Public"
MinSize="100 256"
Orientation="Vertical"
SizeFlagsStretchRatio="2"
VerticalExpand="True">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,291 @@
using System.Linq;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.White.CriminalRecords.UI.Controls;
using Content.Shared.Access.Systems;
using Content.Shared.CrewManifest;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Radio.Components;
using Content.Shared.Roles;
using Content.Shared.StationRecords;
using Content.Shared.White.CriminalRecords;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Console;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.White.CriminalRecords.UI;
[GenerateTypedNameReferences]
public sealed partial class CriminalRecordsWindow : DefaultWindow
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public Action<StationRecordKey>? OnKeySelected;
public Action<StationRecordKey, string>? OnTextBindDown;
public Action<StationRecordKey, CriminalRecordInfo>? OnStatusSelected;
public CriminalRecordsWindow() : base()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void UpdateState(CriminalRecordsConsoleBuiState state)
{
// Check access AllAccess Security
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>();
var canAccess = state.IsAllowed;
// Check if exists card or not
if (state.ContainedId == null || !canAccess)
{
var messageHint = new FormattedMessage();
messageHint.AddMarkup(Loc.GetString("criminal-login-hint", ("name", Loc.GetString("criminal-login-in"))));
messageHint.AddMarkup("\n\n");
messageHint.AddMarkup(Loc.GetString("criminal-login-warn"));
LoginHint.SetMessage(messageHint);
MainContent.Visible = false;
NonAccessContent.Visible = true;
return;
}
MainContent.Visible = true;
NonAccessContent.Visible = false;
// Init header panel
UserLabel.SetMessage(Loc.GetString("criminal-login-info",
("user", (state.ContainedId.FullName ?? string.Empty) + ", " +
(state.ContainedId.JobTitle ?? string.Empty) )));
// Make crew list
Populate(state, state.RecordListing);
// Make card
if (state is { SelectedKey: not null, Record: not null })
{
CreateRecordCard(state, state.SelectedKey.Value, state.Record);
}
}
public void Populate(CriminalRecordsConsoleBuiState State, Dictionary<StationRecordKey, string>? RecordListing)
{
if (RecordListing == null)
return;
// clear govno from list
RecordsListContainer.RemoveAllChildren();
foreach (var (recordKey, name) in RecordListing)
{
var element = CreateRecordItem(State, recordKey, name);
element.ButtonElement.OnPressed += _ =>
{
OnKeySelected?.Invoke(recordKey);
};
}
}
private CriminalRecordInfo? GetRecord(StationRecordKey Key, Dictionary<StationRecordKey, CriminalRecordInfo> Cache)
{
foreach (var (key, info) in Cache)
{
if (Key.Id == key.Id)
{
return info;
}
}
return null;
}
private Color GetColor(StationRecordKey Key, Dictionary<StationRecordKey, CriminalRecordInfo> Cache)
{
var info = GetRecord(Key, Cache);
if (info == null)
return new Color(0,0,0);
switch (info.CriminalType)
{
case EnumCriminalRecordType.Released:
return new Color(14, 106, 254);
case EnumCriminalRecordType.Discharged:
return new Color(14,106,254);
case EnumCriminalRecordType.Parolled:
return new Color(151,196,66);
case EnumCriminalRecordType.Suspected:
return new Color(217,126,35);
case EnumCriminalRecordType.Wanted:
return new Color(190, 50 ,50);
case EnumCriminalRecordType.Incarcerated:
return new Color(196,164,114);
}
return new Color(0,0,0);
}
private RecordItem CreateRecordItem(CriminalRecordsConsoleBuiState State, StationRecordKey Key, string Name)
{
var record = new RecordItem();
record.VerticalAlignment = Control.VAlignment.Top;
if (State.Cache != null && GetRecord(Key, State.Cache) != null)
{
record.SideLineElement.ModulateSelfOverride = GetColor(Key, State.Cache);
}
else
{
record.SideLineElement.ModulateSelfOverride = new Color(0, 0 ,0);
}
record.NameLabel.SetMessage(Name);
// append element into list
RecordsListContainer.AddChild(record);
// result
return record;
}
private RecordCard CreateRecordCard(CriminalRecordsConsoleBuiState State, StationRecordKey Key, GeneralStationRecord Record)
{
var card = new RecordCard();
// set color
if (State.Cache != null && GetRecord(Key, State.Cache) != null)
{
card.SideLineElement.ModulateSelfOverride = GetColor(Key, State.Cache);
}
else
{
card.SideLineElement.ModulateSelfOverride = new Color(0, 0 ,0);
}
// name
card.CharacterNameLabel.Text = Record.Name + ", " + Record.JobTitle;
// job icon
var path = new ResPath("/Textures/Interface/Misc/job_icons.rsi");
_resourceCache.TryGetResource(path, out RSIResource? rsi);
if (rsi != null)
{
if (rsi.RSI.TryGetState(Record.JobIcon, out _))
{
var specifier = new SpriteSpecifier.Rsi(path, Record.JobIcon);
card.JobIcon.Texture = specifier.Frame0();
}
else if (rsi.RSI.TryGetState("Unknown", out _))
{
var specifier = new SpriteSpecifier.Rsi(path, "Unknown");
card.JobIcon.Texture = specifier.Frame0();
}
}
// status icon
card.ViewIcon.SetEntity(CreateCharacterDummy(Record));
// info
var dnaInfo = "";
if (Record.DNA != null)
dnaInfo = Record.DNA;
var fingerprintInfo = "";
if (Record.Fingerprint != null)
fingerprintInfo = Record.Fingerprint;
var message = new FormattedMessage();
message.AddMarkup(Loc.GetString("criminal-dna-name"));
message.AddMarkup("\n");
message.AddMarkup(Loc.GetString("criminal-dna-desc",
("color", new Color(171,129,222).ToHex()), ("info", dnaInfo) ));
message.AddMarkup("\n");
message.AddMarkup(Loc.GetString("criminal-fingerprint-name"));
message.AddMarkup("\n");
message.AddMarkup(Loc.GetString("criminal-fingerprint-desc",
("color", new Color(171,129,222).ToHex()), ("info", fingerprintInfo) ));
card.DetailLabel.SetMessage(message);
// clear and aapend
RecordCardContainer.DisposeAllChildren();
RecordCardContainer.AddChild(card);
// some shit (like change status)
card.StatusOption.OnItemSelected += eventArgs =>
{
if (!card._canDoEvent)
return;
var record = new CriminalRecordInfo(Record, (EnumCriminalRecordType)eventArgs.Id, card.ReasonWritten.GetMessage() ?? string.Empty);
OnStatusSelected?.Invoke(Key, record);
card.StatusOption.SelectId(eventArgs.Id);
};
// init status option
if (State.Cache != null)
{
card.InitializeStatusOption(Key, State.Cache);
card._canDoEvent = true;
}
// init reason text
if (State.Cache != null)
{
var criminalInfo = GetRecord(Key, State.Cache);
if (criminalInfo != null)
card.ReasonWritten.SetMessage(criminalInfo.Reason);
}
card.OnTextBindDown += args =>
{
OnTextBindDown?.Invoke(Key, args);
};
// rsult
return card;
}
private EntityUid CreateCharacterDummy(GeneralStationRecord Record)
{
IEntityManager entityManager = IoCManager.Resolve<IEntityManager>();
IPrototypeManager prototypeManager = IoCManager.Resolve<IPrototypeManager>();
HumanoidAppearanceSystem appearanceSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<HumanoidAppearanceSystem>();
var profile = Record.Profile ?? new HumanoidCharacterProfile();
var _previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(profile.Species).DollPrototype, MapCoordinates.Nullspace);
appearanceSystem.LoadProfile(_previewDummy, profile);
GiveDummyJobClothes(_previewDummy, Record.JobPrototype, profile);
return _previewDummy;
}
private void GiveDummyJobClothes(EntityUid dummy, string jobPrototype, HumanoidCharacterProfile profile)
{
IEntityManager entityManager = IoCManager.Resolve<IEntityManager>();
IPrototypeManager prototypeManager = IoCManager.Resolve<IPrototypeManager>();
ClientInventorySystem inventorySystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ClientInventorySystem>();
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
var job = prototypeManager.Index<JobPrototype>(jobPrototype ?? SharedGameTicker.FallbackOverflowJob);
if (job.StartingGear != null && inventorySystem.TryGetSlots(dummy, out var slots))
{
var gear = prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
foreach (var slot in slots)
{
var itemType = gear.GetGear(slot.Name, profile);
if (inventorySystem.TryUnequip(dummy, slot.Name, out var unequippedItem, true, true))
{
entityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = entityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
inventorySystem.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
}
private void GetAccess()
{
//_accessReader.FindAccessTags(item).ToArray();
}
}

View File

@@ -0,0 +1,183 @@
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Content.Shared.PDA;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Content.Shared.Access.Components;
using Content.Shared.Roles;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Content.Shared.White.CriminalRecords;
using Robust.Shared.Map;
namespace Content.Client.White.EntityCrimeRecords;
public sealed class EntityCrimeRecordsOverlay : Overlay
{
private readonly IEntityManager _entManager;
private readonly SharedTransformSystem _transform;
private readonly IPrototypeManager _prototypeManager;
private readonly InventorySystem _inventorySystem;
private readonly ShaderInstance _shader;
private readonly ShowCrimeRecordsSystem _parentSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public EntityCrimeRecordsOverlay(
IEntityManager entManager,
IPrototypeManager protoManager,
InventorySystem inventorySystem,
ShowCrimeRecordsSystem showCrimSystem)
{
_entManager = entManager;
_prototypeManager = protoManager;
_inventorySystem = inventorySystem;
_parentSystem = showCrimSystem;
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
_shader = protoManager.Index<ShaderPrototype>("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
var spriteQuery = _entManager.GetEntityQuery<SpriteComponent>();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
const float scale = 1f;
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3.CreateRotation(-rotation);
handle.UseShader(_shader);
// da pizda
this.ZIndex = this.ZIndex -= 1;
foreach (var hum in _entManager.EntityQuery<HumanoidAppearanceComponent>(true))
{
if (!xformQuery.TryGetComponent(hum.Owner, out var xform) ||
xform.MapID != args.MapId)
{
continue;
}
var worldPosition = _transform.GetWorldPosition(xform);
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
handle.SetTransform(matty);
if (GetRecord(hum.Owner, args.MapId, out var criminalType))
{
var icon = "released";
switch (criminalType)
{
case EnumCriminalRecordType.Released:
icon = "released";
break;
case EnumCriminalRecordType.Discharged:
icon = "discharged";
break;
case EnumCriminalRecordType.Parolled:
icon = "parolled";
break;
case EnumCriminalRecordType.Suspected:
icon = "suspected";
break;
case EnumCriminalRecordType.Wanted:
icon = "wanted";
break;
case EnumCriminalRecordType.Incarcerated:
icon = "incarcerated";
break;
}
var sprite_icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/White/Interface/records.rsi"), icon);
var _iconTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite_icon);
float yOffset;
float xOffset;
if (spriteQuery.TryGetComponent(hum.Owner, out var sprite))
{
yOffset = sprite.Bounds.Height + 7f - 7f; //sprite.Bounds.Height + 7f;
xOffset = sprite.Bounds.Width - 17f; //sprite.Bounds.Width + 7f;
}
else
{
yOffset = 1f;
xOffset = 1f;
}
// Position above the entity (we've already applied the matrix transform to the entity itself)
// Offset by the texture size for every do_after we have.
var position = new Vector2(xOffset / EyeManager.PixelsPerMeter,
yOffset / EyeManager.PixelsPerMeter);
// Draw the underlying bar texture
if (sprite != null && !sprite.ContainerOccluded)
handle.DrawTexture(_iconTexture, position);
}
}
handle.UseShader(null);
handle.SetTransform(Matrix3.Identity);
}
private bool GetRecord(EntityUid uid, MapId mapId, out EnumCriminalRecordType type)
{
if (!_entManager.TryGetComponent(uid, out MetaDataComponent? meta))
{
type = EnumCriminalRecordType.Released;
return false;
}
var serverList = _entManager.EntityQuery<CriminalRecordsServerComponent>();
foreach (var server in serverList)
{
// if all good - check avaible records
foreach (var (key, info) in server.Cache)
{
// Check id
if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid))
{
// PDA
if (_entManager.TryGetComponent(idUid, out PdaComponent? pda) &&
_entManager.TryGetComponent(pda.ContainedId, out IdCardComponent? idCard))
{
if (idCard.FullName == info.StationRecord.Name &&
idCard.JobTitle == info.StationRecord.JobTitle)
{
type = info.CriminalType;
return true;
}
}
// ID Card
if (_entManager.TryGetComponent(idUid, out IdCardComponent? id))
{
idCard = id;
if (idCard.FullName == info.StationRecord.Name &&
idCard.JobTitle == info.StationRecord.JobTitle)
{
type = info.CriminalType;
return true;
}
}
}
// Check DNA (Dirty Nanotrasen tehnology lol)
// And yeah, he can't check - is pulled mask or not
// it's only Content.Server logic, idk hot it impl to Content.Client
if (_parentSystem.CanIdentityName(uid) != meta.EntityName)
continue;
if (meta.EntityName != info.StationRecord.Name)
continue;
type = info.CriminalType;
return true;
}
}
type = EnumCriminalRecordType.Released;
return false;
}
}

View File

@@ -0,0 +1,104 @@
using Content.Shared.White.EntityCrimeRecords;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement.Components;
using Robust.Client.Player;
using Robust.Client.Graphics;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using Content.Shared.Inventory;
using Robust.Shared.Enums;
using Robust.Shared.Player;
namespace Content.Client.White.EntityCrimeRecords
{
public sealed class ShowCrimeRecordsSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
private EntityCrimeRecordsOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ShowCrimeRecordsComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ShowCrimeRecordsComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<ShowCrimeRecordsComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ShowCrimeRecordsComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
_overlay = new(EntityManager, _protoMan, _inventorySystem, this);
}
private void OnInit(EntityUid uid, ShowCrimeRecordsComponent component, ComponentInit args)
{
if (_player.LocalPlayer?.ControlledEntity == uid)
{
_overlayMan.AddOverlay(_overlay);
}
}
private void OnRemove(EntityUid uid, ShowCrimeRecordsComponent component, ComponentRemove args)
{
if (_player.LocalPlayer?.ControlledEntity == uid)
{
_overlayMan.RemoveOverlay(_overlay);
}
}
private void OnPlayerAttached(EntityUid uid, ShowCrimeRecordsComponent component, PlayerAttachedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, ShowCrimeRecordsComponent component, PlayerDetachedEvent args)
{
_overlayMan.RemoveOverlay(_overlay);
}
private void OnRoundRestart(RoundRestartCleanupEvent args)
{
_overlayMan.RemoveOverlay(_overlay);
}
public string CanIdentityName(EntityUid target)
{
var representation = GetIdentityRepresentation(target);
var ev = new SeeIdentityAttemptEvent();
RaiseLocalEvent(target, ev);
return representation.ToStringKnown(!ev.Cancelled);
}
/// <summary>
/// Gets an 'identity representation' of an entity, with their true name being the entity name
/// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one.
/// </summary>
private IdentityRepresentation GetIdentityRepresentation(EntityUid target,
InventoryComponent? inventory=null,
HumanoidAppearanceComponent? appearance=null)
{
var age = 18;
var gender = Gender.Epicene;
// Always use their actual age and gender, since that can't really be changed by an ID.
if (Resolve(target, ref appearance, false))
{
gender = appearance.Gender;
age = appearance.Age;
}
var trueName = Name(target);
if (!Resolve(target, ref inventory, false))
return new(trueName, gender, age.ToString(), string.Empty);
string? presumedJob = null;
string? presumedName = null;
// If it didn't find a job, that's fine.
return new IdentityRepresentation(trueName, gender, age.ToString(), presumedName, presumedJob);
}
}
}

View File

@@ -349,7 +349,7 @@ namespace Content.Server.Administration.Systems
if (TryComp(item, out PdaComponent? pda) &&
TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) &&
keyStorage.Key is { } key &&
_stationRecords.TryGetRecord(key.OriginStation, key, out GeneralStationRecord? record))
_stationRecords.TryGetRecord(GetEntity(key.OriginStation), key, out GeneralStationRecord? record))
{
if (TryComp(entity, out DnaComponent? dna) &&
dna.DNA != record.DNA)
@@ -363,7 +363,7 @@ namespace Content.Server.Administration.Systems
continue;
}
_stationRecords.RemoveRecord(key.OriginStation, key);
_stationRecords.RemoveRecord(GetEntity(key.OriginStation), key);
Del(item);
}
}

View File

@@ -73,20 +73,20 @@ public sealed class CrewManifestSystem : EntitySystem
// wrt the amount of players readied up.
private void AfterGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
{
BuildCrewManifest(ev.Key.OriginStation);
UpdateEuis(ev.Key.OriginStation);
BuildCrewManifest(GetEntity(ev.Key.OriginStation));
UpdateEuis(GetEntity(ev.Key.OriginStation));
}
private void OnRecordModified(RecordModifiedEvent ev)
{
BuildCrewManifest(ev.Key.OriginStation);
UpdateEuis(ev.Key.OriginStation);
BuildCrewManifest(GetEntity(ev.Key.OriginStation));
UpdateEuis(GetEntity(ev.Key.OriginStation));
}
private void OnRecordRemoved(RecordRemovedEvent ev)
{
BuildCrewManifest(ev.Key.OriginStation);
UpdateEuis(ev.Key.OriginStation);
BuildCrewManifest(GetEntity(ev.Key.OriginStation));
UpdateEuis(GetEntity(ev.Key.OriginStation));
}
private void OnBoundUiClose(EntityUid uid, CrewManifestViewerComponent component, BoundUIClosedEvent ev)

View File

@@ -72,14 +72,14 @@ public sealed class RenameCommand : IConsoleCommand
{
var origin = keyStorage.Key.Value.OriginStation;
if (recordsSystem.TryGetRecord<GeneralStationRecord>(origin,
if (recordsSystem.TryGetRecord<GeneralStationRecord>(_entManager.GetEntity(origin),
keyStorage.Key.Value,
out var generalRecord))
{
generalRecord.Name = name;
}
recordsSystem.Synchronize(origin);
recordsSystem.Synchronize(_entManager.GetEntity(origin));
}
}
}

View File

@@ -56,7 +56,7 @@ public sealed partial class StationRecordSet
/// </summary>
/// <param name="entry">Entry to add.</param>
/// <typeparam name="T">Type of the entry that's being added.</typeparam>
public StationRecordKey AddRecordEntry<T>(EntityUid station, T entry)
public StationRecordKey AddRecordEntry<T>(NetEntity station, T entry)
{
if (entry == null)
return StationRecordKey.Invalid;

View File

@@ -224,7 +224,7 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
if (!Resolve(station, ref records))
return StationRecordKey.Invalid;
return records.Records.AddRecordEntry(station, record);
return records.Records.AddRecordEntry(GetNetEntity(station), record);
}
/// <summary>

View File

@@ -0,0 +1,244 @@
using System.Linq;
using Content.Server.Radio.EntitySystems;
using Content.Server.Station.Systems;
using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.PDA;
using Content.Shared.Radio;
using Content.Shared.StationRecords;
using Content.Shared.White.CriminalRecords;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Server.White.CriminalRecords;
public sealed class CriminalRecordsConsoleSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly StationRecordsSystem _stationRecordsSystem = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly RadioSystem _radioSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<CriminalRecordsConsoleComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, BoundUIOpenedEvent>(UpdateUserInterface);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, SelectCriminalRecord>(OnKeySelected);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, SelectCriminalStatus>(OnStatusSelected);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, SelectCriminalReason>(OnReasonSelected);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, RecordModifiedEvent>(UpdateUserInterface);
SubscribeLocalEvent<CriminalRecordsConsoleComponent, AfterGeneralRecordCreatedEvent>(UpdateUserInterface);
}
private void OnComponentInit(EntityUid uid, CriminalRecordsConsoleComponent component, ComponentInit args)
{
_itemSlotsSystem.AddItemSlot(uid, CriminalRecordsConsoleComponent.IdSlotId, component.IdSlot);
}
private void OnComponentRemove(EntityUid uid, CriminalRecordsConsoleComponent component, ComponentRemove args)
{
_itemSlotsSystem.RemoveItemSlot(uid, component.IdSlot);
}
private void UpdateUserInterface<T>(EntityUid uid, CriminalRecordsConsoleComponent component, T ev)
{
UpdateUserInterface(uid, component);
}
private void OnItemInserted(EntityUid uid, CriminalRecordsConsoleComponent component, EntInsertedIntoContainerMessage args)
{
if (args.Container.ID == CriminalRecordsConsoleComponent.IdSlotId)
component.ContainedID = CompOrNull<IdCardComponent>(args.Entity);
UpdateUserInterface(uid, component);
}
private void OnItemRemoved(EntityUid uid, CriminalRecordsConsoleComponent component, EntRemovedFromContainerMessage args)
{
if (args.Container.ID == component.IdSlot.ID)
component.ContainedID = null;
UpdateUserInterface(uid, component);
}
private void OnKeySelected(EntityUid uid, CriminalRecordsConsoleComponent component,
SelectCriminalRecord msg)
{
component.ActiveKey = msg.SelectedKey;
UpdateUserInterface(uid, component);
}
private void OnReasonSelected(EntityUid uid, CriminalRecordsConsoleComponent component,
SelectCriminalReason msg)
{
var hasServer = new EventCheckServer();
RaiseLocalEvent(hasServer);
if (!hasServer.Result)
return;
var ev = new EventChangeReason(msg.SelectedKey, msg.Text);
RaiseLocalEvent(ev);
UpdateUserInterface(uid, component);
}
private void OnStatusSelected(EntityUid uid, CriminalRecordsConsoleComponent component,
SelectCriminalStatus msg)
{
if (msg.SelectedStatus == null)
return;
var hasServer = new EventCheckServer();
RaiseLocalEvent(hasServer);
if (!hasServer.Result)
return;
var messageId = "null";
switch (msg.SelectedStatus.CriminalType)
{
case EnumCriminalRecordType.Released:
messageId = "criminal-targetchannel-set-released";
break;
case EnumCriminalRecordType.Discharged:
messageId = "criminal-targetchannel-set-discharged";
break;
case EnumCriminalRecordType.Parolled:
messageId = "criminal-targetchannel-set-parolled";
break;
case EnumCriminalRecordType.Suspected:
messageId = "criminal-targetchannel-set-suspected";
break;
case EnumCriminalRecordType.Wanted:
messageId = "criminal-targetchannel-set-wanted";
break;
case EnumCriminalRecordType.Incarcerated:
messageId = "criminal-targetchannel-set-incarcerated";
break;
}
var message = "";
if (msg.SelectedStatus.Reason != string.Empty)
{
messageId += "-reason";
message = Loc.GetString(messageId,
("target", msg.SelectedStatus.StationRecord.Name),
("reason", msg.SelectedStatus.Reason));
}
else
{
message = Loc.GetString(messageId,
("target", msg.SelectedStatus.StationRecord.Name));
}
_radioSystem.SendRadioMessage(uid, message, _prototype.Index<RadioChannelPrototype>(component.TargetChannel), uid);
var ev = new EventChangeCache(msg.SelectedKey, msg.SelectedStatus);
RaiseLocalEvent(ev);
UpdateUserInterface(uid, component);
}
private void UpdateUserInterface(EntityUid uid,
CriminalRecordsConsoleComponent? console = null)
{
if (!Resolve(uid, ref console))
{
return;
}
Dirty(console);
var owningStation = _stationSystem.GetOwningStation(uid);
if (!TryComp<StationRecordsComponent>(owningStation, out var stationRecordsComponent))
{
CriminalRecordsConsoleBuiState state = new(null, null, null, null, null, false); //null
SetStateForInterface(uid, state);
return;
}
var consoleRecords =
_stationRecordsSystem.GetRecordsOfType<GeneralStationRecord>(owningStation.Value, stationRecordsComponent);
var listing = new Dictionary<StationRecordKey, string>();
foreach (var pair in consoleRecords)
{
listing.Add(pair.Item1, pair.Item2.Name);
}
if (listing.Count == 0)
{
CriminalRecordsConsoleBuiState state = new(null, null, null, null, null, false); //console!.Filter
SetStateForInterface(uid, state);
return;
}
else if (listing.Count == 1)
{
console!.ActiveKey = listing.Keys.First();
}
GeneralStationRecord? record = null;
if (console!.ActiveKey != null)
{
_stationRecordsSystem.TryGetRecord(owningStation.Value, console.ActiveKey.Value, out record,
stationRecordsComponent);
}
var serverEv = new EventGetCache();
RaiseLocalEvent(serverEv);
var idCardInfo = console.ContainedID != null ? new IdCardNetInfo(console.ContainedID.FullName, console.ContainedID.JobTitle) : null;
CriminalRecordsConsoleBuiState newState = new(console.ActiveKey, record, listing, serverEv.Cache, idCardInfo, AccessCheck(console.ContainedID)); //console.Filter
SetStateForInterface(uid, newState);
}
private void SetStateForInterface(EntityUid uid, CriminalRecordsConsoleBuiState newState)
{
var ui = _userInterface.GetUiOrNull(uid, CriminalRecordsConsoleKey.Key);
if (ui != null)
_userInterface.SetUiState(ui, newState);
}
private bool AccessCheck(IdCardComponent? component)
{
if (component is null)
return false;
var uid = component.Owner;
if (!EntityManager.TryGetComponent(uid, out AccessComponent? reader))
return false;
foreach (var tag in reader.Tags)
{
var proto = _prototype.Index<EntityPrototype>("ComputerCriminalRecords");
proto.TryGetComponent(out AccessReaderComponent? access);
if (access == null)
continue;
if (access.AccessLists.SelectMany(list => list).Any(entry => entry == tag))
{
return true;
}
}
return false;
}
}

View File

@@ -57,7 +57,7 @@ public sealed class AccessReaderSystem : EntitySystem
if (!id.IsValid())
continue;
component.AccessKeys.Add(new StationRecordKey(key.Item2, id));
component.AccessKeys.Add(new StationRecordKey(key.Item2, GetNetEntity(id)));
}
component.AccessLists = new(state.AccessLists);

View File

@@ -14,11 +14,11 @@ public abstract class SharedStationRecordsSystem : EntitySystem
public StationRecordKey Convert((NetEntity, uint) input)
{
return new StationRecordKey(input.Item2, GetEntity(input.Item1));
return new StationRecordKey(input.Item2, input.Item1);
}
public (NetEntity, uint) Convert(StationRecordKey input)
{
return (GetNetEntity(input.OriginStation), input.Id);
return (input.OriginStation, input.Id);
}
public List<(NetEntity, uint)> Convert(ICollection<StationRecordKey> input)

View File

@@ -1,18 +1,21 @@
using Robust.Shared.Serialization;
namespace Content.Shared.StationRecords;
// Station record keys. These should be stored somewhere,
// preferably within an ID card.
[Serializable, NetSerializable]
public readonly struct StationRecordKey : IEquatable<StationRecordKey>
{
[DataField("id")]
public readonly uint Id;
[DataField("station")]
public readonly EntityUid OriginStation;
public readonly NetEntity OriginStation;
public static StationRecordKey Invalid = default;
public StationRecordKey(uint id, EntityUid originStation)
public StationRecordKey(uint id, NetEntity originStation)
{
Id = id;
OriginStation = originStation;

View File

@@ -0,0 +1,110 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Radio;
using Content.Shared.StationRecords;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.White.CriminalRecords;
[RegisterComponent, NetworkedComponent]
public sealed partial class CriminalRecordsConsoleComponent : Component
{
public const string IdSlotId = "id-slot";
[DataField("idSlot")]
public ItemSlot IdSlot = new();
[DataField("TargetChannel", customTypeSerializer: typeof(PrototypeIdSerializer<RadioChannelPrototype>))]
public string TargetChannel = "Security";
[ViewVariables] public IdCardComponent? ContainedID;
[ViewVariables] public StationRecordKey? ActiveKey { get; set; }
}
[Serializable, NetSerializable]
public sealed class IdCardNetInfo
{
public string? FullName { get; }
public string? JobTitle { get; }
public IdCardNetInfo(string? fullName, string? jobTitle)
{
FullName = fullName;
JobTitle = jobTitle;
}
}
[Serializable, NetSerializable]
public sealed class CriminalRecordsConsoleBuiState : BoundUserInterfaceState
{
/// <summary>
/// Current selected key.
/// </summary>
public StationRecordKey? SelectedKey { get; }
public GeneralStationRecord? Record { get; }
public Dictionary<StationRecordKey, string>? RecordListing { get; }
public Dictionary<StationRecordKey, CriminalRecordInfo>? Cache { get; }
public IdCardNetInfo? ContainedId { get; }
public bool IsAllowed { get; }
//public GeneralStationRecordsFilter? Filter { get; }
public CriminalRecordsConsoleBuiState(StationRecordKey? key, GeneralStationRecord? record,
Dictionary<StationRecordKey, string>? recordListing, Dictionary<StationRecordKey, CriminalRecordInfo>? cache
, IdCardNetInfo? containedId, bool isAllowed) //GeneralStationRecordsFilter? newFilter
{
SelectedKey = key;
Record = record;
RecordListing = recordListing;
Cache = cache;
ContainedId = containedId;
IsAllowed = isAllowed;
//Filter = newFilter;
}
public bool IsEmpty() => SelectedKey == null
&& Record == null && RecordListing == null;
}
[Serializable, NetSerializable]
public sealed class SelectCriminalRecord : BoundUserInterfaceMessage
{
public StationRecordKey? SelectedKey { get; }
//
public SelectCriminalRecord(StationRecordKey? selectedKey)
{
SelectedKey = selectedKey;
}
}
[Serializable, NetSerializable]
public sealed class SelectCriminalStatus : BoundUserInterfaceMessage
{
public StationRecordKey SelectedKey { get; }
public CriminalRecordInfo? SelectedStatus { get; }
public SelectCriminalStatus(StationRecordKey selectedKey, CriminalRecordInfo? selectedStatus)
{
SelectedKey = selectedKey;
SelectedStatus = selectedStatus;
}
}
[Serializable, NetSerializable]
public sealed class SelectCriminalReason : BoundUserInterfaceMessage
{
public StationRecordKey SelectedKey { get; }
public string Text { get; }
public SelectCriminalReason(StationRecordKey key, string text)
{
SelectedKey = key;
Text = text;
}
}
[Serializable, NetSerializable]
public enum CriminalRecordsConsoleKey : byte
{
Key
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.StationRecords;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.White.CriminalRecords;
[Serializable, NetSerializable]
public enum EnumCriminalRecordType
{
Released = 0,
Discharged = 1,
Parolled = 2,
Suspected = 3,
Wanted = 4,
Incarcerated = 5
}
[Serializable, NetSerializable]
public sealed class CriminalRecordInfo
{
// Main data
[DataField("StationRecord")] public GeneralStationRecord StationRecord { get; set; }
[DataField("CriminalType")] public EnumCriminalRecordType CriminalType { get; set; }
[DataField("Reason")] public string Reason { get; set; }
public CriminalRecordInfo(GeneralStationRecord stationRecord, EnumCriminalRecordType criminalType, string reason)
{
this.StationRecord = stationRecord;
this.CriminalType = criminalType;
this.Reason = reason;
}
}
[RegisterComponent]
[NetworkedComponent]
public sealed partial class CriminalRecordsServerComponent : Component
{
[DataField("Cache")] public Dictionary<StationRecordKey, CriminalRecordInfo> Cache = new();
[Serializable, NetSerializable]
public sealed class CriminalRecordsServerComponentState : ComponentState
{
public Dictionary<StationRecordKey, CriminalRecordInfo> Cache { get; init; }
public CriminalRecordsServerComponentState(Dictionary<StationRecordKey, CriminalRecordInfo> cache)
{
Cache = cache;
}
}
}

View File

@@ -0,0 +1,133 @@
using Content.Shared.StationRecords;
using Robust.Shared.GameStates;
namespace Content.Shared.White.CriminalRecords;
public sealed class CriminalRecordsServerSystem: EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EventGetCache>(OnGetCache);
SubscribeLocalEvent<EventChangeCache>(OnChangeCache);
SubscribeLocalEvent<EventChangeReason>(OnChangeReason);
SubscribeLocalEvent<EventCheckServer>(OnCheckServer);
SubscribeLocalEvent<CriminalRecordsServerComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<CriminalRecordsServerComponent, ComponentHandleState>(OnHandleState);
}
private void OnGetState(EntityUid uid, CriminalRecordsServerComponent component, ref ComponentGetState args)
{
args.State = new CriminalRecordsServerComponent.CriminalRecordsServerComponentState(component.Cache);
}
private void OnHandleState(EntityUid uid, CriminalRecordsServerComponent component, ref ComponentHandleState args)
{
if (args.Current is not CriminalRecordsServerComponent.CriminalRecordsServerComponentState state)
return;
component.Cache = state.Cache;
}
private void OnGetCache(EventGetCache ev)
{
var serverList = EntityQuery<CriminalRecordsServerComponent>();
foreach (var server in serverList)
{
ev.Cache = server.Cache;
break;
}
}
private void OnChangeCache(EventChangeCache ev)
{
var serverList = EntityQuery<CriminalRecordsServerComponent>();
foreach (var server in serverList)
{
foreach (var (key, info) in server.Cache)
{
if (key.Id == ev.Key.Id)
{
info.Reason = ev.Record.Reason;
info.CriminalType = ev.Record.CriminalType;
Dirty(server);
return;
}
}
server.Cache.Add(ev.Key, ev.Record);
Dirty(server);
return;
}
}
private void OnChangeReason(EventChangeReason ev)
{
var serverList = EntityQuery<CriminalRecordsServerComponent>();
foreach (var server in serverList)
{
foreach (var (key, info) in server.Cache)
{
if (key.Id == ev.Key.Id)
{
info.Reason = ev.Text;
Dirty(server);
}
}
return;
}
}
private void OnCheckServer(EventCheckServer ev)
{
var serverList = EntityQuery<CriminalRecordsServerComponent>();
foreach (var server in serverList)
{
ev.Result = true;
return;
}
ev.Result = false;
}
}
// Events
public sealed class EventGetCache
{
public Dictionary<StationRecordKey, CriminalRecordInfo> Cache = new();
public EventGetCache()
{
}
}
public sealed class EventChangeCache
{
public CriminalRecordInfo Record { get; }
public StationRecordKey Key { get; }
public EventChangeCache(StationRecordKey key, CriminalRecordInfo record)
{
Key = key;
Record = record;
}
}
public sealed class EventChangeReason
{
public string Text { get; }
public StationRecordKey Key { get; }
public EventChangeReason(StationRecordKey key, string text)
{
Key = key;
Text = text;
}
}
public sealed class EventCheckServer
{
public bool Result { get; set; }
public EventCheckServer()
{
}
}

View File

@@ -0,0 +1,10 @@
namespace Content.Shared.White.EntityCrimeRecords
{
/// <summary>
/// Allow to see crime records info for character
/// </summary>
[RegisterComponent]
public sealed partial class ShowCrimeRecordsComponent : Component
{
}
}

View File

@@ -0,0 +1,39 @@
criminal-console-list = Список экипажа
criminal-console-name = консоль криминальных записей
criminal-grant-status-button-name = Статус
criminal-login-out = Log Out
criminal-login-in = Log In
criminal-login-info = ID: {$user}
criminal-login-hint = Вставьте ID карту в консоль, нажав на "{$name}" (нужно держать ID карту в руке)
criminal-login-warn = WARNING: Доступ к системе осуществляется уровнем доступа "службы безопасности"
criminal-status-released = Освобожден
criminal-status-discharged = Выписан
criminal-status-parolled = Закодирован
criminal-status-suspected = Подозреваемый
criminal-status-wanted = В розыске
criminal-status-incarcerated = Заключенный
criminal-targetchannel-set-released = {$target} освобожден(а).
criminal-targetchannel-set-released-reason = {$target} освобожден(а). Заметка: {$reason}.
criminal-targetchannel-set-discharged = {$target} выписан(а).
criminal-targetchannel-set-discharged-reason = {$target} выписан(а). Заметка: {$reason}.
criminal-targetchannel-set-parolled = {$target} закодирован(а).
criminal-targetchannel-set-parolled-reason = {$target} закодирован(а). Заметка: {$reason}.
criminal-targetchannel-set-suspected = {$target} под подозрением.
criminal-targetchannel-set-suspected-reason = {$target} под подозрением. Заметка: {$reason}.
criminal-targetchannel-set-wanted = {$target} объявлен(а) в розыск.
criminal-targetchannel-set-wanted-reason = {$target} объявлен(а) в розыск. Заметка: {$reason}.
criminal-targetchannel-set-incarcerated = {$target} был(а) заключен(а) под стражу.
criminal-targetchannel-set-incarcerated-reason = {$target} был(а) заключен(а) под стражу. Заметка: {$reason}.
criminal-dna-name = ДНК:
criminal-dna-desc = - [color={$color}]{$info}[/color]
criminal-fingerprint-name = Отпечатки пальцев:
criminal-fingerprint-desc = - [color={$color}]{$info}[/color]
criminal-detail-info = Заметка:
ent-CriminalRecordsServer = сервер криминальных записей
.desc = Содержит все преступные записи об экипажа на станции. Не дайте злоумышлиникам уничтожить его!

View File

@@ -281,12 +281,23 @@
- type: Computer
board: MedicalRecordsComputerCircuitboard
# mark this is needed
- type: entity
parent: BaseComputer
id: ComputerCriminalRecords
name: criminal records computer
description: This can be used to check criminal records.
components:
- type: CriminalRecordsConsole # my own impl
idSlot:
name: ID Card
ejectSound: /Audio/Machines/id_swipe.ogg
insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
ejectOnBreak: true
swap: false
whitelist:
components:
- IdCard
- type: Sprite
layers:
- map: ["computerLayerBody"]
@@ -297,12 +308,28 @@
state: explosive
- map: ["computerLayerKeys"]
state: security_key
- type: UserInterface
interfaces:
- key: enum.CriminalRecordsConsoleKey.Key
type: CriminalRecordsBoundUserInterface
- type: ActivatableUI
key: enum.CriminalRecordsConsoleKey.Key
- type: PointLight
radius: 1.5
energy: 1.6
color: "#1f8c28"
- type: Computer
board: CriminalRecordsComputerCircuitboard
- type: Tag
tags:
- EmagImmune
- type: ItemSlots
- type: ContainerContainer
containers:
board: !type:Container
id-slot: !type:ContainerSlot
- type: AccessReader
access: [["Security"]]
- type: entity
parent: BaseComputer

View File

@@ -781,13 +781,3 @@
Steel: 100
Glass: 900
Gold: 100
- type: latheRecipe
id: ClothingEyesHudMedical
icon: { sprite: Clothing/Eyes/Hud/med.rsi, state: icon }
result: ClothingEyesHudMedical
completetime: 4
materials:
Steel: 100
Glass: 300
Plasma: 200

View File

@@ -0,0 +1,73 @@
- type: entity
parent: [ BaseMachinePowered, ConstructibleMachine ]
id: CriminalRecordsServer
name: criminal records server
description: When powered and filled with encryption keys it allows radio headset communication.
components:
- type: Sprite
sprite: White/Structures/Machines/criminal_record_server.rsi
snapCardinals: true
netsync: false
layers:
- state: icon
- state: unlit
shader: unshaded
map: ["enum.PowerDeviceVisualLayers.Powered"]
- state: panel
map: ["enum.WiresVisualLayers.MaintenancePanel"]
- type: GenericVisualizer
visuals:
enum.PowerDeviceVisuals.Powered:
enum.PowerDeviceVisualLayers.Powered:
True: { visible: true }
False: { visible: false }
- type: Appearance
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.4,-0.4,0.4,0.4"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:ChangeConstructionNodeBehavior
node: machineFrame
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Machine
board: CriminalRecordsServerCircuitboard
- type: WiresPanel
- type: Wires
boardName: "CriminalRecordsServer"
layoutId: CriminalRecordsServer
- type: Transform
anchored: true
- type: Pullable
- type: CriminalRecordsServer
- type: ContainerContainer
containers:
machine_board: !type:Container
machine_parts: !type:Container
- type: entity
id: CriminalRecordsServerCircuitboard
parent: BaseMachineCircuitboard
name: criminal records server machine board
description: A machine printed circuit board for an telecommunication server.
components:
- type: MachineBoard
prototype: CriminalRecordsServer
materialRequirements:
Steel: 1
Cable: 2

View File

@@ -0,0 +1,47 @@
- type: statusIcon
id: CriminalRecordIcon
abstract: true
priority: 3
locationPreference: Right
- type: statusIcon
parent: CriminalRecordIcon
id: CriminalRecordIconReleased
icon:
sprite: White/Interface/records.rsi
state: released
- type: statusIcon
parent: CriminalRecordIcon
id: CriminalRecordIconDischarged
icon:
sprite: White/Interface/records.rsi
state: discharged
- type: statusIcon
parent: CriminalRecordIcon
id: CriminalRecordIconParolled
icon:
sprite: White/Interface/records.rsi
state: parolled
- type: statusIcon
parent: CriminalRecordIcon
id: CriminalRecordIconSuspected
icon:
sprite: White/Interface/records.rsi
state: suspected
- type: statusIcon
parent: CriminalRecordIcon
id: CriminalRecordIconWanted
icon:
sprite: White/Interface/records.rsi
state: wanted
- type: statusIcon
parent: CriminalRecordIcon
id: CriminalRecordIconIncarcerated
icon:
sprite: White/Interface/records.rsi
state: incarcerated

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

View File

@@ -0,0 +1,27 @@
{
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/tgstation/tgstation/blob/8e49222b72f6fdcbe741d2a6ce0a8425d95010b7/icons/mob/huds/hud.dmi",
"version": 1,
"size": { "y": 8, "x": 8 },
"states": [
{
"name": "wanted"
},
{
"name": "suspected"
},
{
"name": "released"
},
{
"name": "parolled"
},
{
"name": "incarcerated"
},
{
"name": "discharged"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -0,0 +1,34 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/9c3494fd79e6bf8dc532300b9de4f688ff276ac9/icons/obj/machines/telecomms.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "unlit",
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "icon"
},
{
"name": "panel"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB