diff --git a/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleBoundUserInterface.cs b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..140706aee52 --- /dev/null +++ b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleBoundUserInterface.cs @@ -0,0 +1,74 @@ +using Content.Shared._Triad.ContrabandPermit; +using Content.Shared.Containers.ItemSlots; +using Robust.Client.Player; + +namespace Content.Client._Triad.ContrabandPermit; + +public sealed partial class ContrabandPermitConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) +{ + [Dependency] private IPlayerManager _playerManager = default!; + + [ViewVariables] + private ContrabandPermitConsoleWindow? _menu; + + protected override void Open() + { + base.Open(); + + _menu = new(_playerManager); + _menu.SetOwner(Owner); + _menu.OpenCentered(); + + _menu.OnClose += Close; + + if (!EntMan.TryGetComponent(Owner, out var consoleComp)) + return; + + _menu.EjectButton.OnPressed += _ => SendPredictedMessage(new ItemSlotButtonPressedEvent(consoleComp.ChipSlotContainerId)); + + _menu.OnReasonChanged += OnReasonChanged; + _menu.OnGrantButtonPressed += OnGrantButtonPressed; + _menu.OnRevokeButtonPressed += OnRevokeButtonPressed; + _menu.OnPrintButtonPressed += OnPrintButtonPressed; + _menu.SendFocusChangeMessage += OnSendFocusChangeMessage; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + var castState = (ContrabandPermitConsoleBuiState) state; + _menu?.UpdateState(castState); + } + + private void OnReasonChanged(string reason) + { + SendPredictedMessage(new ContrabandPermitConsoleReasonUpdatedMessage(reason)); + } + + private void OnGrantButtonPressed() + { + SendPredictedMessage(new ContrabandPermitConsoleGrantButtonPressedMessage()); + } + + private void OnRevokeButtonPressed(string reason) + { + SendPredictedMessage(new ContrabandPermitConsoleRevokeButtonPressedMessage(reason)); + } + + private void OnPrintButtonPressed() + { + SendPredictedMessage(new ContrabandPermitConsolePrintButtonPressedMessage()); + } + + private void OnSendFocusChangeMessage(NetEntity? owner, NetEntity? item) + { + SendPredictedMessage(new ContrabandPermitConsoleFocusChangeMessage(owner, item)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _menu?.Close(); + } +} diff --git a/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleWindow.xaml b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleWindow.xaml new file mode 100644 index 00000000000..0c38305562c --- /dev/null +++ b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleWindow.xaml @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleWindow.xaml.cs b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleWindow.xaml.cs new file mode 100644 index 00000000000..50cd21ce217 --- /dev/null +++ b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitConsoleWindow.xaml.cs @@ -0,0 +1,352 @@ +using System.Linq; +using Content.Client.Administration.Managers; +using Content.Client.Message; +using Content.Client.UserInterface.Controls; +using Content.Shared._Triad.ContrabandPermit; +using Content.Shared._Triad.Humanoid; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.Administration; +using Content.Shared.Humanoid; +using Content.Shared.IdentityManagement; +using Content.Shared.Whitelist; +using Robust.Client.AutoGenerated; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.ColorNaming; +using Robust.Shared.Prototypes; + +namespace Content.Client._Triad.ContrabandPermit; + +[GenerateTypedNameReferences] +public sealed partial class ContrabandPermitConsoleWindow : FancyWindow +{ + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _prototype = default!; + [Dependency] private ILocalizationManager _localization = default!; + [Dependency] private IClientAdminManager _admin = default!; + + private readonly IPlayerManager _player; + + private readonly AccessReaderSystem _accessReader; + private readonly EntityWhitelistSystem _whitelist; + + private EntityUid? _owner; + private ContrabandPermitConsoleTab _currentTab = ContrabandPermitConsoleTab.PermitList; + private ContrabandPermitConsoleEntry[]? _entries = null; + private NetEntity? _trackedFocusOwner; + + public event Action? OnReasonChanged; + public event Action? OnGrantButtonPressed; + public event Action? OnRevokeButtonPressed; + public event Action? OnPrintButtonPressed; + public event Action? SendFocusChangeMessage; + + public ContrabandPermitConsoleWindow(IPlayerManager playerManager) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _player = playerManager; + _accessReader = _entManager.System(); + _whitelist = _entManager.System(); + + // Mode switching + PermitListTabButton.OnPressed += PermitListTabPressed; + GrantPermitTabButton.OnPressed += GrantPermitTabPressed; + + // Permit reason text box + PermitReasonTextBox.OnTextEntered += e => OnReasonChanged?.Invoke(e.Text); + PermitReasonTextBox.OnFocusExit += e => OnReasonChanged?.Invoke(e.Text); + + // Grant button + GrantButton.OnPressed += _ => OnGrantButtonPressed?.Invoke(); + + // Print chip button + ContrabandPermitPrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke(); + + var tabGroup = new ButtonGroup(); + PermitListTabButton.Group = tabGroup; + GrantPermitTabButton.Group = tabGroup; + + PermitListTabButton.Pressed = true; + SetupTab(_currentTab); + } + + public void SetOwner(EntityUid owner) + { + _owner = owner; + + if (_entManager.TryGetComponent(_owner, out var console)) + _trackedFocusOwner = console.FocusedEntry?.PermitOwner; + } + + private void PermitListTabPressed(BaseButton.ButtonEventArgs obj) + { + ChangeTab(ContrabandPermitConsoleTab.PermitList); + } + + private void GrantPermitTabPressed(BaseButton.ButtonEventArgs obj) + { + ChangeTab(ContrabandPermitConsoleTab.PermitGranter); + } + + private void SetupTab(ContrabandPermitConsoleTab tab) + { + // Clear the modes, then set it up + PermitListContainer.Visible = false; + PermitGranterContainer.Visible = false; + + switch (tab) + { + case ContrabandPermitConsoleTab.PermitList: + PermitListContainer.Visible = true; + break; + case ContrabandPermitConsoleTab.PermitGranter: + PermitGranterContainer.Visible = true; + break; + default: + throw new NotImplementedException(); + } + } + + public void ChangeTab(ContrabandPermitConsoleTab tab) + { + if (_currentTab == tab) + return; + + _currentTab = tab; + SetupTab(_currentTab); + } + + public enum ContrabandPermitConsoleTab : byte + { + PermitList, + PermitGranter, + } + + public void UpdateEntries(ContrabandPermitConsoleEntry[] entries, PermitEntryFocusData? focusData) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var consoleComp)) + return; + + if (_trackedFocusOwner != focusData?.PermitOwner) + focusData = null; + + _entries = entries; + + // Clear excess children from the tables + var entryCount = _entries.Length; + + while (ActivePermitsTable.ChildCount > entryCount) + ActivePermitsTable.RemoveChild(ActivePermitsTable.GetChild(ActivePermitsTable.ChildCount - 1)); + + // Update all entries in each table + for (var index = 0; index < _entries.Length; index++) + { + var entry = _entries.ElementAt(index); + UpdateUIEntry(entry, index, ActivePermitsTable, consoleComp, focusData); + } + + if (entryCount == 0) + { + var label = new RichTextLabel() + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + }; + + label.SetMarkup(Loc.GetString("contraband-permit-no-permits")); + + ActivePermitsTable.AddChild(label); + + if (consoleComp.CurrentPermitReason != string.Empty && !PermitReasonTextBox.HasKeyboardFocus()) + PermitReasonTextBox.Text = consoleComp.CurrentPermitReason; + } + } + + private void UpdateUIEntry(ContrabandPermitConsoleEntry entry, int index, Control table, ContrabandPermitConsoleComponent console, PermitEntryFocusData? focusData) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var consoleComp)) + return; + + var items = entry.Items; + + var owner = entry.Owner; + var ownerEnt = _entManager.GetEntity(entry.Owner); + + if (_entManager.TryGetComponent(ownerEnt, out var humanoid) && humanoid.PvsView is { } pvsView) + owner = _entManager.GetNetEntity(pvsView); + + // Make new UI entry if required + if (index >= table.ChildCount) + { + var newEntryContainer = new ContrabandPermitEntryContainer(owner, items, this, _owner); + + // On click + newEntryContainer.FocusButton.OnButtonUp += args => + { + if (_trackedFocusOwner == owner) + { + _trackedFocusOwner = null; + UpdateFocus(null, null); + } + else + { + _trackedFocusOwner = owner; + UpdateFocus(owner, null); + } + }; + + var hasAccess = _player.LocalEntity is not { } localPlayer || + !_entManager.TryGetComponent(_owner, out var access) || + _accessReader.IsAllowed(localPlayer, _owner.Value, access); + + newEntryContainer.RevokeButton.Disabled = !hasAccess; + newEntryContainer.RevokeReasonTextBox.Editable = hasAccess; + + newEntryContainer.RevokeButton.OnPressed += args => + { + var reason = newEntryContainer.RevokeReasonTextBox.Text; + SendRevokeButtonMessage(reason); + }; + + // Add the entry to the current table + table.AddChild(newEntryContainer); + } + + // Update values and UI elements + var tableChild = table.GetChild(index); + + if (tableChild is not ContrabandPermitEntryContainer) + { + table.RemoveChild(tableChild); + UpdateUIEntry(entry, index, table, console, focusData); + return; + } + + var entryContainer = (ContrabandPermitEntryContainer)tableChild; + entryContainer.UpdatePermitOwnerAndEntries(owner, items); + entryContainer.UpdateEntry(focusData); + } + + public void UpdateFocus(NetEntity? owner, NetEntity? item) + { + SendFocusChangeMessage?.Invoke(owner, item); + } + + public void SendRevokeButtonMessage(string reason) + { + OnRevokeButtonPressed?.Invoke(reason); + } + + public void UpdateState(ContrabandPermitConsoleBuiState state) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var consoleComp)) + return; + + var validWhitelist = true; + if (consoleComp.GrantPermitWhitelist != null + && _player.LocalEntity != null + && !_admin.HasFlag(AdminFlags.Admin)) + { + if (_whitelist.CheckBoth(_player.LocalEntity, consoleComp.GrantPermitBlacklist, consoleComp.GrantPermitWhitelist)) + validWhitelist = true; + else + validWhitelist = false; + } + + UpdateEntries(state.Entries, state.FocusData); + + if (state.InsertedChip != null && validWhitelist) + { + UpdateInsertedChipInfo(state, state.InsertedChip.Value); + } + else + { + // Show the no access/no item screen + NoItemInsertedScreen.Visible = true; + PermitGranterLeft.Visible = false; + PermitGranterRight.Visible = false; + + if (!validWhitelist) + NoItemInsertedLabel.Text = Loc.GetString("contraband-permit-console-window-permit-grant-no-access"); + else + NoItemInsertedLabel.Text = Loc.GetString("contraband-permit-console-window-permit-grant-no-item"); + } + } + + private void UpdateInsertedChipInfo(ContrabandPermitConsoleBuiState state, NetEntity chipNetEnt) + { + NoItemInsertedScreen.Visible = false; + PermitGranterLeft.Visible = true; + PermitGranterRight.Visible = true; + + var insertedChip = _entManager.GetEntity(chipNetEnt); + + if (!_entManager.TryGetComponent(insertedChip, out var chip)) + return; + + var carrier = _entManager.GetEntity(chip.ScannedPermitCarrier); + var scannedItem = _entManager.GetEntity(chip.ScannedItem); + + if (carrier == null || scannedItem == null) + { + // Reset info + PermitOwnerView.SetEntity(null); + OwnerNameLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-name", ("name", string.Empty)); + OwnerSpeciesLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-species", ("species", string.Empty)); + OwnerAgeLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-age", ("age", string.Empty)); + OwnerGenderLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-gender", ("gender", string.Empty)); + OwnerEyeColorLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-eye-color", ("color", string.Empty)); + + ItemNameLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-item-name", ("name", string.Empty)); + DateGrantedLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-date", ("date", string.Empty)); + return; + } + + if (_entManager.TryGetComponent(carrier, out var view) + && view.PvsView is { } pvsView + && _entManager.TryGetComponent(pvsView, out var appearance)) + { + PermitOwnerView.SetEntity(pvsView); + + OwnerNameLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-name", ("name", Identity.Name(pvsView, _entManager))); + + var species = _prototype.Index(appearance.Species); + var speciesName = Loc.GetString(species.Name); + OwnerSpeciesLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-species", ("species", speciesName)); + + OwnerAgeLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-age", ("age", appearance.Age)); + + OwnerGenderLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-gender", ("gender", appearance.Gender.ToString())); + + var colorName = ColorNaming.Describe(appearance.EyeColor, _localization); + OwnerEyeColorLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-eye-color", ("color", colorName)); + } + + if (_entManager.TryGetComponent(scannedItem, out var meta)) + { + var itemPrototype = meta.EntityPrototype; + + if (itemPrototype != null) + { + ItemNameLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-item-name", ("name", itemPrototype.Name)); + DateGrantedLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-date", ("date", state.DateTime ?? string.Empty)); + } + } + } +} diff --git a/Content.Client/_Triad/ContrabandPermit/ContrabandPermitEntryContainer.xaml b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitEntryContainer.xaml new file mode 100644 index 00000000000..ea5a3af55d0 --- /dev/null +++ b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitEntryContainer.xaml @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Triad/ContrabandPermit/ContrabandPermitEntryContainer.xaml.cs b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitEntryContainer.xaml.cs new file mode 100644 index 00000000000..eb908756496 --- /dev/null +++ b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitEntryContainer.xaml.cs @@ -0,0 +1,158 @@ +using System.Linq; +using Content.Shared._Triad.ContrabandPermit; +using Content.Shared._Triad.Humanoid; +using Content.Shared.Humanoid; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.ColorNaming; +using Robust.Shared.Prototypes; + +namespace Content.Client._Triad.ContrabandPermit; + +[GenerateTypedNameReferences] +public sealed partial class ContrabandPermitEntryContainer : BoxContainer +{ + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _prototype = default!; + [Dependency] private ILocalizationManager _localization = default!; + + public EntityUid? ConsoleEntity; + public NetEntity PermitOwner; + public List PermitItems; + + private readonly ContrabandPermitConsoleWindow? _window; + private readonly ButtonGroup _permitItemButtons = new(); + + public ContrabandPermitEntryContainer(NetEntity uid, List items, ContrabandPermitConsoleWindow window, EntityUid? consoleEntity) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + PermitOwner = uid; + PermitItems = items; + _window = window; + ConsoleEntity = consoleEntity; + } + + public void UpdatePermitOwnerAndEntries(NetEntity uid, List items) + { + PermitOwner = uid; + PermitItems = items; + } + + public void UpdateEntry(PermitEntryFocusData? focusData = null) + { + if (ConsoleEntity == null) + return; + + var ownerName = string.Empty; + var ownerEntity = _entManager.GetEntity(PermitOwner); + + if (_entManager.TryGetComponent(ownerEntity, out var meta)) + ownerName = meta.EntityName; + + // Set the sprite view to the owner if it is a humanoid view + if (_entManager.HasComponent(ownerEntity)) + PermitOwnerSprite.SetEntity(ownerEntity); + + // Update owner name and amount of permits + PermitOwnerNameLabel.Text = ownerName; + PermitAmountLabel.Text = Loc.GetString("contraband-permit-console-window-permit-owner-entries", ("number", PermitItems.Count)); + + FocusContainer.Visible = false; + + // Clear children if not the focus + if (!_entManager.EntityExists(ownerEntity) || focusData == null || focusData.Value.PermitOwner != PermitOwner) + { + PermitScrollContainer.RemoveAllChildren(); + return; + } + + FocusContainer.Visible = true; + + var hasItemSelected = focusData.Value.SelectedItem != null; + NoItemSelectedScreen.Visible = !hasItemSelected; + PermitViewContainer.Visible = hasItemSelected; + + // Clear excess children from the tables when updating + var itemCount = PermitItems.Count; + + while (PermitScrollContainer.ChildCount > itemCount) + PermitScrollContainer.RemoveChild(PermitScrollContainer.GetChild(PermitScrollContainer.ChildCount - 1)); + + // Update all entries in each table + for (var index = 0; index < PermitItems.Count; index++) + { + var entry = PermitItems.ElementAt(index); + UpdateContainer(entry, ownerEntity, PermitScrollContainer, index, focusData.Value); + } + } + + private void UpdateContainer(NetEntity item, EntityUid permitOwner, Control table, int index, PermitEntryFocusData focusData) + { + var isSelectedItem = focusData.SelectedItem == item; + var itemEntity = _entManager.GetEntity(item); + + if (!_entManager.TryGetComponent(itemEntity, out var itemMeta)) + return; + + if (itemMeta.EntityPrototype == null) + return; + + if (!_entManager.EntityExists(itemEntity)) + return; + + if (isSelectedItem && _entManager.TryGetComponent(itemEntity, out var permit)) + { + // Set item info + if (itemMeta.EntityPrototype != null) + { + ItemPrototype.SetPrototype(itemMeta.EntityPrototype); + ItemLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-item-name", ("name", itemMeta.EntityPrototype.Name)); + } + + DateLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-date", ("date", permit.DateGranted)); + ReasonLabel.Text = Loc.GetString("contraband-permit-console-window-label-permit-tab-reason", ("reason", permit.PermitReason)); + + // Now owner info + OwnerNameLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-name", ("name", permit.PermitOwnerName)); + + if (_entManager.TryGetComponent(permitOwner, out var humanoidAppearance)) + { + var species = _prototype.Index(humanoidAppearance.Species); + var speciesName = Loc.GetString(species.Name); + OwnerSpeciesLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-species", ("species", speciesName)); + + OwnerAgeLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-age", ("age", humanoidAppearance.Age)); + + var gender = humanoidAppearance.Gender.ToString(); + OwnerGenderLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-gender", ("gender", gender)); + + var colorName = ColorNaming.Describe(humanoidAppearance.EyeColor, _localization); + OwnerEyeColorLabel.Text = Loc.GetString("contraband-permit-console-window-label-grant-tab-owner-eye-color", ("color", colorName)); + } + } + + // Make new buttons if needed + if (index >= table.ChildCount) + { + var newButton = new Button + { + Text = itemMeta.EntityName, + ToggleMode = true, + HorizontalExpand = true, + Pressed = isSelectedItem, + Group = _permitItemButtons + }; + + newButton.OnPressed += args => + { + _window?.UpdateFocus(PermitOwner, item); + }; + + table.AddChild(newButton); + } + } +} diff --git a/Content.Client/_Triad/ContrabandPermit/ContrabandPermitSystem.cs b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitSystem.cs new file mode 100644 index 00000000000..365cbbb99c9 --- /dev/null +++ b/Content.Client/_Triad/ContrabandPermit/ContrabandPermitSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._Triad.ContrabandPermit; + +namespace Content.Client._Triad.ContrabandPermit; + +public sealed partial class ContrabandPermitSystem : SharedContrabandPermitSystem; diff --git a/Content.Server/_Triad/ContrabandPermit/ContrabandPermitSystem.cs b/Content.Server/_Triad/ContrabandPermit/ContrabandPermitSystem.cs new file mode 100644 index 00000000000..d459b685c6c --- /dev/null +++ b/Content.Server/_Triad/ContrabandPermit/ContrabandPermitSystem.cs @@ -0,0 +1,319 @@ +using Content.Server.Administration.Logs; +using Content.Server.Chat.Managers; +using Content.Shared._Triad.ContrabandPermit; +using Content.Shared.CartridgeLoader; +using Content.Server.CartridgeLoader; +using Content.Shared.Database; +using Content.Shared.PDA; +using Content.Server._NF.SectorServices; +using Content.Shared._Triad.Humanoid; +using System.Runtime.InteropServices; +using Content.Server.Radio.EntitySystems; +using Robust.Shared.Map.Components; +using Content.Server.Mind; +using Robust.Server.GameStates; + +namespace Content.Server._Triad.ContrabandPermit; + +public sealed partial class ContrabandPermitSystem : SharedContrabandPermitSystem +{ + [Dependency] private IChatManager _chat = default!; + [Dependency] private IAdminLogManager _adminLog = default!; + [Dependency] private CartridgeLoaderSystem _cartridgeLoader = default!; + [Dependency] private RadioSystem _radio = default!; + [Dependency] private MindSystem _mind = default!; + [Dependency] private EntityLookupSystem _lookup = default!; + [Dependency] private PvsOverrideSystem _pvs = default!; + [Dependency] private SectorServiceSystem _sectorService = default!; + + private readonly HashSet> _newPermitItems = new(); + + private EntityQuery _gridQuery; + private EntityQuery _transformQuery; + + public override void Initialize() + { + base.Initialize(); + + _gridQuery = GetEntityQuery(); + _transformQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnPermitGranted); + SubscribeLocalEvent(OnPermitRevoked); + + SubscribeLocalEvent(OnPermitItemStartup); + SubscribeLocalEvent(OnPermitItemShutdown); + SubscribeLocalEvent(OnPermitItemTerminating); + } + + private void OnPermitGranted(Entity ent, ref ContrabandPermitGrantedEvent args) + { + var permitItem = args.PermitEntity; + var permitOwner = args.PermitOwner; + var permitGranter = args.PermitGranter; + var permitReason = args.Reason; + + var message = $"{ToPrettyString(permitGranter):player} granted contraband permit to {ToPrettyString(permitOwner)} with reason {permitReason} " + + $"for the item {ToPrettyString(permitItem)}"; + + _chat.SendAdminAlert(message); + _adminLog.Add(LogType.Action, LogImpact.High, $"{message}"); + + var header = Loc.GetString("contraband-permit-console-pda-message-header"); + var pdaMsg = Loc.GetString("contraband-permit-console-pda-message-permit-granted", ("item", permitItem), ("reason", permitReason)); + SendPermitOwnerPdaMessage(permitOwner, header, pdaMsg); + + if (args.Console != null && permitGranter != null) + { + var consoleMessage = Loc.GetString("contraband-permit-console-radio-message-permit-granted", + ("item", permitItem), + ("user", permitGranter), + ("owner", permitOwner), + ("reason", permitReason)); + SendConsoleRadioMessage(args.Console.Value, consoleMessage); + } + + // Now, add the permit record to the sector service + AddPermitRecordToSectorService(permitOwner, permitItem); + } + + private void OnPermitRevoked(Entity ent, ref ContrabandPermitRevokedEvent args) + { + var permitItem = args.PermitEntity; + var permitOwner = args.PermitOwner; + var permitRevoker = args.PermitRevoker; + var permitReason = args.Reason; + + var message = $"{ToPrettyString(permitRevoker):player} revoked contraband permit from {ToPrettyString(permitOwner)} with reason {permitReason} " + + $"for the item {ToPrettyString(permitItem)}"; + + _chat.SendAdminAlert(message); + _adminLog.Add(LogType.Action, LogImpact.High, $"{message}"); + + var header = Loc.GetString("contraband-permit-console-pda-message-header"); + var pdaMsg = Loc.GetString("contraband-permit-console-pda-message-permit-revoked", ("item", permitItem), ("reason", permitReason)); + SendPermitOwnerPdaMessage(permitOwner, header, pdaMsg); + + if (args.Console != null && permitRevoker != null) + { + var consoleMessage = Loc.GetString("contraband-permit-console-radio-message-permit-revoked", + ("item", permitItem), + ("user", permitRevoker), + ("owner", permitOwner), + ("reason", permitReason)); + SendConsoleRadioMessage(args.Console.Value, consoleMessage); + } + + // Goodbye + RemovePermitRecordToSectorService(permitOwner, permitItem); + } + + private void OnPermitItemStartup(Entity ent, ref ComponentStartup args) + { + // So that permit items outside of PVS range can still be viewed + _pvs.AddGlobalOverride(ent.Owner); + } + + private void OnPermitItemShutdown(Entity ent, ref ComponentShutdown args) + { + _pvs.RemoveGlobalOverride(ent.Owner); + } + + private void OnPermitItemTerminating(Entity ent, ref EntityTerminatingEvent args) + { + if (ent.Comp.PermitOwner == null) + return; + + // Delete permit records of entities about to be terminated + RemovePermitRecordToSectorService(ent.Comp.PermitOwner.Value, ent.Owner); + } + + public void AddPermitRecordToSectorService(EntityUid permitOwner, EntityUid permitItem) + { + if (!TryComp(_sectorService.GetServiceEntity(), out SectorContrabandPermitsComponent? contrabandPermitNet)) + return; + + // The 'permit owner'. This is the Global PVS humanoid view of the owner so the picture works outside of PVS range, or the entity itself if it doesn't exist + var entryEntity = GetNetEntity(permitOwner); + if (TryComp(permitOwner, out var humanoidView) && humanoidView.PvsView != null) + entryEntity = GetNetEntity(humanoidView.PvsView.Value); + + // Initalize the key if it doesn't exist, get the list of permits that the owner has or add one if it doesn't exist, then add the new permit item under the record + ref var permitList = ref CollectionsMarshal.GetValueRefOrAddDefault(contrabandPermitNet.Records, entryEntity, out var exists); + + if (!exists || permitList == null) + permitList = new List(); + + permitList.Add(GetNetEntity(permitItem)); + + UpdatePermitConsoles(); + } + + public void RemovePermitRecordToSectorService(EntityUid permitOwner, EntityUid permitItem) + { + if (!TryComp(_sectorService.GetServiceEntity(), out SectorContrabandPermitsComponent? contrabandPermitNet)) + return; + + var entryEntity = GetNetEntity(permitOwner); + if (TryComp(permitOwner, out var humanoidView) && humanoidView.PvsView != null) + entryEntity = GetNetEntity(humanoidView.PvsView.Value); + + if (contrabandPermitNet.Records.ContainsKey(entryEntity) && contrabandPermitNet.Records.TryGetValue(entryEntity, out var list)) + { + list.Remove(GetNetEntity(permitItem)); + + if (list.Count == 0) + contrabandPermitNet.Records.Remove(entryEntity); + } + + UpdatePermitConsoles(); + } + + public void InitializePermitItemsOnGrid(EntityUid gridUid, EntityUid user) + { + if (!_gridQuery.HasComp(gridUid)) + return; + + _newPermitItems.Clear(); + + var gridTransform = _transformQuery.GetComponent(gridUid); + var worldAABB = _lookup.GetWorldAABB(gridUid, gridTransform); + _lookup.GetEntitiesIntersecting(gridTransform.MapID, worldAABB, _newPermitItems); + + foreach ((var ent, var comp) in _newPermitItems) + { + if (ent == gridUid) + continue; + + if (!_transformQuery.TryComp(ent, out var entXForm) || entXForm.GridUid != gridUid) + continue; + + comp.PermitOwner = user; + + if (_mind.TryGetMind(user, out var mindId, out var mindComp)) + { + comp.PermitOwnerMind = mindId; + + // Log if the names are different + if (mindComp.CharacterName != comp.PermitOwnerName) + { + var message = $"{ToPrettyString(user):player} owns a contraband permit with a different logged name." + + $" (Permit Owner Name: {comp.PermitOwnerName}, Player Name: {mindComp.CharacterName})" + + " Possible abuse of the ship saving system may be at play here."; + + _chat.SendAdminAlert(message); + _adminLog.Add(LogType.EntitySpawn, LogImpact.Medium, $"{message}"); + } + } + + Dirty(ent, comp); + AddPermitRecordToSectorService(user, ent); + } + } + + public void ClearPermitItemsOnGrid(EntityUid gridUid, EntityUid user) + { + if (!_gridQuery.HasComp(gridUid)) + return; + + var toDelete = new HashSet(); + + _newPermitItems.Clear(); + + var gridTransform = _transformQuery.GetComponent(gridUid); + var worldAABB = _lookup.GetWorldAABB(gridUid, gridTransform); + _lookup.GetEntitiesIntersecting(gridTransform.MapID, worldAABB, _newPermitItems); + + foreach ((var ent, var comp) in _newPermitItems) + { + if (ent == gridUid) + continue; + + if (!_transformQuery.TryComp(ent, out var entXForm) || entXForm.GridUid != gridUid) + continue; + + if (comp.PermitOwnerMind != null && _mind.TryGetMind(user, out var userMindId, out _)) + { + if (userMindId != comp.PermitOwnerMind) + { + toDelete.Add(ent); + continue; + } + } + else if (user != comp.PermitOwner) + { + toDelete.Add(ent); + continue; + } + + // If the permit item somehow doesn't have permittable or it was set to false + if (!TryComp(ent, out var permittable) || !permittable.Permittable) + { + toDelete.Add(ent); + continue; + } + } + + foreach (var uid in toDelete) + { + Del(uid); + } + } + + private void SendConsoleRadioMessage(EntityUid console, string message) + { + if (!TryComp(console, out var consoleComp)) + return; + + _radio.SendRadioMessage(console, message, consoleComp.RadioChannel, console); + } + + private void SendPermitOwnerPdaMessage(EntityUid permitOwner, string header, string message) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var cartridgeComp)) + { + // Find the permit owner's PDA and send them a message + if (comp.PdaOwner != permitOwner) + continue; + + _cartridgeLoader.SendNotification(uid, header, message, cartridgeComp); + break; // PDA found, break + } + } + + private void UpdatePermitConsoles() + { + if (!TryComp(_sectorService.GetServiceEntity(), out SectorContrabandPermitsComponent? contrabandPermitNet)) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var console)) + { + var permitEntries = GetAllPermitEntryData(contrabandPermitNet); + var permitEntryArray = permitEntries.ToArray(); + + console.Entries = permitEntryArray; + Dirty(uid, console); + + UpdateUserInterface(uid, console); + } + } + + private static List GetAllPermitEntryData(SectorContrabandPermitsComponent contrabandPermitNet) + { + var permitRecords = contrabandPermitNet.Records; + + var data = new List(); + + foreach (var record in permitRecords) + { + var owner = record.Key; + var items = record.Value; + + data.Add(new ContrabandPermitConsoleEntry(owner, items)); + } + + return data; + } +} diff --git a/Content.Server/_Triad/Humanoid/ClientHumanoidViewerSystem.cs b/Content.Server/_Triad/Humanoid/ClientHumanoidViewerSystem.cs new file mode 100644 index 00000000000..aee2f9afc82 --- /dev/null +++ b/Content.Server/_Triad/Humanoid/ClientHumanoidViewerSystem.cs @@ -0,0 +1,153 @@ +using Content.Shared.GameTicking; +using Content.Shared._Triad.Humanoid; +using Content.Shared.Humanoid; +using Robust.Shared.Prototypes; +using Content.Shared.Roles; +using Content.Shared.Clothing; +using Content.Shared.Preferences.Loadouts; +using Content.Shared.Station; +using Content.Shared.Inventory; +using Content.Shared._Mono.Pvs; + +namespace Content.Server._Triad.Humanoid; + +public sealed partial class ClientHumanoidViewerSystem : EntitySystem +{ + [Dependency] private IPrototypeManager _prototypes = default!; + [Dependency] private IDependencyCollection _dependencyCollection = default!; + [Dependency] private SharedMapSystem _map = default!; + [Dependency] private MetaDataSystem _metaData = default!; + [Dependency] private InventorySystem _inventory = default!; + [Dependency] private SharedHumanoidAppearanceSystem _humanoid = default!; + [Dependency] private SharedStationSpawningSystem _stationSpawning = default!; + [Dependency] private SharedTransformSystem _transform = default!; + + private static readonly SlotFlags RemoveDummyClothingFlags = + SlotFlags.OUTERCLOTHING | SlotFlags.HEAD | SlotFlags.SUITSTORAGE | SlotFlags.MASK | SlotFlags.BACK | SlotFlags.EYES; + + public EntityUid? PausedMap { get; private set; } + + private EntityQuery _humanoidQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundRestart); + SubscribeLocalEvent(OnPlayerSpawnComplete); + + _humanoidQuery = GetEntityQuery(); + } + + private void OnRoundRestart(RoundRestartCleanupEvent _) + { + if (PausedMap == null || !Exists(PausedMap)) + return; + + Del(PausedMap.Value); + } + + private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent ev) + { + EnsurePausedMap(); + + if (PausedMap == null || !Exists(PausedMap)) + return; + + var mob = ev.Mob; + + EntityUid? viewerMob = null; + + var query = AllEntityQuery(); + while (query.MoveNext(out var uid, out var otherViewer)) + { + // Cryoing and respawning shouldn't make duplicates + if (otherViewer.Session == null) + continue; + + if (ev.Player.UserId != otherViewer.Session.UserId) + continue; + + // Same session and name? Don't make a new viewer mob + if (Name(uid) == ev.Profile.Name) + { + viewerMob = uid; + break; + } + + // Name different, but age and species is the same? Likely the same character, use as viewer + // There is a case where someone changes their character name, gender, and age but it doesn't matter too much + if (_humanoidQuery.TryComp(uid, out var humanoid)) + { + if (humanoid.Age == ev.Profile.Age && humanoid.Gender == ev.Profile.Gender) + { + viewerMob = uid; + break; + } + } + } + + if (viewerMob == null && _prototypes.TryIndex(ev.Profile.Species, out var speciesProto)) + { + viewerMob = Spawn(speciesProto.DollPrototype); + _humanoid.LoadProfile(viewerMob.Value, ev.Profile); // Copy humanoid appearance + _metaData.SetEntityName(viewerMob.Value, ev.Profile.Name); + + // Give the dummy a starting gear as well + if (ev.JobId != null && _prototypes.TryIndex(ev.JobId, out var jobPrototype)) + { + var jobLoadout = LoadoutSystem.GetJobPrototype(ev.JobId); + + if (_prototypes.TryIndex(jobLoadout, out var roleProto)) + { + ev.Profile.Loadouts.TryGetValue(jobLoadout, out var loadout); + + // Set to default if not present + if (loadout == null) + { + loadout = new RoleLoadout(jobLoadout); + loadout.SetDefault(ev.Profile, ev.Player, _prototypes); + loadout.EnsureValid(ev.Profile, ev.Player, _dependencyCollection); + } + + _stationSpawning.EquipRoleLoadout(viewerMob.Value, loadout, roleProto); + } + + if (jobPrototype.StartingGear != null) + _stationSpawning.EquipStartingGear(viewerMob.Value, jobPrototype.StartingGear, raiseEvent: false); + } + + // Unequip items that wouldn't make sense in a photograph of the dummy, like hardsuits and masks + var enumerator = _inventory.GetSlotEnumerator(viewerMob.Value, RemoveDummyClothingFlags); + while (enumerator.MoveNext(out var slot)) + { + if (slot.ContainedEntity is not { } item) + continue; + + QueueDel(item); + } + + _transform.SetParent(viewerMob.Value, PausedMap.Value); + + var viewerComp = EnsureComp(viewerMob.Value); + viewerComp.Session = ev.Player; + Dirty(viewerMob.Value, viewerComp); + + EnsureComp(viewerMob.Value); + } + + var humanoidView = EnsureComp(mob); + humanoidView.PvsView = viewerMob; + Dirty(mob, humanoidView); + } + + private void EnsurePausedMap() + { + if (PausedMap != null && Exists(PausedMap)) + return; + + var newmap = _map.CreateMap(); + _map.SetPaused(newmap, true); + PausedMap = newmap; + } +} diff --git a/Content.Server/_Triad/Shipyard/ShipyardGridSaveSystem.cs b/Content.Server/_Triad/Shipyard/ShipyardGridSaveSystem.cs index 80fe8b0756f..d990e4ca22a 100644 --- a/Content.Server/_Triad/Shipyard/ShipyardGridSaveSystem.cs +++ b/Content.Server/_Triad/Shipyard/ShipyardGridSaveSystem.cs @@ -1,17 +1,17 @@ using System.IO; using Content.Server.Construction.Components; using Content.Server.Spreader; -using Content.Server._HL.Shipyard; // HardLight -using Content.Shared._Common.Consent; // HardLight -using Content.Shared._HL.Shipyard; // HardLight +using Content.Server._HL.Shipyard; +using Content.Shared._Common.Consent; +using Content.Shared._HL.Shipyard; using Content.Shared._NF.Shipyard.Components; using Content.Shared._NF.Shipyard.Events; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking.Components; -using Content.Shared.Mind.Components; // HardLight -using Content.Shared.Wall; // WallMountComponent for preserving wall-mounted fixtures +using Content.Shared.Mind.Components; +using Content.Shared.Wall; using Robust.Shared.Containers; using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Systems; @@ -19,7 +19,7 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Player; -using Robust.Shared.Prototypes; // HardLight +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Markdown.Mapping; using YamlDotNet.Core; @@ -43,6 +43,7 @@ using Content.Server.GameTicking; using Content.Server.StationRecords.Components; using Content.Server.StationRecords.Systems; +using Content.Shared._Triad.ContrabandPermit; namespace Content.Server._Triad.Shipyard; @@ -60,7 +61,7 @@ public sealed partial class ShipyardGridSaveSystem : EntitySystem [Dependency] private SharedContainerSystem _containerSystem = default!; [Dependency] private EntityLookupSystem _lookup = default!; [Dependency] private SharedDeviceLinkSystem _deviceLink = default!; - [Dependency] private IPrototypeManager _prototypeManager = default!; // HardLight + [Dependency] private IPrototypeManager _prototypeManager = default!; [Dependency] private SharedTransformSystem _transform = default!; [Dependency] private StationSystem _station = default!; [Dependency] private ShuttleRecordsSystem _shuttleRecords = default!; @@ -532,8 +533,8 @@ private bool IsInvalidEntity(EntityUid uid) return false; if (HasComp(uid) || HasComp(uid)) return true; // do not save things with minds - if (HasComp(uid)) - return true; // no contra + if (HasComp(uid) && !HasComp(uid)) + return true; // No contra, but a permit will allow it if (_persistOnSaveQuery.HasComp(uid)) return false; // preserve stash root outright if (_gridQuery.HasComp(uid)) diff --git a/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.Consoles.cs b/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.Consoles.cs index 951467bb8b7..751305c1541 100644 --- a/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.Consoles.cs +++ b/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.Consoles.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Server._NF.Shipyard.Components; using Content.Server._NF.Station.Components; +using Content.Server._Triad.ContrabandPermit; using Content.Server._Triad.Shipyard; using Content.Server.Database; using Content.Server.Maps; @@ -34,9 +35,10 @@ namespace Content.Server._NF.Shipyard.Systems; public sealed partial class ShipyardSystem : SharedShipyardSystem { - [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; - [Dependency] private readonly ShuttleConsoleSystem _shuttleConsole = default!; - [Dependency] private readonly TriadTamperPolicyService _tamperPolicy = default!; + [Dependency] private ContrabandPermitSystem _contrabandPermit = default!; + [Dependency] private EntityWhitelistSystem _whitelist = default!; + [Dependency] private ShuttleConsoleSystem _shuttleConsole = default!; + [Dependency] private TriadTamperPolicyService _tamperPolicy = default!; public void OnSaveMessage(EntityUid uid, ShipyardConsoleComponent component, ShipyardConsoleSaveMessage args) { @@ -127,6 +129,9 @@ public void OnSaveMessage(EntityUid uid, ShipyardConsoleComponent component, Shi return; } + // Clear out any invalid permits (players trying to save their own permits on someone else's ship) + _contrabandPermit.ClearPermitItemsOnGrid(shuttleUid.Value, player); + // Attempt to save the ship if (!_shipyardGridSave.TrySaveShip(shuttleUid.Value, targetId, playerSession)) { @@ -301,16 +306,13 @@ public void OnLoadMessage(EntityUid uid, ShipyardConsoleComponent component, Shi deedHolderEntity: null); var shipYaml = authShip.ShipYamlString(); - // End Triad - - - // Triad: reuse the name resolved up front (above) so the audit row and the spawned ship + // Reuse the name resolved up front (above) so the audit row and the spawned ship // share one name instead of recomputing (which could diverge on the generated fallback). var name = loadShipName; // Attempt to load the shuttle from the in-message YAML only. - // Triad: F1 fix - removed the SourceFilePath disk-load fallback, which bypassed + // F1 fix - removed the SourceFilePath disk-load fallback, which bypassed // tamper protection by loading whatever path the client named under /UserData. // The YAML path above already runs through compatibility recovery; if it fails, // the load fails. SourceFilePath stays in scope only as audit-row / migration metadata. @@ -350,7 +352,7 @@ public void OnLoadMessage(EntityUid uid, ShipyardConsoleComponent component, Shi var appraisalCost = (int)MathF.Round((float)fullAppraisal * loadShipPrice); // Check if player has a bank account and session to charge them - // Triad: playerSession is captured earlier (above tamper-protection block) + // playerSession is captured earlier (above tamper-protection block) if (!TryComp(player, out var bankAccount)) { ConsolePopup(player, Loc.GetString("shipyard-console-no-bank")); @@ -371,7 +373,7 @@ public void OnLoadMessage(EntityUid uid, ShipyardConsoleComponent component, Shi ("ship", name), ("cost", appraisalCost))); // Add company information to the shuttle from the ID card or voucher - AddCompanyInformation(targetId, shuttleUid); // Triad, generic method for adding company info + AddCompanyInformation(targetId, shuttleUid); // generic method for adding company info var boughtEv = new ShipBoughtEvent(); RaiseLocalEvent(shuttleUid, boughtEv); @@ -510,6 +512,9 @@ public void OnLoadMessage(EntityUid uid, ShipyardConsoleComponent component, Shi _shipyardDirection.SendShipDirectionMessage(player, shuttleUid); + // Change permit info data to the character info of the player that loaded the ship + _contrabandPermit.InitializePermitItemsOnGrid(shuttleUid, player); + // Send radio messages and update UI SendPurchaseMessage(uid, player, name, component.ShipyardChannel, secret: false); if (component.SecretShipyardChannel is { } secretChannel) @@ -578,7 +583,7 @@ private void SendSaveMessage(EntityUid uid, string? player, string name, string } /// - /// Triad - Adds company information from a given id card or voucher onto a shuttle grid entity. + /// Adds company information from a given id card or voucher onto a shuttle grid entity. /// private void AddCompanyInformation(EntityUid idCard, EntityUid shuttleUid) { @@ -608,7 +613,7 @@ private void AddCompanyInformation(EntityUid idCard, EntityUid shuttleUid) } /// - /// Triad - Adds the to a shuttle grid and sets it to enabled + /// Adds the to a shuttle grid and sets it to enabled /// private void SetFtlLockEnabled(EntityUid shuttleUid) { @@ -621,7 +626,7 @@ private void SetFtlLockEnabled(EntityUid shuttleUid) } /// - /// Triad - Adds new access levels to a shuttle deed from a + /// Adds new access levels to a shuttle deed from a /// private void AddNewShuttleDeedAccessLevels(EntityUid targetId, ShipyardConsoleComponent console) { diff --git a/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.cs b/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.cs index 15aac46a4a8..52f1d7b3070 100644 --- a/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.cs +++ b/Content.Server/_Triad/Shipyard/ShipyardSystem.Triad.cs @@ -154,7 +154,7 @@ private bool TryFinalizeLoadedShuttle(EntityUid consoleUid, EntityUid grid, [Not /// private void TryResetUseDelays(EntityUid shuttleGrid) { - var useDelayQuery = EntityManager.EntityQueryEnumerator(); + var useDelayQuery = EntityQueryEnumerator(); while (useDelayQuery.MoveNext(out var uid, out var comp, out var xform)) { diff --git a/Content.Shared/Access/Components/IdCardConsoleComponent.cs b/Content.Shared/Access/Components/IdCardConsoleComponent.cs index 1fca9e7990f..54c8298d3ef 100644 --- a/Content.Shared/Access/Components/IdCardConsoleComponent.cs +++ b/Content.Shared/Access/Components/IdCardConsoleComponent.cs @@ -80,6 +80,7 @@ public WriteToTargetIdMessage(string fullName, string jobTitle, List public static readonly CVarDef YearOffset = - CVarDef.Create("game.current_year_offset", 802, CVar.SERVERONLY); + CVarDef.Create("game.current_year_offset", 802, CVar.REPLICATED); // Triad - replicated cvar /* * Traits diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitChipComponent.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitChipComponent.cs new file mode 100644 index 00000000000..c452bfb2d80 --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitChipComponent.cs @@ -0,0 +1,49 @@ +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared._Triad.ContrabandPermit; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ContrabandPermitChipComponent : Component +{ + /// + /// The net ent of the scanned permit chip. + /// + [DataField, AutoNetworkedField] + public NetEntity? ScannedItem; + + /// + /// The net ent of the permit carrier/owner. + /// + [DataField, AutoNetworkedField] + public NetEntity? ScannedPermitCarrier; + + [DataField, AutoNetworkedField] + public EntityWhitelist? PermitCarrierWhitelist; + + [DataField, AutoNetworkedField] + public EntityWhitelist? PermitCarrierBlacklist; + + [DataField, AutoNetworkedField] + public TimeSpan ScanIdDelay = TimeSpan.FromSeconds(5); + + [DataField, AutoNetworkedField] + public SoundSpecifier? ScanSound = + new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg") + { + Params = AudioParams.Default.WithVolume(-2f) + }; + + [DataField, AutoNetworkedField] + public SoundSpecifier? ClearSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); + + [DataField, AutoNetworkedField] + public DamageSpecifier PrickDamage = new(); +} + +[Serializable, NetSerializable] +public sealed partial class ContrabandPermitChipScanIdentityDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitConsoleComponent.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitConsoleComponent.cs new file mode 100644 index 00000000000..3426f2cf8ba --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitConsoleComponent.cs @@ -0,0 +1,86 @@ +using Content.Shared.Radio; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared._Triad.ContrabandPermit; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ContrabandPermitConsoleComponent : Component +{ + /// + /// Entities in this whitelist will be able to grant permits to people. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? GrantPermitWhitelist; + + /// + /// Entities in this blacklist will not be able to grant permits to people. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? GrantPermitBlacklist; + + [DataField, AutoNetworkedField] + public string ChipSlotContainerId = "chip_slot"; + + [DataField, AutoNetworkedField] + public string CurrentPermitReason = string.Empty; + + /// + /// The current selected permit + /// + [ViewVariables, AutoNetworkedField] + public PermitEntryFocusData? FocusedEntry; + + [DataField, AutoNetworkedField] + public SoundSpecifier ErrorSound = + new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg"); + + [DataField, AutoNetworkedField] + public SoundSpecifier ConfirmSound = + new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg"); + + /// + /// Sound to play when fax printing a permit chip. + /// + [DataField] + public SoundSpecifier ChipPrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg"); + + [DataField] + public EntProtoId ChipPrototype = "PermitChip"; + + /// + /// The comms channel that announces a permit grant or revoke. + /// + [DataField] + public ProtoId RadioChannel = "Nfsd"; // TDF channel + + /// + /// Timeout for printing a permit chip from the console. + /// + [DataField, AutoNetworkedField] + public TimeSpan PrintChipTimeout = TimeSpan.FromSeconds(10); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] + public TimeSpan PrintChipTimeoutEnd; + + [DataField, AutoNetworkedField] + public ContrabandPermitConsoleEntry[] Entries = Array.Empty(); +} + +[Serializable, NetSerializable] +public struct PermitEntryFocusData(NetEntity permitOwner, NetEntity? selectedItem = null) +{ + /// + /// The permit owner's net entity that the console is currently focused on + /// + public NetEntity PermitOwner = permitOwner; + + /// + /// Net entity of the selected permit item. Can be null. + /// + public NetEntity? SelectedItem = selectedItem; +} diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitConsoleUi.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitConsoleUi.cs new file mode 100644 index 00000000000..f75005c62f2 --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitConsoleUi.cs @@ -0,0 +1,57 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Triad.ContrabandPermit; + +[Serializable, NetSerializable] +public enum ContrabandPermitConsoleUi : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class ContrabandPermitConsoleBuiState(NetEntity? insertedChip, string? dateTime, ContrabandPermitConsoleEntry[] entries, PermitEntryFocusData? focusData) : BoundUserInterfaceState +{ + public NetEntity? InsertedChip = insertedChip; + public string? DateTime = dateTime; + public ContrabandPermitConsoleEntry[] Entries = entries; + public PermitEntryFocusData? FocusData = focusData; +} + +[Serializable, NetSerializable] +public struct ContrabandPermitConsoleEntry(NetEntity owner, List items) +{ + /// + /// Owner of the permit(s) that the UI will read from + /// + public NetEntity Owner = owner; + + /// + /// Items that the owner has valid permits for. + /// + public List Items = items; +} + +[Serializable, NetSerializable] +public sealed class ContrabandPermitConsoleReasonUpdatedMessage(string reason) : BoundUserInterfaceMessage +{ + public string Reason { get; } = reason; +} + +[Serializable, NetSerializable] +public sealed class ContrabandPermitConsoleGrantButtonPressedMessage() : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class ContrabandPermitConsoleRevokeButtonPressedMessage(string reason) : BoundUserInterfaceMessage +{ + public string Reason = reason; +} + +[Serializable, NetSerializable] +public sealed class ContrabandPermitConsolePrintButtonPressedMessage() : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class ContrabandPermitConsoleFocusChangeMessage(NetEntity? focusedOwner, NetEntity? focusedItem) : BoundUserInterfaceMessage +{ + public NetEntity? FocusedOwner = focusedOwner; + public NetEntity? FocusedItem = focusedItem; +} diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitGranterComponent.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitGranterComponent.cs new file mode 100644 index 00000000000..571c886069d --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitGranterComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Triad.ContrabandPermit; + +/// +/// Entities with this component will be able to grant permits to players. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ContrabandPermitGranterComponent : Component; diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitItemComponent.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitItemComponent.cs new file mode 100644 index 00000000000..31c067b043d --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitItemComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Triad.ContrabandPermit; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ContrabandPermitItemComponent : Component +{ + /// + /// The name of the permit owner. + /// + [DataField, AutoNetworkedField] + public string PermitOwnerName = string.Empty; + + [DataField, AutoNetworkedField] + public string PermitReason = string.Empty; + + /// + /// The UID of the permit owner. + /// + [DataField, AutoNetworkedField] + public EntityUid? PermitOwner; + + /// + /// The mind of the permit owner. Used for checking if a permitted item should stay or be seized on a saved ship. + /// + [DataField] + public EntityUid? PermitOwnerMind; + + /// + /// Flavor RP date of whenever the contraband permit was granted. + /// + [DataField, AutoNetworkedField] + public string DateGranted = string.Empty; +} diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitOwnerBlacklistComponent.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitOwnerBlacklistComponent.cs new file mode 100644 index 00000000000..cc06261d33e --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermitOwnerBlacklistComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Triad.ContrabandPermit; + +/// +/// Entities with this component will not be able to own permits. Useful for hostile invaders and TDF enforcers. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ContrabandPermitOwnerBlacklistComponent : Component; diff --git a/Content.Shared/_Triad/ContrabandPermit/ContrabandPermittableComponent.cs b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermittableComponent.cs new file mode 100644 index 00000000000..ccd1720954c --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/ContrabandPermittableComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Triad.ContrabandPermit; + +/// +/// Items with this component can be permitted for use, which allows them to be saved on ships. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ContrabandPermittableComponent : Component +{ + /// + /// This purely exists for parenting + /// + [DataField, AutoNetworkedField] + public bool Permittable = true; + + [DataField, AutoNetworkedField] + public LocId ExamineText = "contraband-permittable-examine-default"; +} diff --git a/Content.Shared/_Triad/ContrabandPermit/SectorContrabandPermitsComponent.cs b/Content.Shared/_Triad/ContrabandPermit/SectorContrabandPermitsComponent.cs new file mode 100644 index 00000000000..75ffb3ee1fe --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/SectorContrabandPermitsComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Shared._Triad.ContrabandPermit; + +[RegisterComponent] +public sealed partial class SectorContrabandPermitsComponent : Component +{ + /// + /// Stores all permit entries. + /// Key is the permit owner, the list is the permitted items. + /// + [DataField] + public Dictionary> Records = new(); +} diff --git a/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.Console.cs b/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.Console.cs new file mode 100644 index 00000000000..a239a7155b3 --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.Console.cs @@ -0,0 +1,307 @@ +using Content.Shared._DV.CCVars; +using Content.Shared.Access.Systems; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Coordinates; +using Content.Shared.Mind; +using Content.Shared.Popups; +using Content.Shared.Whitelist; +using Robust.Shared.Configuration; +using Robust.Shared.Containers; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Shared._Triad.ContrabandPermit; + +public abstract partial class SharedContrabandPermitSystem : EntitySystem +{ + [Dependency] private SharedUserInterfaceSystem _userInterface = default!; + [Dependency] private ItemSlotsSystem _itemSlot = default!; + [Dependency] private INetManager _net = default!; + [Dependency] private AccessReaderSystem _accessReader = default!; + [Dependency] private IGameTiming _timing = default!; + [Dependency] private SharedMindSystem _mind = default!; + [Dependency] private EntityWhitelistSystem _whitelist = default!; + [Dependency] private IConfigurationManager _config = default!; + + private static readonly int MinimumPermitReasonLength = 5; + private static readonly int MinimumRevokeReasonLength = 5; + + private static DateTime _serverDate; + + private void InitializeConsole() + { + SubscribeLocalEvent(OnConsoleMapInit); + SubscribeLocalEvent(OnConsoleContainerUpdated); + SubscribeLocalEvent(OnConsoleContainerUpdated); + + SubscribeLocalEvent(OnConsoleReasonChanged); + SubscribeLocalEvent(OnConsoleGrantPressed); + SubscribeLocalEvent(OnConsoleRevokePressed); + SubscribeLocalEvent(OnConsolePrintPressed); + SubscribeLocalEvent(OnConsoleFocusChanged); + + Subs.CVar(_config, DCCVars.YearOffset, value => _serverDate = DateTime.Today.AddYears(value), true); + } + + private void OnConsoleMapInit(Entity ent, ref MapInitEvent args) + { + UpdateUserInterface(ent.Owner, ent.Comp); + } + + private void OnConsoleContainerUpdated(EntityUid uid, ContrabandPermitConsoleComponent component, EntityEventArgs args) + { + UpdateUserInterface(uid, component); + } + + protected void UpdateUserInterface(EntityUid uid, ContrabandPermitConsoleComponent component) + { + if (!component.Initialized) + return; + + if (!_itemSlot.TryGetSlot(uid, component.ChipSlotContainerId, out var itemSlot)) + return; + + ContrabandPermitConsoleBuiState? newState = null; + + var dateString = _serverDate.ToString("dd MMMM yyyy"); + + if (itemSlot.Item is { } targetChip) + { + var chipNetEnt = GetNetEntity(targetChip); + newState = new ContrabandPermitConsoleBuiState(chipNetEnt, dateString, component.Entries, component.FocusedEntry); + } + else + { + newState = new ContrabandPermitConsoleBuiState(null, null, component.Entries, component.FocusedEntry); + } + + _userInterface.SetUiState(uid, ContrabandPermitConsoleUi.Key, newState); + } + + private void OnConsoleReasonChanged(Entity ent, ref ContrabandPermitConsoleReasonUpdatedMessage args) + { + var finalReason = args.Reason.Trim(); + SetConsolePermitReason(ent, finalReason); + } + + private void OnConsoleGrantPressed(Entity ent, ref ContrabandPermitConsoleGrantButtonPressedMessage args) + { + if (!_itemSlot.TryGetSlot(ent.Owner, ent.Comp.ChipSlotContainerId, out var itemSlot)) + return; + + if (itemSlot.Item is not { } insertedChip) + return; + + if (!TryComp(insertedChip, out var chipComp)) + return; + + var user = args.Actor; + + if (!_whitelist.CheckBoth(user, ent.Comp.GrantPermitBlacklist, ent.Comp.GrantPermitWhitelist)) + { + PlayDenySound(ent, user); + ConsolePopup(user, Loc.GetString("contraband-permit-console-popup-error-access-denied"), PopupType.SmallCaution); + return; + } + + var scannedItem = GetEntity(chipComp.ScannedItem); + var scannedPermitCarrier = GetEntity(chipComp.ScannedPermitCarrier); + + if (scannedItem == null || scannedPermitCarrier == null) + { + PlayDenySound(ent, user); + ConsolePopup(user, Loc.GetString("contraband-permit-console-popup-error-no-data"), PopupType.SmallCaution); + return; + } + + if (HasComp(scannedItem)) + { + PlayDenySound(ent, user); + ConsolePopup(user, Loc.GetString("contraband-permit-console-popup-error-already-permit"), PopupType.SmallCaution); + return; + } + + if (!TryComp(scannedItem, out var permittable) || !permittable.Permittable) + return; + + var permitReason = ent.Comp.CurrentPermitReason; + + if (permitReason == string.Empty || permitReason.Length < MinimumPermitReasonLength) + { + PlayDenySound(ent, user); + ConsolePopup(user, Loc.GetString("contraband-permit-console-popup-reason-too-short"), PopupType.SmallCaution); + return; + } + + var dateString = _serverDate.ToString("dd MMMM yyyy"); + + var permit = EnsureComp(scannedItem.Value); + permit.PermitReason = permitReason; + permit.DateGranted = dateString; + permit.PermitOwnerName = Name(scannedPermitCarrier.Value); + permit.PermitOwner = scannedPermitCarrier; + + if (TryComp(scannedPermitCarrier, out var actor) + && _mind.TryGetMind(actor.PlayerSession, out var mindId, out _)) + { + permit.PermitOwnerMind = mindId; + } + + Dirty(scannedItem.Value, permit); + + PlayConfirmSound(ent, user); + ConsolePopup(user, + Loc.GetString("contraband-permit-console-popup-success", ("item", scannedItem), ("owner", permit.PermitOwnerName)), + PopupType.Medium); + + if (_itemSlot.TryEject(ent.Owner, ent.Comp.ChipSlotContainerId, user, out var ejected)) + PredictedQueueDel(ejected); + + var ev = new ContrabandPermitGrantedEvent(scannedItem.Value, scannedPermitCarrier.Value, ent.Owner, user, ent.Comp.CurrentPermitReason); + RaiseLocalEvent(scannedItem.Value, ev, true); + + SetConsolePermitReason(ent, string.Empty); + } + + private void OnConsoleRevokePressed(Entity ent, ref ContrabandPermitConsoleRevokeButtonPressedMessage args) + { + var user = args.Actor; + + if (!_accessReader.IsAllowed(ent.Owner, user)) + { + PlayDenySound(ent, args.Actor); + ConsolePopup(args.Actor, Loc.GetString("contraband-permit-console-popup-error-access-denied"), PopupType.SmallCaution); + return; + } + + var reason = args.Reason.Trim(); + + if (reason.Length < MinimumRevokeReasonLength) + { + PlayDenySound(ent, args.Actor); + ConsolePopup(args.Actor, Loc.GetString("contraband-permit-console-popup-revoke-reason-too-short"), PopupType.SmallCaution); + return; + } + + if (ent.Comp.FocusedEntry == null || ent.Comp.FocusedEntry.Value.SelectedItem is not { } selectedNetItem) + { + PlayDenySound(ent, args.Actor); + ConsolePopup(args.Actor, Loc.GetString("contraband-permit-console-popup-revoke-no-focus"), PopupType.SmallCaution); + return; + } + + var selectedItem = GetEntity(selectedNetItem); + + if (!TryComp(selectedItem, out var permitInfo) || permitInfo.PermitOwner == null) + return; + + var permitOwner = permitInfo.PermitOwner.Value; + var permitOwnerName = permitInfo.PermitOwnerName; + + RemComp(selectedItem, permitInfo); + + PlayConfirmSound(ent, user); + ConsolePopup(user, + Loc.GetString("contraband-permit-console-popup-success-revoke", ("item", selectedItem), ("owner", permitOwnerName)), + PopupType.Medium); + + // Update the focus + if (ent.Comp.FocusedEntry is { } focusedEntry) + { + var lastEntry = false; + + foreach (var entry in ent.Comp.Entries) + { + if (entry.Owner != focusedEntry.PermitOwner) + continue; + + if (entry.Items.Count <= 1) + lastEntry = true; + + break; + } + + // If there's more than one entry left by this owner + if (!lastEntry) + { + var focusData = new PermitEntryFocusData(focusedEntry.PermitOwner, null); + UpdateFocusData(ent, focusData); + } + else + { + UpdateFocusData(ent, null); + } + } + + var ev = new ContrabandPermitRevokedEvent(selectedItem, permitOwner, ent.Owner, user, reason); + RaiseLocalEvent(selectedItem, ev, true); + } + + private void OnConsolePrintPressed(Entity ent, ref ContrabandPermitConsolePrintButtonPressedMessage args) + { + var curTime = _timing.CurTime; + var user = args.Actor; + + if (curTime < ent.Comp.PrintChipTimeoutEnd) + { + var timeRemaining = (ent.Comp.PrintChipTimeoutEnd - curTime).Seconds; + _popup.PopupClient(Loc.GetString("contraband-permit-console-print-chip-cooldown", ("time", timeRemaining)), user, PopupType.MediumCaution); + return; + } + + PredictedSpawnAtPosition(ent.Comp.ChipPrototype, ent.Owner.ToCoordinates()); + _audio.PlayPredicted(ent.Comp.ChipPrintSound, ent.Owner, user); + + ent.Comp.PrintChipTimeoutEnd = curTime + ent.Comp.PrintChipTimeout; + Dirty(ent); + } + + private void OnConsoleFocusChanged(Entity ent, ref ContrabandPermitConsoleFocusChangeMessage args) + { + if (args.FocusedOwner == null) + { + UpdateFocusData(ent, null); + } + else + { + var focusData = new PermitEntryFocusData(args.FocusedOwner.Value, args.FocusedItem); + UpdateFocusData(ent, focusData); + } + } + + private void UpdateFocusData(Entity ent, PermitEntryFocusData? entry) + { + ent.Comp.FocusedEntry = entry; + Dirty(ent); + UpdateUserInterface(ent.Owner, ent.Comp); + } + + private void PlayConfirmSound(Entity ent, EntityUid? user) + { + _audio.PlayPredicted(ent.Comp.ConfirmSound, ent.Owner, user); + } + + private void PlayDenySound(Entity ent, EntityUid? user) + { + _audio.PlayPredicted(ent.Comp.ErrorSound, ent.Owner, user); + } + + private void ConsolePopup(EntityUid actor, string text, PopupType type = PopupType.Small) + { + if (_net.IsClient) + return; + + if (actor is { Valid: true } player) + _popup.PopupEntity(text, player, type); + } + + private void SetConsolePermitReason(Entity ent, string reason) + { + ent.Comp.CurrentPermitReason = reason; + Dirty(ent); + } + + public record struct ContrabandPermitGrantedEvent(EntityUid PermitEntity, EntityUid PermitOwner, EntityUid? Console, EntityUid? PermitGranter, string Reason); + public record struct ContrabandPermitRevokedEvent(EntityUid PermitEntity, EntityUid PermitOwner, EntityUid? Console, EntityUid? PermitRevoker, string Reason); +} diff --git a/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.PermitChip.cs b/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.PermitChip.cs new file mode 100644 index 00000000000..78f5676c42c --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.PermitChip.cs @@ -0,0 +1,202 @@ +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Forensics.Components; +using Content.Shared.Humanoid; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Utility; + +namespace Content.Shared._Triad.ContrabandPermit; + +public abstract partial class SharedContrabandPermitSystem : EntitySystem +{ + [Dependency] private SharedAudioSystem _audio = default!; + [Dependency] private DamageableSystem _damageable = default!; + [Dependency] private SharedDoAfterSystem _doAfter = default!; + [Dependency] private SharedPopupSystem _popup = default!; + + private void InitializePermitChip() + { + SubscribeLocalEvent(OnPermitChipActivate); + SubscribeLocalEvent(OnPermitChipScanDoAfter); + SubscribeLocalEvent(OnPermitChipInteract); + SubscribeLocalEvent(OnPermitChipExamine); + SubscribeLocalEvent>(OnPermitChipGetVerbs); + SubscribeLocalEvent>(OnPermitChipGetUtilityVerbs); + } + + private void OnPermitChipActivate(Entity ent, ref ActivateInWorldEvent args) + { + if (args.Handled || !args.Complex) + return; + + // Need to scan the item first + if (ent.Comp.ScannedPermitCarrier != null || ent.Comp.ScannedItem == null) + return; + + var user = args.User; + + // Only mobs that are humanoid! + if (!HasComp(user)) + return; + + if (!_whitelist.CheckBoth(user, ent.Comp.PermitCarrierBlacklist, ent.Comp.PermitCarrierWhitelist)) + { + _popup.PopupClient(Loc.GetString("contraband-permit-chip-scan-error"), user, user); + return; + } + + var ev = new ContrabandPermitChipScanIdentityDoAfterEvent(); + var doAfter = new DoAfterArgs(EntityManager, user, ent.Comp.ScanIdDelay, ev, ent.Owner, user) + { + BreakOnMove = true, + CancelDuplicate = true, + DuplicateCondition = DuplicateConditions.SameEvent, + NeedHand = true, + BreakOnHandChange = true, + }; + + if (_doAfter.TryStartDoAfter(doAfter)) + { + _popup.PopupClient(Loc.GetString("contraband-permit-chip-scan-id-start"), user, user); + args.Handled = true; + } + } + + private void OnPermitChipScanDoAfter(Entity ent, ref ContrabandPermitChipScanIdentityDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + var user = args.User; + + // Your finger gets pricked if you're not a robot + if (HasComp(user)) + { + _damageable.TryChangeDamage(user, ent.Comp.PrickDamage, true, false); + _popup.PopupClient(Loc.GetString("contraband-permit-chip-scan-id-dna-end"), user, user); + } + else + { + _popup.PopupClient(Loc.GetString("contraband-permit-chip-scan-id-no-dna-end"), user, user); + } + + ent.Comp.ScannedPermitCarrier = GetNetEntity(user); + Dirty(ent); + + _audio.PlayPredicted(ent.Comp.ScanSound, ent.Owner, user); + args.Handled = true; + } + + private void OnPermitChipInteract(Entity ent, ref AfterInteractEvent args) + { + if (args.Handled) + return; + + if (!args.CanReach || args.Target is not { Valid: true } target) + return; + + if (TryScan(ent, target, args.User)) + args.Handled = true; + } + + private bool TryScan(Entity ent, EntityUid target, EntityUid actor) + { + if (HasComp(target)) + { + _popup.PopupClient(Loc.GetString("contraband-permit-chip-failure"), actor, actor, PopupType.SmallCaution); + return false; + } + + if (!TryComp(target, out var permittable) || !permittable.Permittable) + return false; + + ent.Comp.ScannedItem = GetNetEntity(target); + Dirty(ent); + + _audio.PlayPredicted(ent.Comp.ScanSound, target, actor); + _popup.PopupClient(Loc.GetString("contraband-permit-chip-success", ("item", Identity.Entity(target, EntityManager))), actor, actor, PopupType.Medium); + return true; + } + + private void OnPermitChipExamine(Entity ent, ref ExaminedEvent args) + { + if (ent.Comp.ScannedItem is { } item) + { + var fromNetEnt = GetEntity(item); + args.PushMarkup(Loc.GetString("contraband-permit-chip-examine-signature", ("item", Identity.Entity(fromNetEnt, EntityManager)))); + } + else + { + args.PushMarkup(Loc.GetString("contraband-permit-chip-examine-help")); + } + + if (ent.Comp.ScannedPermitCarrier is { } carrier) + { + var fromNetEnt = GetEntity(carrier); + args.PushMarkup(Loc.GetString("contraband-permit-chip-examine-owner", ("owner", Identity.Entity(fromNetEnt, EntityManager))), -1); + } + else if (ent.Comp.ScannedItem != null) + { + args.PushMarkup(Loc.GetString("contraband-permit-chip-examine-help-id"), -1); + } + } + + private void OnPermitChipGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var user = args.User; + + if (ent.Comp.ScannedItem != null || ent.Comp.ScannedPermitCarrier != null) + { + Verb verb = new() + { + Act = () => ClearScannedItem(ent, user), + Text = Loc.GetString("contraband-permit-chip-clear-verb"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/delete.svg.192dpi.png")), + Priority = -3, + DoContactInteraction = true + }; + args.Verbs.Add(verb); + } + } + + private void OnPermitChipGetUtilityVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var user = args.User; + var item = args.Target; + + if (!TryComp(item, out var permittable) || !permittable.Permittable) + return; + + var verb = new UtilityVerb() + { + Act = () => TryScan(ent, item, user), + IconEntity = GetNetEntity(ent.Owner), + Text = Loc.GetString("contraband-permit-scan-verb-text"), + Message = Loc.GetString("contraband-permit-scan-verb-message"), + DoContactInteraction = true + }; + + args.Verbs.Add(verb); + } + + private void ClearScannedItem(Entity ent, EntityUid user) + { + ent.Comp.ScannedItem = null; + ent.Comp.ScannedPermitCarrier = null; + Dirty(ent); + + _audio.PlayPredicted(ent.Comp.ClearSound, ent.Owner, user); + _popup.PopupClient(Loc.GetString("contraband-permit-chip-cleared"), user, user); + } +} diff --git a/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.cs b/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.cs new file mode 100644 index 00000000000..213076643cd --- /dev/null +++ b/Content.Shared/_Triad/ContrabandPermit/SharedContrabandPermitSystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Examine; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Shared._Triad.ContrabandPermit; + +public abstract partial class SharedContrabandPermitSystem : EntitySystem +{ + [Dependency] private ExamineSystemShared _examine = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnPermittableItemDetailedExamine); + + InitializeConsole(); + InitializePermitChip(); + } + + private void OnPermittableItemDetailedExamine(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract) + return; + + if (!ent.Comp.Permittable) + return; + + var defaultText = Loc.GetString(ent.Comp.ExamineText); + + var msg = new FormattedMessage(); + + if (!TryComp(ent.Owner, out var permit)) + { + msg.AddMarkupOrThrow(defaultText); + } + else if (permit.PermitOwner != null) + { + var permitMsg = Loc.GetString("contraband-permittable-examine-permit-format", ("name", permit.PermitOwnerName), ("date", permit.DateGranted)); + msg.AddMarkupOrThrow(permitMsg); + } + + _examine.AddDetailedExamineVerb(args, + ent.Comp, + msg, + Loc.GetString("contraband-permittable-examine-verb-text"), + "/Textures/_Triad/Interface/VerbIcons/savecontraband.svg.192dpi.png", + Loc.GetString("contraband-permittable-examine-verb-message")); + } +} diff --git a/Content.Shared/_Triad/Humanoid/HumanoidViewComponent.cs b/Content.Shared/_Triad/Humanoid/HumanoidViewComponent.cs new file mode 100644 index 00000000000..1e43ca32674 --- /dev/null +++ b/Content.Shared/_Triad/Humanoid/HumanoidViewComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Triad.Humanoid; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class HumanoidViewComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid? PvsView; +} diff --git a/Content.Shared/_Triad/Humanoid/HumanoidViewerEntityComponent.cs b/Content.Shared/_Triad/Humanoid/HumanoidViewerEntityComponent.cs new file mode 100644 index 00000000000..a1c12bf4aa7 --- /dev/null +++ b/Content.Shared/_Triad/Humanoid/HumanoidViewerEntityComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Player; + +namespace Content.Shared._Triad.Humanoid; + +/// +/// Used so clients can see humanoid appearance data from players across the map, such as for photographs. +/// Probably a hacky way of doing this. +/// Viewer entities are stored on an empty map. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class HumanoidViewerEntityComponent : Component +{ + [ViewVariables] + public ICommonSession? Session; +} diff --git a/Content.Shared/_Triad/Shipyard/Save/Contraband/SavingContrabandSystem.cs b/Content.Shared/_Triad/Shipyard/Save/Contraband/SavingContrabandSystem.cs index 95d621ca11f..c17da3195aa 100644 --- a/Content.Shared/_Triad/Shipyard/Save/Contraband/SavingContrabandSystem.cs +++ b/Content.Shared/_Triad/Shipyard/Save/Contraband/SavingContrabandSystem.cs @@ -4,9 +4,9 @@ namespace Content.Shared._Triad.Shipyard.Save.Contraband; -public sealed class SavingContrabandSystem : EntitySystem +public sealed partial class SavingContrabandSystem : EntitySystem { - [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private ExamineSystemShared _examine = default!; public override void Initialize() { diff --git a/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permit-chip.ftl b/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permit-chip.ftl new file mode 100644 index 00000000000..f3c28228373 --- /dev/null +++ b/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permit-chip.ftl @@ -0,0 +1,17 @@ +contraband-permit-chip-failure = Could not scan item. Item already has a valid contraband permit. +contraband-permit-chip-success = Successfully scanned {$item}! + +contraband-permit-chip-examine-help = [bold][color=green]Tap the chip onto contraband to scan it.[/color][/bold] +contraband-permit-chip-examine-help-id = [bold][color=green]Now, interact with the item to scan your identity.[/color][/bold] +contraband-permit-chip-examine-signature = Scanned item: {$item} +contraband-permit-chip-examine-owner = Scanned owner: {$owner} + +contraband-permit-chip-clear-verb = Clear data +contraband-permit-chip-cleared = Cleared data +contraband-permit-scan-verb-text = Scan +contraband-permit-scan-verb-message = Perform a chip scan + +contraband-permit-chip-scan-id-start = You put your finger on the chip... +contraband-permit-chip-scan-id-dna-end = A small needle pricked your finger! Ow! +contraband-permit-chip-scan-id-no-dna-end = A small blue glow shined over your finger as the chip scanned you. +contraband-permit-chip-scan-error = Error. Cannot scan identity. diff --git a/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permit-console.ftl b/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permit-console.ftl new file mode 100644 index 00000000000..3f5e8e4144b --- /dev/null +++ b/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permit-console.ftl @@ -0,0 +1,57 @@ +contraband-permit-console-window-title = TDF Contraband Permit Manager +contraband-permit-station-name = [color=white][font size=14]{$stationName}[/font][/color] +contraband-permit-unknown-location = Unknown location +contraband-permit-no-permits = [font size=16][color=white]No active permits detected[/font] + +contraband-permit-console-item-slot-name = Chip + +contraband-permit-console-pda-message-header = TDF Contraband Permit Network +contraband-permit-console-pda-message-permit-granted = Your permit for {$item} has been granted for reason "{$reason}". Please note that this can be revoked at any time. +contraband-permit-console-pda-message-permit-revoked = Your permit for {$item} has been revoked for reason "{$reason}". + +contraband-permit-console-radio-message-permit-granted = {$user} has granted a contraband permit for {$item} for "{$reason}" to {$owner}. +contraband-permit-console-radio-message-permit-revoked = {$user} has revoked {$owner}'s contraband permit for {$item} for "{$reason}". + +contraband-permit-console-popup-success = Permit for {$item} granted for {$owner} successfully! Ejecting and destroying chip. +contraband-permit-console-popup-success-revoke = Permit for {$item} revoked for {$owner} successfully. +contraband-permit-console-popup-error-access-denied = Access denied. Alarm sounded. +contraband-permit-console-popup-reason-too-short = Enter a valid permit reason +contraband-permit-console-popup-revoke-reason-too-short = Enter a valid revoke reason +contraband-permit-console-popup-revoke-no-focus = No permit entry selected +contraband-permit-console-popup-error-no-data = Chip lacking data, needs both permit owner identity and permit item scanned. +contraband-permit-console-popup-error-already-permit = This item already has a valid permit. + +contraband-permit-console-search-bar-placeholder = Input text and press "Enter" +contraband-permit-console-print-chip = Print Permit Chip +contraband-permit-console-print-chip-cooldown = Printing on cooldown. Time remaining: {$time} seconds. + +contraband-permit-console-window-permit-owner-entries-placeholder = Permits on file: N/A +contraband-permit-console-window-permit-owner-entries = Permit(s) on file: {$number} + +contraband-permit-console-window-permit-entries-list = Active permits: +contraband-permit-console-window-permit-entries-select-entry = Please select entry + +contraband-permit-console-window-permit-grant-no-item = No permit chip inserted into console +contraband-permit-console-window-permit-grant-no-access = Unauthorized access + +contraband-permit-console-window-permit-grant = Grant +contraband-permit-console-window-permit-eject = Eject +contraband-permit-console-window-permit-revoke = Revoke permit +contraband-permit-console-window-permit-revoke-confirmation = Are you sure? + +contraband-permit-console-window-label-permit-tab-reason = Reason for permit: "{$reason}" +contraband-permit-console-window-label-permit-tab-revoke-reason-placeholder = Revoke Reason Here + +contraband-permit-console-window-label-grant-tab-item-name = Item Name: {$name} +contraband-permit-console-window-label-grant-tab-date = Date Granted: {$date} +contraband-permit-console-window-label-grant-tab-reason = Reason: +contraband-permit-console-window-label-grant-tab-reason-placeholder = Reason Here + +contraband-permit-console-window-label-grant-tab-owner-name = Name: {$name} +contraband-permit-console-window-label-grant-tab-owner-species = Species: {$species} +contraband-permit-console-window-label-grant-tab-owner-age = Age: {$age} +contraband-permit-console-window-label-grant-tab-owner-gender = Gender: {$gender} +contraband-permit-console-window-label-grant-tab-owner-eye-color = Eye Color: {$color} + +contraband-permit-console-window-flavor-left = ⚠ Class 3 contraband permits are prohibited +contraband-permit-console-window-flavor-right = v1.9 ⛊[T.D.F.]⛊ diff --git a/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permittable-component.ftl b/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permittable-component.ftl new file mode 100644 index 00000000000..1bf0ff10c8f --- /dev/null +++ b/Resources/Locale/en-US/_Triad/contraband-permit/contraband-permittable-component.ftl @@ -0,0 +1,10 @@ +contraband-permittable-examine-default = [color=yellow]This item is considered Class 2 Contraband, and is illegal for civilians to possess without a permit.[/color] +contraband-permittable-examine-no-save = [color=yellow]This item is considered Class 2 Contraband, and is illegal for civilians to possess without a permit. It will be seized on ship storage.[/color] +contraband-permittable-examine-tdf = [color=yellow]This item is considered Class 2A Contraband. Property of the TDF faction. Civilian use is restricted without a permit.[/color] + +contraband-permittable-examine-verb-text = Contraband Permit Status +contraband-permittable-examine-verb-message = Check if this item has a permit. + +contraband-permittable-examine-permit-format = [color=green]The permit owner below has been allowed to use and carry this controlled item:[/color] + Permit Owner Name: [color=lightblue]{$name}[/color] + Date Granted: [color=lightblue]{$date}[/color] diff --git a/Resources/Locale/en-US/_Triad/prototypes/access/accesses.ftl b/Resources/Locale/en-US/_Triad/prototypes/access/accesses.ftl index df8ddc47436..5ca2130e3ff 100644 --- a/Resources/Locale/en-US/_Triad/prototypes/access/accesses.ftl +++ b/Resources/Locale/en-US/_Triad/prototypes/access/accesses.ftl @@ -4,7 +4,8 @@ id-card-access-level-security = TDF id-card-access-level-tdf-warden = Warden id-card-access-level-tdf-chief-enforcer = Chief Enforcer id-card-access-level-tdf-patrol-team-leader = Patrol Team Leader +id-card-access-level-tdf-permit-control = Contraband Permit Management id-card-access-level-sd = Solarian -id-card-access-level-tic = Coalition \ No newline at end of file +id-card-access-level-tic = Coalition diff --git a/Resources/Maps/_Triad/POI/tdfoutpost.yml b/Resources/Maps/_Triad/POI/tdfoutpost.yml index a7c16b76c66..db34e43c8e2 100644 --- a/Resources/Maps/_Triad/POI/tdfoutpost.yml +++ b/Resources/Maps/_Triad/POI/tdfoutpost.yml @@ -4,8 +4,8 @@ meta: engineVersion: 277.2.1 forkId: "" forkVersion: "" - time: 07/24/2026 22:07:54 - entityCount: 9633 + time: 08/03/2026 16:18:33 + entityCount: 9578 maps: [] grids: - 1 @@ -322,10 +322,10 @@ entities: - type: ThermalSignature - type: SpreaderGrid spreadQueues: - Smoke: [] Puddle: [] - Kudzu: [] MetalFoam: [] + Smoke: [] + Kudzu: [] updateAccumulator: 0.6666002 - type: Shuttle dampingModifier: 0.25 @@ -8623,227 +8623,6 @@ entities: - type: ContainedSolution containerName: food container: 20 - - uid: 24 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 23 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 23 - - uid: 26 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 25 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 25 - - uid: 28 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 27 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 27 - - uid: 30 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 29 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 29 - - uid: 32 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 31 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 31 - - uid: 34 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 33 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 33 - - uid: 37 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 36 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 36 - - uid: 39 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 38 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 38 - - uid: 41 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 40 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 40 - - uid: 43 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 42 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 42 - - uid: 45 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 44 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 44 - - uid: 47 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 46 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 46 - - uid: 49 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 48 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 48 - uid: 52 components: - type: MetaData @@ -9048,193 +8827,6 @@ entities: - type: ContainedSolution containerName: food container: 74 - - uid: 78 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 77 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 77 - - uid: 80 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 79 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 79 - - uid: 82 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 81 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 81 - - uid: 84 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 83 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 83 - - uid: 86 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 85 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 85 - - uid: 89 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 88 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 88 - - uid: 91 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 90 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 90 - - uid: 93 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 92 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 92 - - uid: 95 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 94 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 94 - - uid: 97 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 96 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 96 - - uid: 99 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 98 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 98 - uid: 102 components: - type: MetaData @@ -13119,142 +12711,6 @@ entities: - type: ContainedSolution containerName: food container: 730 - - uid: 733 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 732 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 732 - - uid: 735 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 734 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 734 - - uid: 737 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 736 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 736 - - uid: 739 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 738 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 738 - - uid: 741 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 740 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 740 - - uid: 743 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 742 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 742 - - uid: 745 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 744 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 744 - - uid: 747 - components: - - type: MetaData - name: solution - food - - type: Transform - parent: 746 - - type: Solution - solution: - maxVol: 1 - name: food - reagents: - - data: [] - ReagentId: Fiber - Quantity: 1 - - type: ContainedSolution - containerName: food - container: 746 - uid: 749 components: - type: MetaData @@ -26875,112 +26331,32 @@ entities: quickInsert: length: 0.5 storage: {} -- proto: BoxFolderTdfForms +- proto: BoxFolderTdf entities: - uid: 22 components: - type: Transform - rot: 1.5707963267948966 rad - pos: -6.531857,-31.289026 + pos: -6.490563,-31.079119 parent: 1 - - type: Storage - storedItems: - 23: - position: 0,0 - _rotation: South - 25: - position: 1,0 - _rotation: South - 27: - position: 2,0 - _rotation: South - 29: - position: 3,0 - _rotation: South - 31: - position: 4,0 - _rotation: South - 33: - position: 0,1 - _rotation: South - - type: ContainerContainer - containers: - storagebase: !type:Container - showEnts: False - occludes: True - ents: - - 23 - - 25 - - 27 - - 29 - - 31 - - 33 - - type: Physics - canCollide: False - - type: UseDelay - delays: - default: - endTime: 106.3589167 - length: 1 - quickInsert: - endTime: 106.3589167 - length: 0.5 - storage: - endTime: 106.3589167 - - uid: 35 + - uid: 23 components: - type: Transform - rot: 1.5707963267948966 rad - pos: -6.3705816,-31.146463 + pos: -6.675748,-31.44023 parent: 1 - - type: Storage - storedItems: - 36: - position: 0,0 - _rotation: South - 38: - position: 1,0 - _rotation: South - 40: - position: 2,0 - _rotation: South - 42: - position: 3,0 - _rotation: South - 44: - position: 4,0 - _rotation: South - 46: - position: 0,1 - _rotation: South - 48: - position: 1,1 - _rotation: South - - type: ContainerContainer - containers: - storagebase: !type:Container - showEnts: False - occludes: True - ents: - - 36 - - 38 - - 40 - - 42 - - 44 - - 46 - - 48 - - type: Physics - canCollide: False - - type: UseDelay - delays: - default: - endTime: 106.3589167 - length: 1 - quickInsert: - endTime: 106.3589167 - length: 0.5 - storage: - endTime: 106.3589167 + - uid: 24 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.3150997,-2.4283137 + parent: 1 + - uid: 25 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.6113963,-2.261647 + parent: 1 +- proto: BoxFolderTdfForms + entities: - uid: 50 components: - type: Transform @@ -27079,102 +26455,6 @@ entities: length: 0.5 storage: endTime: 106.3589167 - - uid: 76 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.6440287,-2.3937345 - parent: 1 - - type: Storage - storedItems: - 77: - position: 0,0 - _rotation: South - 79: - position: 1,0 - _rotation: South - 81: - position: 2,0 - _rotation: South - 83: - position: 3,0 - _rotation: South - 85: - position: 4,0 - _rotation: South - - type: ContainerContainer - containers: - storagebase: !type:Container - showEnts: False - occludes: True - ents: - - 77 - - 79 - - 81 - - 83 - - 85 - - type: Physics - canCollide: False - - type: UseDelay - delays: - default: - endTime: 106.3589167 - length: 1 - quickInsert: - endTime: 106.3589167 - length: 0.5 - storage: - endTime: 106.3589167 - - uid: 87 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.4524393,-2.7286344 - parent: 1 - - type: Storage - storedItems: - 88: - position: 0,0 - _rotation: South - 90: - position: 1,0 - _rotation: South - 92: - position: 2,0 - _rotation: South - 94: - position: 3,0 - _rotation: South - 96: - position: 4,0 - _rotation: South - 98: - position: 0,1 - _rotation: South - - type: ContainerContainer - containers: - storagebase: !type:Container - showEnts: False - occludes: True - ents: - - 88 - - 90 - - 92 - - 94 - - 96 - - 98 - - type: Physics - canCollide: False - - type: UseDelay - delays: - default: - endTime: 106.3589167 - length: 1 - quickInsert: - endTime: 106.3589167 - length: 0.5 - storage: - endTime: 106.3589167 - uid: 100 components: - type: Transform @@ -38984,35 +38264,20 @@ entities: nextSound: 21659.1399414 - type: ApcPowerReceiver powerLoad: 5 -- proto: ComputerCriminalRecords +- proto: ComputerContrabandPermit entities: - - uid: 4213 + - uid: 34 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-33.5 parent: 1 - - type: Battery - startingCharge: 0 - - type: PointLight - enabled: True - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 4214 - - type: LanguageSpeaker - understoodLanguages: - - TauCetiBasic - spokenLanguages: - - TauCetiBasic - currentLanguage: TauCetiBasic - - type: SpamEmitSound - nextSound: 21633.3824458 - type: ApcPowerReceiver powerLoad: 5 + - type: Battery + startingCharge: 0 +- proto: ComputerCriminalRecords + entities: - uid: 4215 components: - type: Transform @@ -39293,6 +38558,16 @@ entities: powerLoad: 5 - proto: ComputerShuttleRecords entities: + - uid: 35 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-33.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 5 + - type: Battery + startingCharge: 0 - uid: 4232 components: - type: Transform @@ -39344,33 +38619,6 @@ entities: powerLoad: 5 - proto: ComputerStationRecords entities: - - uid: 4235 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -2.5,-33.5 - parent: 1 - - type: Battery - startingCharge: 0 - - type: PointLight - enabled: True - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 4236 - - type: LanguageSpeaker - understoodLanguages: - - TauCetiBasic - spokenLanguages: - - TauCetiBasic - currentLanguage: TauCetiBasic - - type: SpamEmitSound - nextSound: 21646.3082254 - - type: ApcPowerReceiver - powerLoad: 5 - uid: 4237 components: - type: Transform @@ -39559,6 +38807,18 @@ entities: nextSound: 21628.2086625 - type: ApcPowerReceiver powerLoad: 5 +- proto: ComputerTabletopContrabandPermit + entities: + - uid: 33 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-3.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 5 + - type: Battery + startingCharge: 0 - proto: ComputerTabletopCrewMonitoring entities: - uid: 4243 @@ -39572,6 +38832,16 @@ entities: startingCharge: 0 - proto: ComputerTabletopCriminalRecords entities: + - uid: 36 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-31.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 5 + - type: Battery + startingCharge: 0 - uid: 4244 components: - type: Transform @@ -41684,12 +40954,6 @@ entities: parent: 1 - proto: CriminalRecordsComputerCircuitboard entities: - - uid: 4214 - components: - - type: Transform - parent: 4213 - - type: Physics - canCollide: False - uid: 4216 components: - type: Transform @@ -46115,7 +45379,7 @@ entities: - type: Label currentLabel: Astrotame - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.56822014 + sprayFizzinessThresholdRoll: 0.7313005 - type: Physics canCollide: False - type: NameModifier @@ -46251,7 +45515,7 @@ entities: - type: Label currentLabel: BBQ sauce - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.9672415 + sprayFizzinessThresholdRoll: 0.23824058 - type: Physics canCollide: False - type: NameModifier @@ -46581,7 +45845,7 @@ entities: - type: Label currentLabel: coconut water - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.88705283 + sprayFizzinessThresholdRoll: 0.12702285 - type: Physics canCollide: False - type: NameModifier @@ -46605,7 +45869,7 @@ entities: - type: Label currentLabel: coffee - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.9829515 + sprayFizzinessThresholdRoll: 0.06896283 - type: Physics canCollide: False - type: NameModifier @@ -46653,7 +45917,7 @@ entities: - type: Label currentLabel: coldsauce - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.0680465 + sprayFizzinessThresholdRoll: 0.11766104 - type: Physics canCollide: False - type: NameModifier @@ -46677,7 +45941,7 @@ entities: - type: Label currentLabel: cream - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.86512333 + sprayFizzinessThresholdRoll: 0.91006887 - type: Physics canCollide: False - type: NameModifier @@ -46891,7 +46155,7 @@ entities: - type: Label currentLabel: green tea - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.094226584 + sprayFizzinessThresholdRoll: 0.886561 - type: Physics canCollide: False - type: NameModifier @@ -46915,7 +46179,7 @@ entities: - type: Label currentLabel: horseradish sauce - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.8288235 + sprayFizzinessThresholdRoll: 0.78507745 - type: Physics canCollide: False - type: NameModifier @@ -46939,7 +46203,7 @@ entities: - type: Label currentLabel: hotsauce - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.14806867 + sprayFizzinessThresholdRoll: 0.8617372 - type: Physics canCollide: False - type: NameModifier @@ -46980,7 +46244,7 @@ entities: - type: Label currentLabel: ice - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.5963224 + sprayFizzinessThresholdRoll: 0.21809724 - type: Physics canCollide: False - type: NameModifier @@ -47004,7 +46268,7 @@ entities: - type: Label currentLabel: lime juice - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.7598539 + sprayFizzinessThresholdRoll: 0.34254462 - type: Physics canCollide: False - type: NameModifier @@ -47028,7 +46292,7 @@ entities: - type: Label currentLabel: orange juice - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.6565924 + sprayFizzinessThresholdRoll: 0.22410251 - type: Physics canCollide: False - type: NameModifier @@ -47052,7 +46316,7 @@ entities: - type: Label currentLabel: ketchup - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.35162577 + sprayFizzinessThresholdRoll: 0.8807438 - type: Physics canCollide: False - type: NameModifier @@ -47100,7 +46364,7 @@ entities: - type: Label currentLabel: mayonnaise - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.78293544 + sprayFizzinessThresholdRoll: 0.103291675 - type: Physics canCollide: False - type: NameModifier @@ -47141,7 +46405,7 @@ entities: - type: Label currentLabel: mustard - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.77159667 + sprayFizzinessThresholdRoll: 0.9530113 - type: Physics canCollide: False - type: NameModifier @@ -47357,7 +46621,7 @@ entities: - type: Label currentLabel: soy sauce - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.39695448 + sprayFizzinessThresholdRoll: 0.24296445 - type: Physics canCollide: False - type: NameModifier @@ -47429,7 +46693,7 @@ entities: - type: Label currentLabel: sugar - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.44466186 + sprayFizzinessThresholdRoll: 0.41351593 - type: Physics canCollide: False - type: NameModifier @@ -47470,7 +46734,7 @@ entities: - type: Label currentLabel: black tea - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.41685376 + sprayFizzinessThresholdRoll: 0.49854642 - type: Physics canCollide: False - type: NameModifier @@ -47800,7 +47064,7 @@ entities: - type: Label currentLabel: watermelon juice - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.9604254 + sprayFizzinessThresholdRoll: 0.2693596 - type: Physics canCollide: False - type: NameModifier @@ -51067,8 +50331,6 @@ entities: solutions: null containers: - drainBuffer - - type: Physics - canCollide: False - type: Fixtures fixtures: {} - type: ContainerContainer @@ -51084,8 +50346,6 @@ entities: solutions: null containers: - drainBuffer - - type: Physics - canCollide: False - type: Fixtures fixtures: {} - type: ContainerContainer @@ -51101,8 +50361,6 @@ entities: solutions: null containers: - drainBuffer - - type: Physics - canCollide: False - type: Fixtures fixtures: {} - type: ContainerContainer @@ -51118,8 +50376,6 @@ entities: solutions: null containers: - drainBuffer - - type: Physics - canCollide: False - type: Fixtures fixtures: {} - type: ContainerContainer @@ -51139,6 +50395,8 @@ entities: containers: solution@drainBuffer: !type:ContainerSlot ent: 462 + - type: Fixtures + fixtures: {} - uid: 463 components: - type: Transform @@ -51152,6 +50410,8 @@ entities: containers: solution@drainBuffer: !type:ContainerSlot ent: 464 + - type: Fixtures + fixtures: {} - uid: 465 components: - type: Transform @@ -51165,6 +50425,8 @@ entities: containers: solution@drainBuffer: !type:ContainerSlot ent: 466 + - type: Fixtures + fixtures: {} - uid: 467 components: - type: Transform @@ -51178,11 +50440,15 @@ entities: containers: solution@drainBuffer: !type:ContainerSlot ent: 468 + - type: Fixtures + fixtures: {} - uid: 5144 components: - type: Transform pos: -14.5,27.5 parent: 1 + - type: Fixtures + fixtures: {} - proto: FloorWaterEntity entities: - uid: 469 @@ -88208,7 +87474,7 @@ entities: - type: Label currentLabel: caramexinin - type: PressurizedSolution - sprayFizzinessThresholdRoll: 0.46188444 + sprayFizzinessThresholdRoll: 0.866679 - type: Physics canCollide: False - type: NameModifier @@ -89299,7 +88565,7 @@ entities: - uid: 6656 components: - type: Transform - pos: -6.2866936,-31.41888 + pos: -6.4072294,-31.19023 parent: 1 - type: UseDelay delays: @@ -89311,8 +88577,6 @@ entities: endTime: 106.3589167 startTime: 0 length: 1.5 - - type: Physics - canCollide: False - proto: MachineAnomalyVessel entities: - uid: 612 @@ -91734,1551 +90998,53 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 21 - - uid: 33 - components: - - type: Transform - parent: 22 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 34 - - uid: 46 - components: - - type: Transform - parent: 35 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 47 - - uid: 48 - components: - - type: Transform - parent: 35 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 49 - - uid: 61 - components: - - type: Transform - parent: 50 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 62 - - uid: 63 - components: - - type: Transform - parent: 50 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 64 - - uid: 98 - components: - - type: Transform - parent: 87 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 99 - - uid: 111 - components: - - type: Transform - parent: 100 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 112 - - uid: 124 - components: - - type: Transform - parent: 113 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 125 - - uid: 137 - components: - - type: Transform - parent: 126 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 138 - - uid: 347 - components: - - type: Transform - parent: 346 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 348 - - uid: 351 - components: - - type: Transform - parent: 346 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 352 - - uid: 361 - components: - - type: Transform - parent: 360 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 362 - - uid: 365 - components: - - type: Transform - parent: 360 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 366 - - uid: 367 - components: - - type: Transform - parent: 360 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 368 - - uid: 379 - components: - - type: Transform - parent: 378 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 380 - - uid: 383 - components: - - type: Transform - parent: 378 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 384 - - uid: 392 - components: - - type: Transform - parent: 391 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 393 - - uid: 394 - components: - - type: Transform - parent: 391 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 395 - - uid: 403 - components: - - type: Transform - parent: 402 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 404 - - uid: 405 - components: - - type: Transform - parent: 402 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 406 - - uid: 419 - components: - - type: Transform - parent: 418 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 420 - - uid: 423 - components: - - type: Transform - parent: 418 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 424 - - uid: 434 - components: - - type: Transform - parent: 433 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 435 - - uid: 436 - components: - - type: Transform - parent: 433 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 437 - - uid: 445 - components: - - type: Transform - parent: 444 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Physics - canCollide: False - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 446 - - uid: 728 - components: - - type: Transform - pos: -0.7766088,-3.1829383 - parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 729 - - uid: 730 - components: - - type: Transform - pos: -0.53759897,-3.0684974 - parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 731 - - uid: 732 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.4817734,-3.4606457 - parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 733 - - uid: 734 + ent: 21 + - uid: 26 components: - type: Transform rot: -1.5707963267948966 rad - pos: 6.5027966,-3.6009722 + pos: 6.658175,-2.6196716 parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 735 - - uid: 736 + - uid: 27 components: - type: Transform - rot: -1.5707486430790762 rad - pos: 6.7352366,-3.4248924 + rot: -1.5707963267948966 rad + pos: 6.3310146,-2.7863383 parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 737 - - uid: 738 + - uid: 28 components: - type: Transform rot: -1.5707963267948966 rad - pos: 6.669979,-3.5100708 + pos: 6.676694,-2.7739928 parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 739 - - uid: 740 + - uid: 30 components: - type: Transform rot: -1.5707963267948966 rad - pos: 6.72057,-3.1933389 + pos: 6.398916,-2.841894 parent: 1 - - type: SolutionContainerManager - solutions: null - containers: - - food - - type: Fixtures - fixtures: - fix1: - shape: !type:PolygonShape - radius: 0.01 - vertices: - - -0.25,-0.25 - - 0.25,-0.25 - - 0.25,0.25 - - -0.25,0.25 - mask: - - Impassable - - HighImpassable - layer: [] - density: 20 - hard: True - restitution: 0.3 - friction: 0.2 - flammable: - shape: !type:PhysShapeCircle - radius: 0.35 - position: 0,0 - mask: - - TableLayer - - HighImpassable - - LowImpassable - - BulletImpassable - - InteractImpassable - - Opaque - layer: [] - density: 1 - hard: False - restitution: 0 - friction: 0.4 - - type: ContainerContainer - containers: - solution@food: !type:ContainerSlot - ent: 741 - - uid: 742 + - uid: 31 components: - type: Transform - rot: -1.5708860913899283 rad - pos: 6.7362294,-3.2346299 + rot: -1.5707963267948966 rad + pos: 6.769286,-2.5764618 parent: 1 + - uid: 32 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.324842,-2.9283137 + parent: 1 + - uid: 61 + components: + - type: Transform + parent: 50 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -93316,17 +91082,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 743 - - uid: 744 + ent: 62 + - uid: 63 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 6.4819365,-3.2837522 - parent: 1 + parent: 50 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -93364,17 +91130,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 745 - - uid: 746 + ent: 64 + - uid: 111 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 6.494306,-3.1599188 - parent: 1 + parent: 100 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -93412,16 +91178,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 747 - - uid: 748 + ent: 112 + - uid: 124 components: - type: Transform - pos: -0.6081289,-3.4309852 - parent: 1 + parent: 113 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -93459,16 +91226,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 749 - - uid: 750 + ent: 125 + - uid: 137 components: - type: Transform - pos: -0.7701982,-3.459855 - parent: 1 + parent: 126 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -93506,26 +91274,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 751 -- proto: PaperBin20 - entities: - - uid: 6745 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -24.5,-5.5 - parent: 1 - - uid: 6746 - components: - - type: Transform - pos: 6.5,3.5 - parent: 1 -- proto: PaperOffice - entities: - - uid: 326 + ent: 138 + - uid: 347 components: - type: Transform - parent: 325 + parent: 346 - type: SolutionContainerManager solutions: null containers: @@ -93569,11 +91322,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 327 - - uid: 328 + ent: 348 + - uid: 351 components: - type: Transform - parent: 324 + parent: 346 - type: SolutionContainerManager solutions: null containers: @@ -93617,11 +91370,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 329 - - uid: 330 + ent: 352 + - uid: 361 components: - type: Transform - parent: 324 + parent: 360 - type: SolutionContainerManager solutions: null containers: @@ -93665,11 +91418,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 331 - - uid: 333 + ent: 362 + - uid: 365 components: - type: Transform - parent: 332 + parent: 360 - type: SolutionContainerManager solutions: null containers: @@ -93713,11 +91466,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 334 - - uid: 335 + ent: 366 + - uid: 367 components: - type: Transform - parent: 332 + parent: 360 - type: SolutionContainerManager solutions: null containers: @@ -93761,11 +91514,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 336 - - uid: 339 + ent: 368 + - uid: 379 components: - type: Transform - parent: 338 + parent: 378 - type: SolutionContainerManager solutions: null containers: @@ -93809,11 +91562,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 340 - - uid: 341 + ent: 380 + - uid: 383 components: - type: Transform - parent: 338 + parent: 378 - type: SolutionContainerManager solutions: null containers: @@ -93857,11 +91610,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 342 - - uid: 349 + ent: 384 + - uid: 392 components: - type: Transform - parent: 346 + parent: 391 - type: SolutionContainerManager solutions: null containers: @@ -93905,11 +91658,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 350 - - uid: 353 + ent: 393 + - uid: 394 components: - type: Transform - parent: 346 + parent: 391 - type: SolutionContainerManager solutions: null containers: @@ -93953,11 +91706,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 354 - - uid: 355 + ent: 395 + - uid: 403 components: - type: Transform - parent: 345 + parent: 402 - type: SolutionContainerManager solutions: null containers: @@ -94001,11 +91754,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 356 - - uid: 357 + ent: 404 + - uid: 405 components: - type: Transform - parent: 345 + parent: 402 - type: SolutionContainerManager solutions: null containers: @@ -94049,11 +91802,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 358 - - uid: 363 + ent: 406 + - uid: 419 components: - type: Transform - parent: 360 + parent: 418 - type: SolutionContainerManager solutions: null containers: @@ -94097,11 +91850,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 364 - - uid: 369 + ent: 420 + - uid: 423 components: - type: Transform - parent: 359 + parent: 418 - type: SolutionContainerManager solutions: null containers: @@ -94145,11 +91898,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 370 - - uid: 373 + ent: 424 + - uid: 434 components: - type: Transform - parent: 372 + parent: 433 - type: SolutionContainerManager solutions: null containers: @@ -94193,11 +91946,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 374 - - uid: 381 + ent: 435 + - uid: 436 components: - type: Transform - parent: 378 + parent: 433 - type: SolutionContainerManager solutions: null containers: @@ -94241,11 +91994,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 382 - - uid: 385 + ent: 437 + - uid: 445 components: - type: Transform - parent: 377 + parent: 444 - type: SolutionContainerManager solutions: null containers: @@ -94289,17 +92042,16 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 386 - - uid: 387 + ent: 446 + - uid: 728 components: - type: Transform - parent: 377 + pos: -0.7766088,-3.1829383 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -94337,17 +92089,16 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 388 - - uid: 396 + ent: 729 + - uid: 730 components: - type: Transform - parent: 390 + pos: -0.53759897,-3.0684974 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -94385,17 +92136,16 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 397 - - uid: 398 + ent: 731 + - uid: 748 components: - type: Transform - parent: 390 + pos: -0.6081289,-3.4309852 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -94433,17 +92183,16 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 399 - - uid: 407 + ent: 749 + - uid: 750 components: - type: Transform - parent: 401 + pos: -0.7701982,-3.459855 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -94481,11 +92230,26 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 408 - - uid: 409 + ent: 751 +- proto: PaperBin20 + entities: + - uid: 6745 components: - type: Transform - parent: 401 + rot: 3.141592653589793 rad + pos: -24.5,-5.5 + parent: 1 + - uid: 6746 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 +- proto: PaperOffice + entities: + - uid: 326 + components: + - type: Transform + parent: 325 - type: SolutionContainerManager solutions: null containers: @@ -94529,11 +92293,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 410 - - uid: 413 + ent: 327 + - uid: 328 components: - type: Transform - parent: 412 + parent: 324 - type: SolutionContainerManager solutions: null containers: @@ -94577,11 +92341,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 414 - - uid: 415 + ent: 329 + - uid: 330 components: - type: Transform - parent: 411 + parent: 324 - type: SolutionContainerManager solutions: null containers: @@ -94625,11 +92389,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 416 - - uid: 421 + ent: 331 + - uid: 333 components: - type: Transform - parent: 418 + parent: 332 - type: SolutionContainerManager solutions: null containers: @@ -94673,11 +92437,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 422 - - uid: 425 + ent: 334 + - uid: 335 components: - type: Transform - parent: 418 + parent: 332 - type: SolutionContainerManager solutions: null containers: @@ -94721,11 +92485,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 426 - - uid: 427 + ent: 336 + - uid: 339 components: - type: Transform - parent: 417 + parent: 338 - type: SolutionContainerManager solutions: null containers: @@ -94769,11 +92533,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 428 - - uid: 429 + ent: 340 + - uid: 341 components: - type: Transform - parent: 417 + parent: 338 - type: SolutionContainerManager solutions: null containers: @@ -94817,11 +92581,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 430 - - uid: 438 + ent: 342 + - uid: 349 components: - type: Transform - parent: 432 + parent: 346 - type: SolutionContainerManager solutions: null containers: @@ -94865,11 +92629,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 439 - - uid: 440 + ent: 350 + - uid: 353 components: - type: Transform - parent: 432 + parent: 346 - type: SolutionContainerManager solutions: null containers: @@ -94913,11 +92677,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 441 - - uid: 447 + ent: 354 + - uid: 355 components: - type: Transform - parent: 444 + parent: 345 - type: SolutionContainerManager solutions: null containers: @@ -94961,11 +92725,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 448 - - uid: 449 + ent: 356 + - uid: 357 components: - type: Transform - parent: 444 + parent: 345 - type: SolutionContainerManager solutions: null containers: @@ -95009,11 +92773,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 450 - - uid: 451 + ent: 358 + - uid: 363 components: - type: Transform - parent: 443 + parent: 360 - type: SolutionContainerManager solutions: null containers: @@ -95057,17 +92821,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 452 - - uid: 752 + ent: 364 + - uid: 369 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5425317,-2.8899565 - parent: 1 + parent: 359 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -95105,17 +92869,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 753 - - uid: 754 + ent: 370 + - uid: 373 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 2.3166606,-3.03758 - parent: 1 + parent: 372 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -95153,17 +92917,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 755 - - uid: 756 + ent: 374 + - uid: 381 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 2.3454287,-3.3167143 - parent: 1 + parent: 378 - type: SolutionContainerManager solutions: null containers: - food + - type: Physics + canCollide: False - type: Fixtures fixtures: fix1: @@ -95201,13 +92965,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 757 -- proto: PaperWrittenTdfAfterActionReport - entities: - - uid: 23 + ent: 382 + - uid: 385 components: - type: Transform - parent: 22 + parent: 377 - type: SolutionContainerManager solutions: null containers: @@ -95251,11 +93013,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 24 - - uid: 36 + ent: 386 + - uid: 387 components: - type: Transform - parent: 35 + parent: 377 - type: SolutionContainerManager solutions: null containers: @@ -95299,11 +93061,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 37 - - uid: 51 + ent: 388 + - uid: 396 components: - type: Transform - parent: 50 + parent: 390 - type: SolutionContainerManager solutions: null containers: @@ -95347,11 +93109,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 52 - - uid: 66 + ent: 397 + - uid: 398 components: - type: Transform - parent: 65 + parent: 390 - type: SolutionContainerManager solutions: null containers: @@ -95395,11 +93157,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 67 - - uid: 77 + ent: 399 + - uid: 407 components: - type: Transform - parent: 76 + parent: 401 - type: SolutionContainerManager solutions: null containers: @@ -95443,11 +93205,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 78 - - uid: 88 + ent: 408 + - uid: 409 components: - type: Transform - parent: 87 + parent: 401 - type: SolutionContainerManager solutions: null containers: @@ -95491,11 +93253,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 89 - - uid: 101 + ent: 410 + - uid: 413 components: - type: Transform - parent: 100 + parent: 412 - type: SolutionContainerManager solutions: null containers: @@ -95539,11 +93301,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 102 - - uid: 114 + ent: 414 + - uid: 415 components: - type: Transform - parent: 113 + parent: 411 - type: SolutionContainerManager solutions: null containers: @@ -95587,11 +93349,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 115 - - uid: 127 + ent: 416 + - uid: 421 components: - type: Transform - parent: 126 + parent: 418 - type: SolutionContainerManager solutions: null containers: @@ -95635,11 +93397,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 128 - - uid: 140 + ent: 422 + - uid: 425 components: - type: Transform - parent: 139 + parent: 418 - type: SolutionContainerManager solutions: null containers: @@ -95683,11 +93445,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 141 - - uid: 151 + ent: 426 + - uid: 427 components: - type: Transform - parent: 150 + parent: 417 - type: SolutionContainerManager solutions: null containers: @@ -95731,13 +93493,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 152 -- proto: PaperWrittenTdfContrabandPermit - entities: - - uid: 31 + ent: 428 + - uid: 429 components: - type: Transform - parent: 22 + parent: 417 - type: SolutionContainerManager solutions: null containers: @@ -95781,11 +93541,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 32 - - uid: 44 + ent: 430 + - uid: 438 components: - type: Transform - parent: 35 + parent: 432 - type: SolutionContainerManager solutions: null containers: @@ -95829,11 +93589,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 45 - - uid: 59 + ent: 439 + - uid: 440 components: - type: Transform - parent: 50 + parent: 432 - type: SolutionContainerManager solutions: null containers: @@ -95877,11 +93637,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 60 - - uid: 74 + ent: 441 + - uid: 447 components: - type: Transform - parent: 65 + parent: 444 - type: SolutionContainerManager solutions: null containers: @@ -95925,11 +93685,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 75 - - uid: 85 + ent: 448 + - uid: 449 components: - type: Transform - parent: 76 + parent: 444 - type: SolutionContainerManager solutions: null containers: @@ -95973,11 +93733,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 86 - - uid: 96 + ent: 450 + - uid: 451 components: - type: Transform - parent: 87 + parent: 443 - type: SolutionContainerManager solutions: null containers: @@ -96021,17 +93781,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 97 - - uid: 109 + ent: 452 + - uid: 752 components: - type: Transform - parent: 100 + rot: -1.5707963267948966 rad + pos: 2.5425317,-2.8899565 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -96069,17 +93829,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 110 - - uid: 122 + ent: 753 + - uid: 754 components: - type: Transform - parent: 113 + rot: -1.5707963267948966 rad + pos: 2.3166606,-3.03758 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -96117,17 +93877,17 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 123 - - uid: 135 + ent: 755 + - uid: 756 components: - type: Transform - parent: 126 + rot: -1.5707963267948966 rad + pos: 2.3454287,-3.3167143 + parent: 1 - type: SolutionContainerManager solutions: null containers: - food - - type: Physics - canCollide: False - type: Fixtures fixtures: fix1: @@ -96165,11 +93925,13 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 136 - - uid: 148 + ent: 757 +- proto: PaperWrittenTdfAfterActionReport + entities: + - uid: 51 components: - type: Transform - parent: 139 + parent: 50 - type: SolutionContainerManager solutions: null containers: @@ -96213,11 +93975,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 149 - - uid: 159 + ent: 52 + - uid: 66 components: - type: Transform - parent: 150 + parent: 65 - type: SolutionContainerManager solutions: null containers: @@ -96261,13 +94023,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 160 -- proto: PaperWrittenTdfDnr - entities: - - uid: 25 + ent: 67 + - uid: 101 components: - type: Transform - parent: 22 + parent: 100 - type: SolutionContainerManager solutions: null containers: @@ -96311,11 +94071,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 26 - - uid: 38 + ent: 102 + - uid: 114 components: - type: Transform - parent: 35 + parent: 113 - type: SolutionContainerManager solutions: null containers: @@ -96359,11 +94119,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 39 - - uid: 53 + ent: 115 + - uid: 127 components: - type: Transform - parent: 50 + parent: 126 - type: SolutionContainerManager solutions: null containers: @@ -96407,11 +94167,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 54 - - uid: 68 + ent: 128 + - uid: 140 components: - type: Transform - parent: 65 + parent: 139 - type: SolutionContainerManager solutions: null containers: @@ -96455,11 +94215,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 69 - - uid: 79 + ent: 141 + - uid: 151 components: - type: Transform - parent: 76 + parent: 150 - type: SolutionContainerManager solutions: null containers: @@ -96503,11 +94263,13 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 80 - - uid: 90 + ent: 152 +- proto: PaperWrittenTdfContrabandPermit + entities: + - uid: 59 components: - type: Transform - parent: 87 + parent: 50 - type: SolutionContainerManager solutions: null containers: @@ -96551,11 +94313,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 91 - - uid: 103 + ent: 60 + - uid: 74 components: - type: Transform - parent: 100 + parent: 65 - type: SolutionContainerManager solutions: null containers: @@ -96599,11 +94361,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 104 - - uid: 116 + ent: 75 + - uid: 109 components: - type: Transform - parent: 113 + parent: 100 - type: SolutionContainerManager solutions: null containers: @@ -96647,11 +94409,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 117 - - uid: 129 + ent: 110 + - uid: 122 components: - type: Transform - parent: 126 + parent: 113 - type: SolutionContainerManager solutions: null containers: @@ -96695,11 +94457,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 130 - - uid: 142 + ent: 123 + - uid: 135 components: - type: Transform - parent: 139 + parent: 126 - type: SolutionContainerManager solutions: null containers: @@ -96743,11 +94505,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 143 - - uid: 153 + ent: 136 + - uid: 148 components: - type: Transform - parent: 150 + parent: 139 - type: SolutionContainerManager solutions: null containers: @@ -96791,13 +94553,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 154 -- proto: PaperWrittenTdfInvestigatorReport - entities: - - uid: 27 + ent: 149 + - uid: 159 components: - type: Transform - parent: 22 + parent: 150 - type: SolutionContainerManager solutions: null containers: @@ -96841,11 +94601,13 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 28 - - uid: 40 + ent: 160 +- proto: PaperWrittenTdfDnr + entities: + - uid: 53 components: - type: Transform - parent: 35 + parent: 50 - type: SolutionContainerManager solutions: null containers: @@ -96889,11 +94651,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 41 - - uid: 55 + ent: 54 + - uid: 68 components: - type: Transform - parent: 50 + parent: 65 - type: SolutionContainerManager solutions: null containers: @@ -96937,11 +94699,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 56 - - uid: 70 + ent: 69 + - uid: 103 components: - type: Transform - parent: 65 + parent: 100 - type: SolutionContainerManager solutions: null containers: @@ -96985,11 +94747,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 71 - - uid: 81 + ent: 104 + - uid: 116 components: - type: Transform - parent: 76 + parent: 113 - type: SolutionContainerManager solutions: null containers: @@ -97033,11 +94795,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 82 - - uid: 92 + ent: 117 + - uid: 129 components: - type: Transform - parent: 87 + parent: 126 - type: SolutionContainerManager solutions: null containers: @@ -97081,11 +94843,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 93 - - uid: 105 + ent: 130 + - uid: 142 components: - type: Transform - parent: 100 + parent: 139 - type: SolutionContainerManager solutions: null containers: @@ -97129,11 +94891,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 106 - - uid: 118 + ent: 143 + - uid: 153 components: - type: Transform - parent: 113 + parent: 150 - type: SolutionContainerManager solutions: null containers: @@ -97177,11 +94939,13 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 119 - - uid: 131 + ent: 154 +- proto: PaperWrittenTdfInvestigatorReport + entities: + - uid: 55 components: - type: Transform - parent: 126 + parent: 50 - type: SolutionContainerManager solutions: null containers: @@ -97225,11 +94989,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 132 - - uid: 144 + ent: 56 + - uid: 70 components: - type: Transform - parent: 139 + parent: 65 - type: SolutionContainerManager solutions: null containers: @@ -97273,11 +95037,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 145 - - uid: 155 + ent: 71 + - uid: 105 components: - type: Transform - parent: 150 + parent: 100 - type: SolutionContainerManager solutions: null containers: @@ -97321,13 +95085,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 156 -- proto: PaperWrittenTdfShipSearchWarrant - entities: - - uid: 29 + ent: 106 + - uid: 118 components: - type: Transform - parent: 22 + parent: 113 - type: SolutionContainerManager solutions: null containers: @@ -97371,11 +95133,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 30 - - uid: 42 + ent: 119 + - uid: 131 components: - type: Transform - parent: 35 + parent: 126 - type: SolutionContainerManager solutions: null containers: @@ -97419,11 +95181,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 43 - - uid: 57 + ent: 132 + - uid: 144 components: - type: Transform - parent: 50 + parent: 139 - type: SolutionContainerManager solutions: null containers: @@ -97467,11 +95229,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 58 - - uid: 72 + ent: 145 + - uid: 155 components: - type: Transform - parent: 65 + parent: 150 - type: SolutionContainerManager solutions: null containers: @@ -97515,11 +95277,13 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 73 - - uid: 83 + ent: 156 +- proto: PaperWrittenTdfShipSearchWarrant + entities: + - uid: 57 components: - type: Transform - parent: 76 + parent: 50 - type: SolutionContainerManager solutions: null containers: @@ -97563,11 +95327,11 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 84 - - uid: 94 + ent: 58 + - uid: 72 components: - type: Transform - parent: 87 + parent: 65 - type: SolutionContainerManager solutions: null containers: @@ -97611,7 +95375,7 @@ entities: - type: ContainerContainer containers: solution@food: !type:ContainerSlot - ent: 95 + ent: 73 - uid: 107 components: - type: Transform @@ -141412,10 +139176,15 @@ entities: canCollide: False - proto: RubberStampApproved entities: + - uid: 37 + components: + - type: Transform + pos: -6.3424144,-31.468008 + parent: 1 - uid: 7196 components: - type: Transform - pos: 6.3666425,-3.7446904 + pos: 6.2585545,-3.9069881 parent: 1 - type: UseDelay delays: @@ -141427,14 +139196,17 @@ entities: endTime: 106.3589167 startTime: 0 length: 1 - - type: Physics - canCollide: False - proto: RubberStampDenied entities: + - uid: 38 + components: + - type: Transform + pos: -6.101674,-31.403193 + parent: 1 - uid: 7197 components: - type: Transform - pos: 6.659313,-3.7525845 + pos: 6.5857153,-3.8205686 parent: 1 - type: UseDelay delays: @@ -141446,8 +139218,6 @@ entities: endTime: 106.3589167 startTime: 0 length: 1 - - type: Physics - canCollide: False - proto: ScreenTimer entities: - uid: 7198 @@ -144886,12 +142656,6 @@ entities: canCollide: False - proto: StationRecordsComputerCircuitboard entities: - - uid: 4236 - components: - - type: Transform - parent: 4235 - - type: Physics - canCollide: False - uid: 4238 components: - type: Transform @@ -145882,6 +143646,12 @@ entities: parent: 1 - proto: TableReinforced entities: + - uid: 29 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-31.5 + parent: 1 - uid: 7444 components: - type: Transform @@ -158113,7 +155883,7 @@ entities: - type: UseDelay delays: RMCWieldDelay: - endTime: 536.2664398 + endTime: 536.2664397 startTime: 535.6327977 length: 1.2 - type: Physics diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index f133f9743ba..3f8899d4e50 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -116,6 +116,7 @@ - Biological - Inorganic - type: ShowSyndicateIcons # Monolith + - type: ContrabandPermitGranter # Triad - type: entity id: ActionAGhostShowSolar diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index b6bae14b402..c69f083193d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -864,19 +864,17 @@ - type: Access groups: - AllAccess - - USSPAA # Mono tags: - CentralCommand - NuclearOperative - SyndicateAgent - Wizard - - Pirate # Mono - - TsfmcEngineering # Mono - - GrandVizier - - PDVCommand # Mono + - Solarian # Triad + - Coalition # Triad - TdfChiefEnforcer # Triad - TdfPatrolTeamLeader # Triad - TdfWarden # Triad + - TdfPermitControl # Triad - type: Tag # Ignore Chameleon tags tags: - DoorBumpOpener diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_breaker.yml b/Resources/Prototypes/Entities/Objects/Tools/access_breaker.yml index e8f5916b547..4e89b66d288 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_breaker.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_breaker.yml @@ -1,5 +1,5 @@ - type: entity - parent: [ BaseTriadC2Contraband, BaseItem ] # Triad + parent: [ BaseTriadC2Contraband, BaseItem, BaseContrabandPermittable ] # Triad id: AccessBreakerUnlimited suffix: Unlimited name: authentication disruptor diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index c604d75fce2..6ea6b7c231d 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -37,6 +37,7 @@ - Frontier # Frontier - HeadOfPersonnel - TdfChiefEnforcer # Triad + - TdfPermitControl # Triad - Hydroponics - Janitor - Kitchen @@ -139,6 +140,7 @@ - Wizard - Solarian # Triad - Coalition # Triad + - TdfPermitControl # Triad privilegedIdSlot: name: id-card-console-privileged-id ejectSound: /Audio/Machines/id_swipe.ogg diff --git a/Resources/Prototypes/Entities/Objects/Tools/emag.yml b/Resources/Prototypes/Entities/Objects/Tools/emag.yml index 0b46c6b8feb..53adc3dbc2f 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/emag.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/emag.yml @@ -1,5 +1,5 @@ - type: entity - parent: [BaseTriadC2Contraband, BaseItem ] # Triad + parent: [BaseTriadC2Contraband, BaseItem, BaseContrabandPermittable] # Triad id: EmagUnlimited suffix: Unlimited name: cryptographic sequencer diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml index e70c345e60a..0bdd016a5c1 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml @@ -1,7 +1,6 @@ -# mono edit - type: entity name: breaching hammer - parent: [BaseC2ContrabandUnredeemable, BaseItem] + parent: [BaseItem, BaseContrabandPermittable] # Triad id: SecBreachingHammer description: A large, heavy hammer with a long handle, used for breaking stones or other heavy material such as the skulls of violent criminals, also perfect for forcing your way trough airlocks. components: @@ -11,7 +10,7 @@ - type: Item size: Ginormous # Triad, Ginormous < Huge shape: - - 0,0,4,5 # Triad + - 0,0,4,5 # Triad - type: MeleeWeapon heavyStaminaCost: 15 # Triad attackRate: 0.5 diff --git a/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Energy/launcher.yml b/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Energy/launcher.yml index 48d943e1a9f..30da88f30cb 100644 --- a/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Energy/launcher.yml +++ b/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Energy/launcher.yml @@ -312,7 +312,7 @@ - type: entity id: WeaponTurretM220 name: M220 RUBICON EMP launcher - parent: BallisticArtillery + parent: [BallisticArtillery, BaseShipContrabandPermittable] # Triad - contra permit description: Launches EMP projectiles at ships, disabling systems with powerful electromagnetic pulses. Ideal for non-lethal engagements and can be remotely activated or linked to a GCS. components: - type: StaticPrice diff --git a/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Kinetic/tarnyx_launcher.yml b/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Kinetic/tarnyx_launcher.yml index 2090aff334c..edfa8545ba3 100644 --- a/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Kinetic/tarnyx_launcher.yml +++ b/Resources/Prototypes/_Mono/Entities/SpaceArtillery/SpaceArtillery/Kinetic/tarnyx_launcher.yml @@ -3,7 +3,7 @@ - type: entity id: WeaponTurretTarnyx name: ADBX-31 TARNYX 150mm EMP Cannon - parent: BallisticArtilleryUnanchorable + parent: [BallisticArtilleryUnanchorable, BaseShipContrabandPermittableTdf] # Triad - contra permit suffix: Station, Recharging Ammo, EMP description: A heavy EMP cannon made by Aetherion Dynamics, designed to disable large sections of enemy ships with a single, powerful shot. Effective in asymmetric combat. Can be remotely activated or linked up to a GCS. components: diff --git a/Resources/Prototypes/_NF/contraband_severities.yml b/Resources/Prototypes/_NF/contraband_severities.yml index 5d40397c0e5..de6e628201e 100644 --- a/Resources/Prototypes/_NF/contraband_severities.yml +++ b/Resources/Prototypes/_NF/contraband_severities.yml @@ -7,7 +7,7 @@ - type: contrabandSeverity id: Class2 examineText: contraband-examine-text-Class2 - showDepartmentsAndJobs: true + #showDepartmentsAndJobs: true Triad - type: contrabandSeverity id: Class2Expedition diff --git a/Resources/Prototypes/_Triad/Access/tdf.yml b/Resources/Prototypes/_Triad/Access/tdf.yml index 5ebfb563631..035ffff1d07 100644 --- a/Resources/Prototypes/_Triad/Access/tdf.yml +++ b/Resources/Prototypes/_Triad/Access/tdf.yml @@ -10,6 +10,10 @@ id: TdfPatrolTeamLeader name: id-card-access-level-tdf-patrol-team-leader +- type: accessLevel + id: TdfPermitControl + name: id-card-access-level-tdf-permit-control + - type: accessGroup id: GeneralTdfAccess # No brig access tags: diff --git a/Resources/Prototypes/_Triad/Entities/Clothing/OuterClothing/Hardsuits/tdf.yml b/Resources/Prototypes/_Triad/Entities/Clothing/OuterClothing/Hardsuits/tdf.yml index 14eea66fa27..cb4967ec2b6 100644 --- a/Resources/Prototypes/_Triad/Entities/Clothing/OuterClothing/Hardsuits/tdf.yml +++ b/Resources/Prototypes/_Triad/Entities/Clothing/OuterClothing/Hardsuits/tdf.yml @@ -1,5 +1,5 @@ - type: entity - parent: [ ClothingOuterHardsuitBase, BaseFactionGearTDFT2, BaseClass2AShipContraband] + parent: [ClothingOuterHardsuitBase, BaseFactionGearTDFT2, BaseShipContrabandPermittableTdf] id: ClothingOuterHardsuitTdf name: TA-21 hardsuit description: Originally of NanoTrasen design, this hardsuit has been reverse-engineered by the TDF and is now issued to enforcers operating in low-pressure and high-risk environments. Specialized in bullet protection. @@ -149,3 +149,5 @@ coefficient: 0.35 - type: PirateBountyItem id: ClothingOuterHardsuitTacsuitHighValue + - type: ContrabandPermittable + permittable: false # Hell no diff --git a/Resources/Prototypes/_Triad/Entities/Objects/Devices/Misc/permit_chip.yml b/Resources/Prototypes/_Triad/Entities/Objects/Devices/Misc/permit_chip.yml new file mode 100644 index 00000000000..7a0fec280b1 --- /dev/null +++ b/Resources/Prototypes/_Triad/Entities/Objects/Devices/Misc/permit_chip.yml @@ -0,0 +1,34 @@ +- type: entity + parent: [BaseItem, BaseClass2AShipContrabandSilent] + id: PermitChip + name: TDF permit chip + description: A small chip used to store the serial number of a controlled item, along with the permit carrier's DNA signature for use in a permit manager console. + components: + - type: Item + size: Small + storedRotation: -90 + - type: Sprite + sprite: _Triad/Objects/Misc/permit_chip.rsi + state: icon + - type: EmitSoundOnPickup + sound: + path: /Audio/_Goobstation/Items/handling/card_pickup.ogg + params: + volume: -5 + - type: EmitSoundOnDrop + sound: + path: /Audio/_Goobstation/Items/handling/card_drop.ogg + params: + volume: -5 + - type: EmitSoundOnLand + sound: + path: /Audio/_Goobstation/Items/handling/card_drop.ogg + params: + volume: -5 + - type: ContrabandPermitChip + prickDamage: + types: + Piercing: 1 + permitCarrierBlacklist: + components: + - ContrabandPermitOwnerBlacklist diff --git a/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Lmgs/LMGs.yml b/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Lmgs/LMGs.yml index 4cad79e40df..3d2fdf19de1 100644 --- a/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Lmgs/LMGs.yml +++ b/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Lmgs/LMGs.yml @@ -1,6 +1,6 @@ - type: entity name: TDF MMG-38 "Riot" (6.8x52mm caseless) - parent: [BaseWeaponLightMachineGun, BaseGunWieldable, BaseClass2AShipContraband] + parent: [BaseWeaponLightMachineGun, BaseGunWieldable, BaseFactionGearTDFT2, BaseShipContrabandPermittableTdf] id: WeaponLMGRiot description: A reverse-engineered medium machine gun developed by the TDF from captured enemy technology. Designed to provide sustained suppressive fire, it is chambered in 6.8x52mm caseless and accepts both box and standard magazines. It is also compatible with 5.56x45mm and 7.62x39mm ammunition to simplify logistics. A label on the receiver reads "TRIAD DEFENSE FORCE." components: diff --git a/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index a2a8d6613c6..a18a07b4c8f 100644 --- a/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Rifles/rifles.yml +++ b/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Rifles/rifles.yml @@ -103,7 +103,7 @@ - type: entity name: TDF MR-8T DMR (8x65mm SKR) - parent: [BaseFactionGearTDFT2, WeaponRifleMR8C, BaseClass2AShipContraband] + parent: [BaseFactionGearTDFT2, WeaponRifleMR8C, BaseShipContrabandPermittableTdf] id: WeaponRifleMR8T description: An expensive TDF-modified version of the solarian republic's MR-8 DMR. It comes with a wooden grip, stock, a sleeker scope, and an improved barrel. Chambered in 8x65mm SKR. A label on the side reads "TRIAD DEFENSE FORCE". components: diff --git a/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Shotgun/shotgun.yml b/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Shotgun/shotgun.yml index 18350c3f18f..a42479ad2aa 100644 --- a/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Shotgun/shotgun.yml +++ b/Resources/Prototypes/_Triad/Entities/Objects/Weapons/Guns/Shotgun/shotgun.yml @@ -1,6 +1,6 @@ - type: entity name: Bastion double-barreled shotgun (4 gauge) - parent: [BaseWeaponShotgun, BaseGunWieldable, BaseClass2AShipContraband] + parent: [BaseWeaponShotgun, BaseGunWieldable, BaseFactionGearTDFT3, BaseShipContrabandPermittableTdf] id: WeaponShotgunDoubleBarreledTdfBastion description: A doom-slaying classic. A military grade double barrel shotgun chambered in 4 gauge. No attachments, no crutches, all skill. Suitable for breaching. components: diff --git a/Resources/Prototypes/_Triad/Entities/Objects/base_contraband.yml b/Resources/Prototypes/_Triad/Entities/Objects/base_contraband.yml index c85c98e6cb2..c1549ddb7e7 100644 --- a/Resources/Prototypes/_Triad/Entities/Objects/base_contraband.yml +++ b/Resources/Prototypes/_Triad/Entities/Objects/base_contraband.yml @@ -78,25 +78,83 @@ # Class 2 Contraband - type: entity - parent: BaseC2ContrabandUnredeemable - id: BaseTriadC2Contraband + id: BaseTriadC2ContrabandUnredeemable abstract: true components: - type: Contraband severity: Class2 + +- type: entity + parent: BaseTriadC2ContrabandUnredeemable + id: BaseTriadC2Contraband + abstract: true + components: + - type: Contraband turnInValues: - TriadCommerceCredit: 5 + TriadCommerceCredit: 15 # Class 3 Contraband - type: entity - parent: BaseC3ContrabandUnredeemable - id: BaseTriadC3Contraband + id: BaseTriadC3ContrabandUnredeemable abstract: true components: - type: Contraband severity: Class3General - turnInValues: - TriadCommerceCredit: 15 - type: ItemTax taxAccounts: TDF: -0.05 # TDF account + +- type: entity + parent: BaseTriadC3ContrabandUnredeemable + id: BaseTriadC3Contraband + abstract: true + components: + - type: Contraband + turnInValues: + TriadCommerceCredit: 25 + +# Ship Saving Contraband +- type: entity + id: BaseClass1ShipContraband # Legal to own, but seized. Typically for things that cannot serialize. + abstract: true + components: + - type: SavingContraband + examineText: ship-saving-contraband-1-text + +- type: entity + id: BaseClass2AShipContraband + abstract: true + components: + - type: SavingContraband + examineText: ship-saving-contraband-2a-text + +- type: entity + id: BaseClass2AShipContrabandSilent + abstract: true + components: + - type: SavingContraband + +# Contraband permit bases +- type: entity + id: BaseContrabandPermittable # Use for things that are permittable, but shouldn't be seized on save. Like access breakers. + abstract: true + components: + - type: ContrabandPermittable + permittable: true # This value exists purely for parenting + +- type: entity + id: BaseShipContrabandPermittable # This one is different than BaseContrabandPermittable, since it is deleted on ship save. + abstract: true + components: + - type: SavingContraband + examineText: null # ContrabandPermittable gives the examine text + - type: ContrabandPermittable + examineText: contraband-permittable-examine-no-save + +- type: entity + parent: BaseShipContrabandPermittable # TDF variant + id: BaseShipContrabandPermittableTdf + abstract: true + components: + - type: ContrabandPermittable + examineText: contraband-permittable-examine-tdf diff --git a/Resources/Prototypes/_Triad/Entities/Structures/Machines/computers.yml b/Resources/Prototypes/_Triad/Entities/Structures/Machines/computers.yml new file mode 100644 index 00000000000..6ff33fae287 --- /dev/null +++ b/Resources/Prototypes/_Triad/Entities/Structures/Machines/computers.yml @@ -0,0 +1,54 @@ +- type: entity + parent: + - BaseStructureIndestructible + - BaseComputerAiAccess + - BaseStructureAccessReaderImmuneToEmag + - BaseStructureDisableScrewing + - BaseClass2AShipContrabandSilent + id: ComputerContrabandPermit + name: contraband permit manager + description: Used to manage, grant, and revoke TDF contraband permits to Triad sector citizens. + components: + - type: Sprite + netsync: false + sprite: Structures/Machines/computers.rsi + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + sprite: _Triad/Structure/Machines/computers.rsi + state: permits + - map: ["computerLayerKeys"] + state: security_key + - type: ActivatableUI + key: enum.ContrabandPermitConsoleUi.Key + singleUser: true + - type: UserInterface + interfaces: + enum.ContrabandPermitConsoleUi.Key: + type: ContrabandPermitConsoleBoundUserInterface + - type: ItemSlots + slots: + chip_slot: + name: contraband-permit-console-item-slot-name + ejectSound: /Audio/Machines/id_swipe.ogg + insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg + priority: 1 + whitelist: + components: + - ContrabandPermitChip + - type: ContrabandPermitConsole + grantPermitWhitelist: + components: + - ContrabandPermitGranter + - type: CompanyAccessReader + requiredCompany: TDF + popupMessage: company-access-denied-tdf + - type: AccessReader + access: [["TdfPermitControl"]] # The ability to revoke permits + - type: ContainerContainer + containers: + board: !type:Container + chip_slot: !type:ContainerSlot diff --git a/Resources/Prototypes/_Triad/Entities/Structures/Machines/computers_tabletop.yml b/Resources/Prototypes/_Triad/Entities/Structures/Machines/computers_tabletop.yml new file mode 100644 index 00000000000..470d88c4231 --- /dev/null +++ b/Resources/Prototypes/_Triad/Entities/Structures/Machines/computers_tabletop.yml @@ -0,0 +1,19 @@ +- type: entity + parent: [BaseStructureIndestructible, BaseStructureComputerTabletop, ComputerContrabandPermit] + id: ComputerTabletopContrabandPermit + components: + - type: Sprite + drawdepth: SmallObjects + layers: + - map: ["computerLayerBody"] + sprite: _NF/Structures/Machines/computer_tabletop.rsi + state: computer_tabletop + - map: ["computerLayerKeyboard"] + sprite: _NF/Structures/Machines/computer_tabletop.rsi + state: generic_keyboard_tabletop + - map: ["computerLayerScreen"] + sprite: _Triad/Structure/Machines/computers.rsi + state: permits + - map: ["computerLayerKeys"] + sprite: Structures/Machines/computers.rsi + state: security_key diff --git a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/chief_enforcer.yml b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/chief_enforcer.yml index c0c34c71341..9f92ca7b301 100644 --- a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/chief_enforcer.yml +++ b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/chief_enforcer.yml @@ -26,6 +26,7 @@ - TdfChiefEnforcer - TdfWarden - TdfPatrolTeamLeader + - TdfPermitControl ranks: RankChiefEnforcer: [] special: @@ -43,6 +44,8 @@ - type: CryoSleepRadioOverride overrides: - Nfsd # TDF radio + - type: ContrabandPermitGranter + - type: ContrabandPermitOwnerBlacklist # No giving the TDF permits! - !type:GiveItemOnHolidaySpecial holiday: FrontierBirthday prototype: FrontierBirthdayGift diff --git a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/enforcer.yml b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/enforcer.yml index 379f3949985..8f13e610083 100644 --- a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/enforcer.yml +++ b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/enforcer.yml @@ -29,6 +29,7 @@ - type: CryoSleepRadioOverride overrides: - Nfsd # TDF radio + - type: ContrabandPermitOwnerBlacklist # No giving the TDF permits! - !type:AddImplantSpecial implants: [ MindShieldImplant, TrackingImplant ] - !type:GiveItemOnHolidaySpecial diff --git a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/junior_enforcer.yml b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/junior_enforcer.yml index 64e548652b8..a8a00365f28 100644 --- a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/junior_enforcer.yml +++ b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/junior_enforcer.yml @@ -32,6 +32,7 @@ - type: CryoSleepRadioOverride overrides: - Nfsd # TDF radio + - type: ContrabandPermitOwnerBlacklist # No giving the TDF permits! - !type:AddImplantSpecial implants: [ MindShieldImplant, TrackingImplant ] - !type:GiveItemOnHolidaySpecial diff --git a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/medic.yml b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/medic.yml index 7934466cdf7..37a0c58fde0 100644 --- a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/medic.yml +++ b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/medic.yml @@ -41,6 +41,7 @@ - type: CryoSleepRadioOverride overrides: - Nfsd # TDF radio + - type: ContrabandPermitOwnerBlacklist # No giving the TDF permits! - !type:AddImplantSpecial implants: [ MindShieldImplant, TrackingImplant ] - !type:GiveItemOnHolidaySpecial diff --git a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/patrol_team_leader.yml b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/patrol_team_leader.yml index 33c0ea9554b..54c6f917e05 100644 --- a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/patrol_team_leader.yml +++ b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/patrol_team_leader.yml @@ -28,8 +28,10 @@ - GeneralTdfAccessBrig access: - TdfPatrolTeamLeader + - Command - Medical - Chemistry + - TdfPermitControl ranks: RankSeniorEnforcer: [] special: @@ -42,6 +44,7 @@ - type: CryoSleepRadioOverride overrides: - Nfsd # TDF radio + - type: ContrabandPermitOwnerBlacklist # No giving the TDF permits! - !type:AddImplantSpecial implants: [ MindShieldImplant, TrackingImplant ] - !type:GiveItemOnHolidaySpecial diff --git a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/warden.yml b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/warden.yml index 11bbc2fbf7c..971735effdb 100644 --- a/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/warden.yml +++ b/Resources/Prototypes/_Triad/Roles/Jobs/Tdf/warden.yml @@ -27,6 +27,7 @@ - Chemistry - TdfWarden - TdfPatrolTeamLeader + - TdfPermitControl ranks: RankWarden: [] special: @@ -39,6 +40,7 @@ - type: CryoSleepRadioOverride overrides: - Nfsd # TDF radio + - type: ContrabandPermitOwnerBlacklist # No giving the TDF permits! - !type:AddImplantSpecial implants: [ MindShieldImplant, TrackingImplant ] - !type:GiveItemOnHolidaySpecial diff --git a/Resources/Prototypes/_Triad/SectorServices/services.yml b/Resources/Prototypes/_Triad/SectorServices/services.yml new file mode 100644 index 00000000000..4e838fc9fa6 --- /dev/null +++ b/Resources/Prototypes/_Triad/SectorServices/services.yml @@ -0,0 +1,5 @@ +# Stores contraband permit data +- type: sectorService + id: ContrabandPermits + components: + - type: SectorContrabandPermits diff --git a/Resources/Prototypes/_Triad/base_contraband.yml b/Resources/Prototypes/_Triad/base_contraband.yml deleted file mode 100644 index 8feeaded854..00000000000 --- a/Resources/Prototypes/_Triad/base_contraband.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Ship Saving Contraband -- type: entity - id: BaseClass1ShipContraband # Legal to own, but seized. Typically for things that cannot serialize. - abstract: true - components: - - type: SavingContraband - examineText: ship-saving-contraband-1-text - -- type: entity - id: BaseClass2AShipContraband - abstract: true - components: - - type: SavingContraband - examineText: ship-saving-contraband-2a-text - -- type: entity - id: BaseClass2AShipContrabandSilent - abstract: true - components: - - type: SavingContraband diff --git a/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/icon.png b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/icon.png new file mode 100644 index 00000000000..8915a3a97c6 Binary files /dev/null and b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/icon.png differ diff --git a/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/inhand-left.png b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/inhand-left.png new file mode 100644 index 00000000000..d61e4873a42 Binary files /dev/null and b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/inhand-left.png differ diff --git a/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/inhand-right.png b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/inhand-right.png new file mode 100644 index 00000000000..63cf532d20a Binary files /dev/null and b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/inhand-right.png differ diff --git a/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/meta.json b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/meta.json new file mode 100644 index 00000000000..768c9f88437 --- /dev/null +++ b/Resources/Textures/_Triad/Objects/Misc/permit_chip.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/d917f4c2a088419d5c3aec7656b7ff8cebd1822e | Resprited for TDF by TheRealMasterChief117", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "icon" + } + ] +} diff --git a/Resources/Textures/_Triad/Structure/Machines/computers.rsi/meta.json b/Resources/Textures/_Triad/Structure/Machines/computers.rsi/meta.json new file mode 100644 index 00000000000..ef5e45bb66d --- /dev/null +++ b/Resources/Textures/_Triad/Structure/Machines/computers.rsi/meta.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "permits", + "directions": 4, + "delays": [ + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ], + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ], + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ], + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/_Triad/Structure/Machines/computers.rsi/permits.png b/Resources/Textures/_Triad/Structure/Machines/computers.rsi/permits.png new file mode 100644 index 00000000000..66f18e13562 Binary files /dev/null and b/Resources/Textures/_Triad/Structure/Machines/computers.rsi/permits.png differ