Borgs (#18136)
* Laws * positronic brain and PAI rewrite * MMI * MMI pt. 2 * borg brain transfer * Roleban support, Borg job (WIP), the end of mind shenaniganry * battery drain, item slot cleanup, alerts * visuals * fix this pt1 * fix this pt2 * Modules, Lingering Stacks, Better borg flashlight * Start on UI, fix battery alerts, expand activation/deactivation, low movement speed on no power. * sprotes * no zombie borgs * oh fuck yeah i love a good relay * charger * fix the tiniest of sprite issues * adjustable names * a functional UI???? * foobar * more modules * this shit for some reason * upstream * genericize selectable borg modules * upstream again * holy fucking shit * i love christ * proper construction * da job * AA borgs * and boom more shit * admin logs * laws redux * ok just do this rq * oh boy that looks like modules * oh shit research * testos passo * so much shit holy fuck * fuckit we SHIP * last minute snags * should've gotten me on a better day
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Animations;
|
||||
using Content.Client.Examine;
|
||||
@@ -63,6 +64,18 @@ namespace Content.Client.Hands.Systems
|
||||
return;
|
||||
|
||||
var handsModified = component.Hands.Count != state.Hands.Count;
|
||||
// we need to check that, even if we have the same amount, that the individual hands didn't change.
|
||||
if (!handsModified)
|
||||
{
|
||||
foreach (var hand in component.Hands.Values)
|
||||
{
|
||||
if (state.Hands.Contains(hand))
|
||||
continue;
|
||||
handsModified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var manager = EnsureComp<ContainerManagerComponent>(uid);
|
||||
|
||||
if (handsModified)
|
||||
@@ -87,12 +100,13 @@ namespace Content.Client.Hands.Systems
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var hand in addedHands)
|
||||
component.SortedHands = new(state.HandNames);
|
||||
var sorted = addedHands.OrderBy(hand => component.SortedHands.IndexOf(hand.Name));
|
||||
|
||||
foreach (var hand in sorted)
|
||||
{
|
||||
AddHand(uid, hand, component);
|
||||
}
|
||||
|
||||
component.SortedHands = new(state.HandNames);
|
||||
}
|
||||
|
||||
_stripSys.UpdateUi(uid);
|
||||
@@ -327,7 +341,7 @@ namespace Content.Client.Hands.Systems
|
||||
}
|
||||
|
||||
var ev = new GetInhandVisualsEvent(uid, hand.Location);
|
||||
RaiseLocalEvent(held, ev, false);
|
||||
RaiseLocalEvent(held, ev);
|
||||
|
||||
if (ev.Layers.Count == 0)
|
||||
{
|
||||
@@ -340,7 +354,7 @@ namespace Content.Client.Hands.Systems
|
||||
{
|
||||
if (!revealedLayers.Add(key))
|
||||
{
|
||||
Logger.Warning($"Duplicate key for in-hand visuals: {key}. Are multiple components attempting to modify the same layer? Entity: {ToPrettyString(held)}");
|
||||
Log.Warning($"Duplicate key for in-hand visuals: {key}. Are multiple components attempting to modify the same layer? Entity: {ToPrettyString(held)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
66
Content.Client/Silicons/Borgs/BorgBoundUserInterface.cs
Normal file
66
Content.Client/Silicons/Borgs/BorgBoundUserInterface.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BorgBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private BorgMenu? _menu;
|
||||
|
||||
public BorgBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
var owner = Owner;
|
||||
|
||||
_menu = new BorgMenu(owner);
|
||||
|
||||
_menu.BrainButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new BorgEjectBrainBuiMessage());
|
||||
};
|
||||
|
||||
_menu.EjectBatteryButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new BorgEjectBatteryBuiMessage());
|
||||
};
|
||||
|
||||
_menu.NameChanged += name =>
|
||||
{
|
||||
SendMessage(new BorgSetNameBuiMessage(name));
|
||||
};
|
||||
|
||||
_menu.RemoveModuleButtonPressed += module =>
|
||||
{
|
||||
SendMessage(new BorgRemoveModuleBuiMessage(module));
|
||||
};
|
||||
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not BorgBuiState msg)
|
||||
return;
|
||||
_menu?.UpdateState(msg);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
58
Content.Client/Silicons/Borgs/BorgMenu.xaml
Normal file
58
Content.Client/Silicons/Borgs/BorgMenu.xaml
Normal file
@@ -0,0 +1,58 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'borg-ui-menu-title'}"
|
||||
MinSize="600 350"
|
||||
SetSize="650 450">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="15 10 15 15">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<ProgressBar Name="ChargeBar" Access="Public" HorizontalExpand="True" MinValue="0" MaxValue="1">
|
||||
<Label Name="ChargeLabel" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5 0 0 0"/>
|
||||
</ProgressBar>
|
||||
<Control MinWidth="5"/>
|
||||
<Button Name="EjectBatteryButton" Text="{Loc 'borg-ui-remove-battery'}" StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
<customControls:HSeparator Margin="0 10 0 10"/>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<SpriteView Name="BorgSprite" Scale="5 5"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<customControls:HSeparator Margin="0 10 0 10"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<LineEdit Name="NameLineEdit" HorizontalExpand="True"/>
|
||||
<Label Name="NameIdentifierLabel" Margin="10 0 0 0"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10"/>
|
||||
<Button HorizontalExpand="True" Name="BrainButton" ToggleMode="False" Margin="0" MinHeight="40" StyleClasses="OpenLeft">
|
||||
<SpriteView Name="BrainView" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
<customControls:VSeparator/>
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="15 10 15 15">
|
||||
<Label Text="{Loc 'borg-ui-modules-label'}" Margin="0 0 0 5"/>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer VerticalExpand="True" Orientation="Vertical">
|
||||
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Name="ModuleContainer" RectClipContent="True"/>
|
||||
</ScrollContainer>
|
||||
<Label Name="ModuleCounter"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="WindowFooterText"
|
||||
Margin="5"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
165
Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
Normal file
165
Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BorgMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
public Action? BrainButtonPressed;
|
||||
public Action? EjectBatteryButtonPressed;
|
||||
public Action<string>? NameChanged;
|
||||
public Action<EntityUid>? RemoveModuleButtonPressed;
|
||||
|
||||
private readonly BorgChassisComponent? _chassis;
|
||||
public readonly EntityUid Entity;
|
||||
public float AccumulatedTime;
|
||||
private string _lastValidName;
|
||||
|
||||
public BorgMenu(EntityUid entity)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Entity = entity;
|
||||
|
||||
if (_entity.TryGetComponent<BorgChassisComponent>(Entity, out var chassis))
|
||||
_chassis = chassis;
|
||||
|
||||
BorgSprite.SetEntity(entity);
|
||||
ChargeBar.MaxValue = 1f;
|
||||
ChargeBar.Value = 1f;
|
||||
|
||||
if (_entity.TryGetComponent<NameIdentifierComponent>(Entity, out var nameIdentifierComponent))
|
||||
{
|
||||
NameIdentifierLabel.Visible = true;
|
||||
NameIdentifierLabel.Text = nameIdentifierComponent.FullIdentifier;
|
||||
|
||||
var fullName = _entity.GetComponent<MetaDataComponent>(Entity).EntityName;
|
||||
var name = fullName.Substring(0, fullName.Length - nameIdentifierComponent.FullIdentifier.Length - 1);
|
||||
NameLineEdit.Text = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
NameIdentifierLabel.Visible = false;
|
||||
NameLineEdit.Text = _entity.GetComponent<MetaDataComponent>(Entity).EntityName;
|
||||
}
|
||||
|
||||
_lastValidName = NameLineEdit.Text;
|
||||
|
||||
EjectBatteryButton.OnPressed += _ => EjectBatteryButtonPressed?.Invoke();
|
||||
BrainButton.OnPressed += _ => BrainButtonPressed?.Invoke();
|
||||
|
||||
NameLineEdit.OnTextChanged += OnNameChanged;
|
||||
NameLineEdit.OnTextEntered += OnNameEntered;
|
||||
NameLineEdit.OnFocusExit += OnNameFocusExit;
|
||||
|
||||
UpdateBrainButton();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
AccumulatedTime += args.DeltaSeconds;
|
||||
BorgSprite.OverrideDirection = (Direction) ((int) AccumulatedTime % 4 * 2);
|
||||
}
|
||||
|
||||
public void UpdateState(BorgBuiState state)
|
||||
{
|
||||
EjectBatteryButton.Disabled = !state.HasBattery;
|
||||
ChargeBar.Value = state.ChargePercent;
|
||||
ChargeLabel.Text = Loc.GetString("borg-ui-charge-label",
|
||||
("charge", (int) MathF.Round(state.ChargePercent * 100)));
|
||||
|
||||
UpdateBrainButton();
|
||||
UpdateModulePanel();
|
||||
}
|
||||
|
||||
private void UpdateBrainButton()
|
||||
{
|
||||
if (_chassis?.BrainEntity is { } brain)
|
||||
{
|
||||
BrainButton.Text = _entity.GetComponent<MetaDataComponent>(brain).EntityName;
|
||||
BrainView.Visible = true;
|
||||
BrainView.SetEntity(brain);
|
||||
BrainButton.Disabled = false;
|
||||
BrainButton.AddStyleClass(StyleBase.ButtonOpenLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
BrainButton.Text = Loc.GetString("borg-ui-no-brain");
|
||||
BrainButton.Disabled = true;
|
||||
BrainView.Visible = false;
|
||||
BrainButton.RemoveStyleClass(StyleBase.ButtonOpenLeft);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateModulePanel()
|
||||
{
|
||||
if (_chassis == null)
|
||||
return;
|
||||
|
||||
ModuleCounter.Text = Loc.GetString("borg-ui-module-counter",
|
||||
("actual", _chassis.ModuleCount),
|
||||
("max", _chassis.MaxModules));
|
||||
|
||||
ModuleContainer.Children.Clear();
|
||||
foreach (var module in _chassis.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
var control = new BorgModuleControl(module, _entity);
|
||||
control.RemoveButtonPressed += () =>
|
||||
{
|
||||
RemoveModuleButtonPressed?.Invoke(module);
|
||||
};
|
||||
ModuleContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNameChanged(LineEdit.LineEditEventArgs obj)
|
||||
{
|
||||
if (obj.Text.Length == 0 ||
|
||||
string.IsNullOrWhiteSpace(obj.Text) ||
|
||||
string.IsNullOrEmpty(obj.Text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.Text.Length > HumanoidCharacterProfile.MaxNameLength)
|
||||
{
|
||||
obj.Control.Text = obj.Text.Substring(0, HumanoidCharacterProfile.MaxNameLength);
|
||||
}
|
||||
|
||||
_lastValidName = obj.Control.Text;
|
||||
obj.Control.Text = _lastValidName;
|
||||
}
|
||||
|
||||
private void OnNameEntered(LineEdit.LineEditEventArgs obj)
|
||||
{
|
||||
NameChanged?.Invoke(_lastValidName);
|
||||
}
|
||||
|
||||
private void OnNameFocusExit(LineEdit.LineEditEventArgs obj)
|
||||
{
|
||||
if (obj.Text.Length > HumanoidCharacterProfile.MaxNameLength ||
|
||||
obj.Text.Length == 0 ||
|
||||
string.IsNullOrWhiteSpace(obj.Text) ||
|
||||
string.IsNullOrEmpty(obj.Text))
|
||||
{
|
||||
obj.Control.Text = _lastValidName.Trim();
|
||||
}
|
||||
|
||||
NameChanged?.Invoke(_lastValidName);
|
||||
}
|
||||
}
|
||||
|
||||
12
Content.Client/Silicons/Borgs/BorgModuleControl.xaml
Normal file
12
Content.Client/Silicons/Borgs/BorgModuleControl.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="PanelBackgroundLight"
|
||||
Margin="5 5 5 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 5 0 5">
|
||||
<SpriteView Name="ModuleView" Margin="0 0 5 0"/>
|
||||
<BoxContainer RectClipContent="True" HorizontalExpand="True">
|
||||
<Label Name="ModuleName" HorizontalExpand="True" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
<TextureButton Name="RemoveButton" VerticalAlignment="Top" HorizontalAlignment="Right" Scale="0.5 0.5"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
25
Content.Client/Silicons/Borgs/BorgModuleControl.xaml.cs
Normal file
25
Content.Client/Silicons/Borgs/BorgModuleControl.xaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BorgModuleControl : PanelContainer
|
||||
{
|
||||
public Action? RemoveButtonPressed;
|
||||
|
||||
public BorgModuleControl(EntityUid entity, IEntityManager entityManager)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ModuleView.SetEntity(entity);
|
||||
ModuleName.Text = entityManager.GetComponent<MetaDataComponent>(entity).EntityName;
|
||||
RemoveButton.TexturePath = "/Textures/Interface/Nano/cross.svg.png";
|
||||
RemoveButton.OnPressed += _ =>
|
||||
{
|
||||
RemoveButtonPressed?.Invoke();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
85
Content.Client/Silicons/Borgs/BorgSystem.cs
Normal file
85
Content.Client/Silicons/Borgs/BorgSystem.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BorgChassisComponent, AppearanceChangeEvent>(OnBorgAppearanceChanged);
|
||||
SubscribeLocalEvent<MMIComponent, AppearanceChangeEvent>(OnMMIAppearanceChanged);
|
||||
}
|
||||
|
||||
private void OnBorgAppearanceChanged(EntityUid uid, BorgChassisComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
UpdateBorgAppearnce(uid, component, args.Component, args.Sprite);
|
||||
}
|
||||
|
||||
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (!component.Initialized)
|
||||
return;
|
||||
|
||||
base.OnInserted(uid, component, args);
|
||||
UpdateBorgAppearnce(uid, component);
|
||||
}
|
||||
|
||||
protected override void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (!component.Initialized)
|
||||
return;
|
||||
|
||||
base.OnRemoved(uid, component, args);
|
||||
UpdateBorgAppearnce(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateBorgAppearnce(EntityUid uid,
|
||||
BorgChassisComponent? component = null,
|
||||
AppearanceComponent? appearance = null,
|
||||
SpriteComponent? sprite = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref appearance, ref sprite))
|
||||
return;
|
||||
|
||||
if (!_appearance.TryGetData<bool>(uid, BorgVisuals.HasPlayer, out var hasPlayer, appearance))
|
||||
hasPlayer = false;
|
||||
|
||||
sprite.LayerSetVisible(BorgVisualLayers.Light, component.BrainEntity != null || hasPlayer);
|
||||
sprite.LayerSetState(BorgVisualLayers.Light, hasPlayer ? component.HasMindState : component.NoMindState);
|
||||
}
|
||||
|
||||
private void OnMMIAppearanceChanged(EntityUid uid, MMIComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
var sprite = args.Sprite;
|
||||
|
||||
if (!_appearance.TryGetData(uid, MMIVisuals.BrainPresent, out bool brain))
|
||||
brain = false;
|
||||
if (!_appearance.TryGetData(uid, MMIVisuals.HasMind, out bool hasMind))
|
||||
hasMind = false;
|
||||
|
||||
sprite.LayerSetVisible(MMIVisualLayers.Brain, brain);
|
||||
if (!brain)
|
||||
{
|
||||
sprite.LayerSetState(MMIVisualLayers.Base, component.NoBrainState);
|
||||
}
|
||||
else
|
||||
{
|
||||
var state = hasMind
|
||||
? component.HasMindState
|
||||
: component.NoMindState;
|
||||
sprite.LayerSetState(MMIVisualLayers.Base, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Content.Client/Silicons/Laws/SiliconLawSystem.cs
Normal file
9
Content.Client/Silicons/Laws/SiliconLawSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Silicons.Laws;
|
||||
|
||||
namespace Content.Client.Silicons.Laws;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
{
|
||||
|
||||
}
|
||||
18
Content.Client/Silicons/Laws/Ui/LawDisplay.xaml
Normal file
18
Content.Client/Silicons/Laws/Ui/LawDisplay.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Margin="0 0 0 10">
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252a"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="5 5 5 5">
|
||||
<Label Name="LawNumberLabel" StyleClasses="StatusFieldTitle"/>
|
||||
<customControls:HSeparator Margin="0 5 0 5"/>
|
||||
<RichTextLabel Name="LawLabel"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
21
Content.Client/Silicons/Laws/Ui/LawDisplay.xaml.cs
Normal file
21
Content.Client/Silicons/Laws/Ui/LawDisplay.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LawDisplay : Control
|
||||
{
|
||||
public LawDisplay(SiliconLaw prototype)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var identifier = prototype.LawIdentifierOverride ?? $"{prototype.Order}";
|
||||
|
||||
LawNumberLabel.Text = Loc.GetString("laws-ui-law-header", ("id", identifier));
|
||||
LawLabel.SetMessage(Loc.GetString(prototype.LawString));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SiliconLawBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private SiliconLawMenu? _menu;
|
||||
|
||||
public SiliconLawBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new();
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
_menu?.Close();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not SiliconLawBuiState msg)
|
||||
return;
|
||||
|
||||
_menu?.Update(msg);
|
||||
}
|
||||
}
|
||||
27
Content.Client/Silicons/Laws/Ui/SiliconLawMenu.xaml
Normal file
27
Content.Client/Silicons/Laws/Ui/SiliconLawMenu.xaml
Normal file
@@ -0,0 +1,27 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'laws-ui-menu-title'}"
|
||||
MinSize="200 100"
|
||||
SetSize="450 385">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<PanelContainer VerticalExpand="True" Margin="10 10 10 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer
|
||||
HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Name="LawDisplayContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
Margin="10 10 10 0">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
31
Content.Client/Silicons/Laws/Ui/SiliconLawMenu.xaml.cs
Normal file
31
Content.Client/Silicons/Laws/Ui/SiliconLawMenu.xaml.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SiliconLawMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public SiliconLawMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void Update(SiliconLawBuiState state)
|
||||
{
|
||||
state.Laws.Sort();
|
||||
foreach (var law in state.Laws)
|
||||
{
|
||||
var control = new LawDisplay(law);
|
||||
|
||||
LawDisplayContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Shared.Stacks;
|
||||
@@ -31,8 +32,22 @@ namespace Content.Client.Stack
|
||||
|
||||
base.SetCount(uid, amount, component);
|
||||
|
||||
if (component.Lingering &&
|
||||
TryComp<SpriteComponent>(uid, out var sprite))
|
||||
{
|
||||
// tint the stack gray and make it transparent if it's lingering.
|
||||
var color = component.Count == 0 && component.Lingering
|
||||
? Color.DarkGray.WithAlpha(0.65f)
|
||||
: Color.White;
|
||||
|
||||
for (var i = 0; i < sprite.AllLayers.Count(); i++)
|
||||
{
|
||||
sprite.LayerSetColor(i, color);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call.
|
||||
if (component.Count <= 0)
|
||||
if (component.Count <= 0 && !component.Lingering)
|
||||
{
|
||||
Xform.DetachParentToNull(uid, Transform(uid));
|
||||
return;
|
||||
|
||||
@@ -1332,6 +1332,10 @@ namespace Content.Client.Stylesheets
|
||||
.Prop("panel", new StyleBoxTexture(BaseButtonOpenBoth) { Padding = default })
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#1F1F23")),
|
||||
|
||||
Element<PanelContainer>().Class("PanelBackgroundLight")
|
||||
.Prop("panel", new StyleBoxTexture(BaseButtonOpenBoth) { Padding = default })
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#2F2F3B")),
|
||||
|
||||
// Window Footer
|
||||
Element<TextureRect>().Class("NTLogoDark")
|
||||
.Prop(TextureRect.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Nano/ntlogo.svg.png"))
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Cargo.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
|
||||
namespace Content.Server.Armor
|
||||
{
|
||||
@@ -22,6 +23,7 @@ namespace Content.Server.Armor
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ArmorComponent, InventoryRelayedEvent<DamageModifyEvent>>(OnDamageModify);
|
||||
SubscribeLocalEvent<ArmorComponent, BorgModuleRelayedEvent<DamageModifyEvent>>(OnBorgDamageModify);
|
||||
SubscribeLocalEvent<ArmorComponent, GetVerbsEvent<ExamineVerb>>(OnArmorVerbExamine);
|
||||
SubscribeLocalEvent<ArmorComponent, PriceCalculationEvent>(GetArmorPrice);
|
||||
}
|
||||
@@ -67,6 +69,11 @@ namespace Content.Server.Armor
|
||||
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
|
||||
}
|
||||
|
||||
private void OnBorgDamageModify(EntityUid uid, ArmorComponent component, ref BorgModuleRelayedEvent<DamageModifyEvent> args)
|
||||
{
|
||||
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
|
||||
}
|
||||
|
||||
private void OnArmorVerbExamine(EntityUid uid, ArmorComponent component, GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
|
||||
@@ -40,12 +40,12 @@ namespace Content.Server.Body.Systems
|
||||
|
||||
private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
|
||||
{
|
||||
EntityManager.EnsureComponent<MindContainerComponent>(newEntity);
|
||||
var oldMind = EntityManager.EnsureComponent<MindContainerComponent>(oldEntity);
|
||||
EnsureComp<MindContainerComponent>(newEntity);
|
||||
var oldMind = EnsureComp<MindContainerComponent>(oldEntity);
|
||||
|
||||
EnsureComp<GhostOnMoveComponent>(newEntity);
|
||||
var ghostOnMove = EnsureComp<GhostOnMoveComponent>(newEntity);
|
||||
if (HasComp<BodyComponent>(newEntity))
|
||||
Comp<GhostOnMoveComponent>(newEntity).MustBeDead = true;
|
||||
ghostOnMove.MustBeDead = true;
|
||||
|
||||
// TODO: This is an awful solution.
|
||||
// Our greatest minds still can't figure out how to allow brains/heads to ghost without giving them the
|
||||
|
||||
@@ -357,8 +357,12 @@ namespace Content.Server.Construction
|
||||
if (!_container.TryGetContainer(uid, container, out var ourContainer, containerManager))
|
||||
continue;
|
||||
|
||||
// NOTE: Only Container is supported by Construction!
|
||||
var otherContainer = _container.EnsureContainer<Container>(newUid, container, newContainerManager);
|
||||
if (!_container.TryGetContainer(newUid, container, out var otherContainer, newContainerManager))
|
||||
{
|
||||
// NOTE: Only Container is supported by Construction!
|
||||
// todo: one day, the ensured container should be the same type as ourContainer
|
||||
otherContainer = _container.EnsureContainer<Container>(newUid, container, newContainerManager);
|
||||
}
|
||||
|
||||
for (var i = ourContainer.ContainedEntities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Construction.Components;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Construction.Steps;
|
||||
using Content.Shared.DoAfter;
|
||||
@@ -39,6 +40,7 @@ namespace Content.Server.Construction
|
||||
new []{typeof(AnchorableSystem)},
|
||||
new []{typeof(EncryptionKeySystem)});
|
||||
SubscribeLocalEvent<ConstructionComponent, OnTemperatureChangeEvent>(EnqueueEvent);
|
||||
SubscribeLocalEvent<ConstructionComponent, PartAssemblyPartInsertedEvent>(EnqueueEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -388,6 +390,16 @@ namespace Content.Server.Construction
|
||||
|
||||
}
|
||||
|
||||
case PartAssemblyConstructionGraphStep partAssemblyStep:
|
||||
{
|
||||
if (ev is not PartAssemblyPartInsertedEvent)
|
||||
break;
|
||||
|
||||
if (partAssemblyStep.Condition(uid, EntityManager))
|
||||
return HandleResult.True;
|
||||
return HandleResult.False;
|
||||
}
|
||||
|
||||
#endregion
|
||||
// --- CONSTRUCTION STEP EVENT HANDLING FINISH ---
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// Drop all items from specified containers
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class EmptyContainersBehaviour : IThresholdBehavior
|
||||
{
|
||||
[DataField("containers")]
|
||||
public List<string> Containers = new();
|
||||
|
||||
[DataField("randomOffset")]
|
||||
public float RandomOffset = 0.25f;
|
||||
|
||||
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
|
||||
{
|
||||
if (!system.EntityManager.TryGetComponent<ContainerManagerComponent>(owner, out var containerManager))
|
||||
return;
|
||||
|
||||
var containerSys = system.EntityManager.System<ContainerSystem>();
|
||||
|
||||
|
||||
foreach (var containerId in Containers)
|
||||
{
|
||||
if (!containerSys.TryGetContainer(owner, containerId, out var container, containerManager))
|
||||
continue;
|
||||
|
||||
var entities = containerSys.EmptyContainer(container, true);
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
ent.RandomOffset(RandomOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
||||
var prefList = new List<IPlayerSession>();
|
||||
foreach (var player in allPlayers)
|
||||
{
|
||||
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity))
|
||||
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity) || HasComp<ZombieImmuneComponent>(player.AttachedEntity))
|
||||
continue;
|
||||
playerList.Add(player);
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Content.Server.Ghost.Roles.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for a ghost role which can be toggled on and off at will, like a PAI.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ToggleableGhostRoleComponent : Component
|
||||
{
|
||||
[DataField("examineTextMindPresent")]
|
||||
public string ExamineTextMindPresent = string.Empty;
|
||||
|
||||
[DataField("examineTextMindSearching")]
|
||||
public string ExamineTextMindSearching = string.Empty;
|
||||
|
||||
[DataField("examineTextNoMind")]
|
||||
public string ExamineTextNoMind = string.Empty;
|
||||
|
||||
[DataField("beginSearchingText")]
|
||||
public string BeginSearchingText = string.Empty;
|
||||
|
||||
[DataField("roleName")]
|
||||
public string RoleName = string.Empty;
|
||||
|
||||
[DataField("roleDescription")]
|
||||
public string RoleDescription = string.Empty;
|
||||
|
||||
[DataField("wipeVerbText")]
|
||||
public string WipeVerbText = string.Empty;
|
||||
|
||||
[DataField("wipeVerbPopup")]
|
||||
public string WipeVerbPopup = string.Empty;
|
||||
|
||||
[DataField("stopSearchVerbText")]
|
||||
public string StopSearchVerbText = string.Empty;
|
||||
|
||||
[DataField("stopSearchVerbPopup")]
|
||||
public string StopSearchVerbPopup = string.Empty;
|
||||
}
|
||||
140
Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs
Normal file
140
Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.PAI;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Server.Ghost.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// This handles logic and interaction related to <see cref="ToggleableGhostRoleComponent"/>
|
||||
/// </summary>
|
||||
public sealed class ToggleableGhostRoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
//todo this really shouldn't be in here but this system was converted from PAIs
|
||||
[Dependency] private readonly PAISystem _pai = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ToggleableGhostRoleComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<ToggleableGhostRoleComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<ToggleableGhostRoleComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<ToggleableGhostRoleComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
SubscribeLocalEvent<ToggleableGhostRoleComponent, GetVerbsEvent<ActivationVerb>>(AddWipeVerb);
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, ToggleableGhostRoleComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
// check if a mind is present
|
||||
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(component.ExamineTextMindPresent), uid, args.User, PopupType.Large);
|
||||
return;
|
||||
}
|
||||
if (HasComp<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(component.ExamineTextMindSearching), uid, args.User);
|
||||
return;
|
||||
}
|
||||
_popup.PopupEntity(Loc.GetString(component.BeginSearchingText), uid, args.User);
|
||||
|
||||
UpdateAppearance(uid, ToggleableGhostRoleStatus.Searching);
|
||||
|
||||
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
|
||||
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
||||
ghostRole.RoleName = Loc.GetString(component.RoleName);
|
||||
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(component.ExamineTextMindPresent));
|
||||
}
|
||||
else if (HasComp<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(component.ExamineTextMindSearching));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(component.ExamineTextNoMind));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, ToggleableGhostRoleComponent pai, MindAddedMessage args)
|
||||
{
|
||||
// Mind was added, shutdown the ghost role stuff so it won't get in the way
|
||||
RemComp<GhostTakeoverAvailableComponent>(uid);
|
||||
UpdateAppearance(uid, ToggleableGhostRoleStatus.On);
|
||||
}
|
||||
|
||||
private void OnMindRemoved(EntityUid uid, ToggleableGhostRoleComponent component, MindRemovedMessage args)
|
||||
{
|
||||
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, ToggleableGhostRoleStatus status)
|
||||
{
|
||||
_appearance.SetData(uid, ToggleableGhostRoleVisuals.Status, status);
|
||||
}
|
||||
|
||||
private void AddWipeVerb(EntityUid uid, ToggleableGhostRoleComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString(component.WipeVerbText),
|
||||
Act = () =>
|
||||
{
|
||||
if (component.Deleted || !HasComp<MindContainerComponent>(uid))
|
||||
return;
|
||||
// Wiping device :(
|
||||
// The shutdown of the Mind should cause automatic reset of the pAI during OnMindRemoved
|
||||
// EDIT: But it doesn't!!!! Wtf? Do stuff manually
|
||||
RemComp<MindContainerComponent>(uid);
|
||||
_popup.PopupEntity(Loc.GetString(component.WipeVerbPopup), uid, args.User, PopupType.Large);
|
||||
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
|
||||
_pai.PAITurningOff(uid);
|
||||
}
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
else if (HasComp<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString(component.StopSearchVerbText),
|
||||
Act = () =>
|
||||
{
|
||||
if (component.Deleted || !HasComp<GhostTakeoverAvailableComponent>(uid))
|
||||
return;
|
||||
RemComp<GhostTakeoverAvailableComponent>(uid);
|
||||
RemComp<GhostRoleComponent>(uid);
|
||||
_popup.PopupEntity(Loc.GetString(component.StopSearchVerbPopup), uid, args.User);
|
||||
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
|
||||
_pai.PAITurningOff(uid);
|
||||
}
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class HandheldLightSystem : SharedHandheldLightSystem
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
@@ -40,6 +41,9 @@ namespace Content.Server.Light.EntitySystems
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentGetState>(OnGetState);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
|
||||
|
||||
@@ -100,6 +104,24 @@ namespace Content.Server.Light.EntitySystems
|
||||
args.State = new HandheldLightComponent.HandheldLightComponentState(component.Activated, GetLevel(uid, component));
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, HandheldLightComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.ToggleAction == null
|
||||
&& _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act))
|
||||
{
|
||||
component.ToggleAction = new(act);
|
||||
}
|
||||
|
||||
if (component.ToggleAction != null)
|
||||
_actions.AddAction(uid, component.ToggleAction, null);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, HandheldLightComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.ToggleAction != null)
|
||||
_actions.RemoveAction(uid, component.ToggleAction);
|
||||
}
|
||||
|
||||
private byte? GetLevel(EntityUid uid, HandheldLightComponent component)
|
||||
{
|
||||
// Curently every single flashlight has the same number of levels for status and that's all it uses the charge for
|
||||
@@ -121,7 +143,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
|
||||
private void OnActivate(EntityUid uid, HandheldLightComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
if (args.Handled || !component.ToggleOnInteract)
|
||||
return;
|
||||
|
||||
if (ToggleStatus(args.User, uid, component))
|
||||
@@ -176,7 +198,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
|
||||
private void AddToggleLightVerb(EntityUid uid, HandheldLightComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
if (!args.CanAccess || !args.CanInteract || !component.ToggleOnInteract)
|
||||
return;
|
||||
|
||||
ActivationVerb verb = new()
|
||||
|
||||
@@ -86,6 +86,9 @@ public sealed class HealingSystem : EntitySystem
|
||||
// Re-verify that we can heal the damage.
|
||||
_stacks.Use(args.Used.Value, 1);
|
||||
|
||||
if (_stacks.GetCount(args.Used.Value) <= 0)
|
||||
dontRepeat = true;
|
||||
|
||||
if (uid != args.User)
|
||||
{
|
||||
_adminLogger.Add(LogType.Healed,
|
||||
@@ -166,11 +169,8 @@ public sealed class HealingSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (component.HealingBeginSound != null)
|
||||
{
|
||||
_audio.PlayPvs(component.HealingBeginSound, uid,
|
||||
_audio.PlayPvs(component.HealingBeginSound, uid,
|
||||
AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
|
||||
}
|
||||
|
||||
var isNotSelf = user != target;
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ public sealed class MindSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly ActorSystem _actor = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly ActorSystem _actor = default!;
|
||||
|
||||
// This is dictionary is required to track the minds of disconnected players that may have had their entity deleted.
|
||||
private readonly Dictionary<NetUserId, Mind> _userMinds = new();
|
||||
@@ -128,8 +128,8 @@ public sealed class MindSystem : EntitySystem
|
||||
if (!Resolve(uid, ref mind, false))
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(uid, new MindRemovedMessage(), true);
|
||||
mind.Mind = null;
|
||||
RaiseLocalEvent(uid, new MindRemovedMessage(), true);
|
||||
}
|
||||
|
||||
private void OnVisitingTerminating(EntityUid uid, VisitingMindComponent component, ref EntityTerminatingEvent args)
|
||||
@@ -414,10 +414,6 @@ public sealed class MindSystem : EntitySystem
|
||||
InternalEjectMind(oldEntity.Value, oldComp);
|
||||
|
||||
SetOwnedEntity(mind, entity, component);
|
||||
if (mind.OwnedComponent != null){
|
||||
InternalAssignMind(mind.OwnedEntity!.Value, mind, mind.OwnedComponent);
|
||||
mind.OriginalOwnedEntity ??= mind.OwnedEntity;
|
||||
}
|
||||
|
||||
// Don't do the full deletion cleanup if we're transferring to our VisitingEntity
|
||||
if (alreadyAttached)
|
||||
@@ -438,9 +434,15 @@ public sealed class MindSystem : EntitySystem
|
||||
// Player is CURRENTLY connected.
|
||||
if (mind.Session != null && !alreadyAttached && mind.VisitingEntity == null)
|
||||
{
|
||||
mind.Session.AttachToEntity(entity);
|
||||
_actor.Attach(entity, mind.Session, true);
|
||||
Log.Info($"Session {mind.Session.Name} transferred to entity {entity}.");
|
||||
}
|
||||
|
||||
if (mind.OwnedComponent != null)
|
||||
{
|
||||
InternalAssignMind(mind.OwnedEntity!.Value, mind, mind.OwnedComponent);
|
||||
mind.OriginalOwnedEntity ??= mind.OwnedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,7 +561,7 @@ public sealed class MindSystem : EntitySystem
|
||||
public bool TryGetMind(EntityUid uid, [NotNullWhen(true)] out Mind? mind, MindContainerComponent? mindContainerComponent = null)
|
||||
{
|
||||
mind = null;
|
||||
if (!Resolve(uid, ref mindContainerComponent))
|
||||
if (!Resolve(uid, ref mindContainerComponent, false))
|
||||
return false;
|
||||
|
||||
if (!mindContainerComponent.HasMind)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.NameIdentifier;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class NameIdentifierComponent : Component
|
||||
{
|
||||
[DataField("group", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<NameIdentifierGroupPrototype>))]
|
||||
public string Group = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The randomly generated ID for this entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("identifier")]
|
||||
public int Identifier = -1;
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.NameIdentifier;
|
||||
|
||||
@@ -15,12 +13,13 @@ public sealed class NameIdentifierSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Free IDs available per <see cref="NameIdentifierGroupPrototype"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, List<int>> CurrentIds = new();
|
||||
public readonly Dictionary<string, List<int>> CurrentIds = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -103,11 +102,16 @@ public sealed class NameIdentifierSystem : EntitySystem
|
||||
component.Identifier = id;
|
||||
}
|
||||
|
||||
component.FullIdentifier = group.FullName
|
||||
? uniqueName
|
||||
: $"({uniqueName})";
|
||||
|
||||
var meta = MetaData(uid);
|
||||
// "DR-1234" as opposed to "drone (DR-1234)"
|
||||
meta.EntityName = group.FullName
|
||||
_metaData.SetEntityName(uid, group.FullName
|
||||
? uniqueName
|
||||
: $"{meta.EntityName} ({uniqueName})";
|
||||
: $"{meta.EntityName} ({uniqueName})", meta);
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void InitialSetupPrototypes()
|
||||
|
||||
@@ -1,91 +1,45 @@
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Instruments;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.PAI;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.PAI
|
||||
{
|
||||
public sealed class PAISystem : SharedPAISystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PAIComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<PAIComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
SubscribeLocalEvent<PAIComponent, GetVerbsEvent<ActivationVerb>>(AddWipeVerb);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, PAIComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("pai-system-pai-installed"));
|
||||
}
|
||||
else if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("pai-system-still-searching"));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("pai-system-off"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
if (!TryComp<MindContainerComponent>(uid, out var mind) || !mind.HasMind)
|
||||
component.LastUser = args.User;
|
||||
}
|
||||
|
||||
// Placeholder PAIs are essentially portable ghost role generators.
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
// Check for pAI activation
|
||||
if (EntityManager.TryGetComponent<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("pai-system-pai-installed"), uid, args.User, PopupType.Large);
|
||||
private void OnMindAdded(EntityUid uid, PAIComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (component.LastUser == null)
|
||||
return;
|
||||
}
|
||||
else if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("pai-system-still-searching"), uid, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ownership tag
|
||||
string val = Loc.GetString("pai-system-pai-name", ("owner", args.User));
|
||||
var val = Loc.GetString("pai-system-pai-name", ("owner", component.LastUser));
|
||||
|
||||
// TODO Identity? People shouldn't dox-themselves by carrying around a PAI.
|
||||
// But having the pda's name permanently be "old lady's PAI" is weird.
|
||||
// Changing the PAI's identity in a way that ties it to the owner's identity also seems weird.
|
||||
// Cause then you could remotely figure out information about the owner's equipped items.
|
||||
|
||||
EntityManager.GetComponent<MetaDataComponent>(component.Owner).EntityName = val;
|
||||
|
||||
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
|
||||
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
||||
|
||||
ghostRole.RoleName = Loc.GetString("pai-system-role-name");
|
||||
ghostRole.RoleDescription = Loc.GetString("pai-system-role-description");
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("pai-system-searching"), uid, args.User);
|
||||
UpdatePAIAppearance(uid, PAIStatus.Searching);
|
||||
_metaData.SetEntityName(uid, val);
|
||||
}
|
||||
|
||||
private void OnMindRemoved(EntityUid uid, PAIComponent component, MindRemovedMessage args)
|
||||
@@ -94,18 +48,8 @@ namespace Content.Server.PAI
|
||||
PAITurningOff(uid);
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, PAIComponent pai, MindAddedMessage args)
|
||||
public void PAITurningOff(EntityUid uid)
|
||||
{
|
||||
// Mind was added, shutdown the ghost role stuff so it won't get in the way
|
||||
if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
|
||||
EntityManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
|
||||
UpdatePAIAppearance(uid, PAIStatus.On);
|
||||
}
|
||||
|
||||
private void PAITurningOff(EntityUid uid)
|
||||
{
|
||||
UpdatePAIAppearance(uid, PAIStatus.Off);
|
||||
|
||||
// Close the instrument interface if it was open
|
||||
// before closing
|
||||
if (HasComp<ActiveInstrumentComponent>(uid) && TryComp<ActorComponent>(uid, out var actor))
|
||||
@@ -114,63 +58,12 @@ namespace Content.Server.PAI
|
||||
}
|
||||
|
||||
// Stop instrument
|
||||
if (EntityManager.TryGetComponent<InstrumentComponent>(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument);
|
||||
if (EntityManager.TryGetComponent<MetaDataComponent>(uid, out var metadata))
|
||||
if (TryComp<InstrumentComponent>(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument);
|
||||
if (TryComp<MetaDataComponent>(uid, out var metadata))
|
||||
{
|
||||
var proto = metadata.EntityPrototype;
|
||||
if (proto != null)
|
||||
metadata.EntityName = proto.Name;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePAIAppearance(EntityUid uid, PAIStatus status)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
_appearance.SetData(uid, PAIVisuals.Status, status, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddWipeVerb(EntityUid uid, PAIComponent pai, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (EntityManager.TryGetComponent<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
ActivationVerb verb = new();
|
||||
verb.Text = Loc.GetString("pai-system-wipe-device-verb-text");
|
||||
verb.Act = () => {
|
||||
if (pai.Deleted)
|
||||
return;
|
||||
// Wiping device :(
|
||||
// The shutdown of the Mind should cause automatic reset of the pAI during OnMindRemoved
|
||||
// EDIT: But it doesn't!!!! Wtf? Do stuff manually
|
||||
if (EntityManager.HasComponent<MindContainerComponent>(uid))
|
||||
{
|
||||
EntityManager.RemoveComponent<MindContainerComponent>(uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("pai-system-wiped-device"), uid, args.User, PopupType.Large);
|
||||
PAITurningOff(uid);
|
||||
}
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
else if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
ActivationVerb verb = new();
|
||||
verb.Text = Loc.GetString("pai-system-stop-searching-verb-text");
|
||||
verb.Act = () => {
|
||||
if (pai.Deleted)
|
||||
return;
|
||||
if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
|
||||
{
|
||||
EntityManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
|
||||
EntityManager.RemoveComponent<GhostRoleComponent>(uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("pai-system-stopped-searching"), uid, args.User);
|
||||
PAITurningOff(uid);
|
||||
}
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
_metaData.SetEntityName(uid, proto.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Robust.Server.Containers;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class ChargerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly PowerCellSystem _cellSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _sharedAppearanceSystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -27,6 +29,7 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<ChargerComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
|
||||
SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
|
||||
SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
|
||||
}
|
||||
|
||||
@@ -42,15 +45,19 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var (_, charger, slotComp) in EntityManager.EntityQuery<ActiveChargerComponent, ChargerComponent, ItemSlotsComponent>())
|
||||
var query = EntityQueryEnumerator<ActiveChargerComponent, ChargerComponent, ContainerManagerComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var charger, out var containerComp))
|
||||
{
|
||||
if (!_itemSlotsSystem.TryGetSlot(charger.Owner, charger.SlotId, out ItemSlot? slot, slotComp))
|
||||
if (!_container.TryGetContainer(uid, charger.SlotId, out var container, containerComp))
|
||||
continue;
|
||||
|
||||
if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || !slot.HasItem)
|
||||
if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || container.ContainedEntities.Count == 0)
|
||||
continue;
|
||||
|
||||
TransferPower(charger.Owner, slot.Item!.Value, charger, frameTime);
|
||||
foreach (var contained in container.ContainedEntities)
|
||||
{
|
||||
TransferPower(uid, contained, charger, frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,23 +107,32 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
if (args.Container.ID != component.SlotId)
|
||||
return;
|
||||
|
||||
if (!TryComp(args.EntityUid, out PowerCellSlotComponent? cellSlot))
|
||||
if (!TryComp<PowerCellSlotComponent>(args.EntityUid, out var cellSlot))
|
||||
return;
|
||||
|
||||
if (!_itemSlotsSystem.TryGetSlot(args.EntityUid, cellSlot.CellSlotId, out ItemSlot? itemSlot))
|
||||
return;
|
||||
|
||||
if (!cellSlot.FitsInCharger || !itemSlot.HasItem)
|
||||
if (!cellSlot.FitsInCharger)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnEntityStorageInsertAttempt(EntityUid uid, ChargerComponent component, ref InsertIntoEntityStorageAttemptEvent args)
|
||||
{
|
||||
if (!component.Initialized || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlot))
|
||||
return;
|
||||
|
||||
if (!cellSlot.FitsInCharger)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void UpdateStatus(EntityUid uid, ChargerComponent component)
|
||||
{
|
||||
var status = GetStatus(uid, component);
|
||||
if (component.Status == status || !TryComp(uid, out ApcPowerReceiverComponent? receiver))
|
||||
return;
|
||||
|
||||
if (!_itemSlotsSystem.TryGetSlot(uid, component.SlotId, out ItemSlot? slot))
|
||||
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
|
||||
return;
|
||||
|
||||
TryComp(uid, out AppearanceComponent? appearance);
|
||||
@@ -136,25 +152,25 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
{
|
||||
case CellChargerStatus.Off:
|
||||
receiver.Load = 0;
|
||||
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance);
|
||||
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance);
|
||||
break;
|
||||
case CellChargerStatus.Empty:
|
||||
receiver.Load = 0;
|
||||
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance);
|
||||
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance);
|
||||
break;
|
||||
case CellChargerStatus.Charging:
|
||||
receiver.Load = component.ChargeRate;
|
||||
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance);
|
||||
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance);
|
||||
break;
|
||||
case CellChargerStatus.Charged:
|
||||
receiver.Load = 0;
|
||||
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance);
|
||||
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_sharedAppearanceSystem.SetData(uid, CellVisual.Occupied, slot.HasItem, appearance);
|
||||
_appearance.SetData(uid, CellVisual.Occupied, container.ContainedEntities.Count != 0, appearance);
|
||||
}
|
||||
|
||||
private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component)
|
||||
@@ -171,16 +187,16 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
if (!apcPowerReceiverComponent.Powered)
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (!_itemSlotsSystem.TryGetSlot(uid, component.SlotId, out ItemSlot? slot))
|
||||
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (!slot.HasItem)
|
||||
if (container.ContainedEntities.Count == 0)
|
||||
return CellChargerStatus.Empty;
|
||||
|
||||
if (!SearchForBattery(slot.Item!.Value, out BatteryComponent? heldBattery))
|
||||
if (!SearchForBattery(container.ContainedEntities.First(), out var heldBattery))
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (heldBattery != null && Math.Abs(heldBattery.MaxCharge - heldBattery.CurrentCharge) < 0.01)
|
||||
if (Math.Abs(heldBattery.MaxCharge - heldBattery.CurrentCharge) < 0.01)
|
||||
return CellChargerStatus.Charged;
|
||||
|
||||
return CellChargerStatus.Charging;
|
||||
@@ -194,7 +210,7 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
if (!receiverComponent.Powered)
|
||||
return;
|
||||
|
||||
if (!SearchForBattery(targetEntity, out BatteryComponent? heldBattery))
|
||||
if (!SearchForBattery(targetEntity, out var heldBattery))
|
||||
return;
|
||||
|
||||
heldBattery.CurrentCharge += component.ChargeRate * frameTime;
|
||||
@@ -213,7 +229,7 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
if (!TryComp(uid, out component))
|
||||
{
|
||||
// or by checking for a power cell slot on the inserted entity
|
||||
return _cellSystem.TryGetBatteryFromSlot(uid, out component);
|
||||
return _powerCell.TryGetBatteryFromSlot(uid, out component);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,9 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
|
||||
{
|
||||
base.OnCellRemoved(uid, component, args);
|
||||
|
||||
if (args.Container.ID != component.CellSlotId)
|
||||
return;
|
||||
|
||||
var ev = new PowerCellSlotEmptyEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
@@ -135,7 +138,7 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
|
||||
if (!Resolve(uid, ref battery, ref cell, false))
|
||||
return true;
|
||||
|
||||
return HasCharge(uid, float.MinValue, cell, user);
|
||||
return HasCharge(uid, battery.DrawRate, cell, user);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -229,19 +232,26 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
|
||||
|
||||
private void OnCellExamined(EntityUid uid, PowerCellComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (TryComp<BatteryComponent>(uid, out var battery))
|
||||
OnBatteryExamined(uid, battery, args);
|
||||
TryComp<BatteryComponent>(uid, out var battery);
|
||||
OnBatteryExamined(uid, battery, args);
|
||||
}
|
||||
|
||||
private void OnCellSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (TryGetBatteryFromSlot(uid, out var battery))
|
||||
OnBatteryExamined(uid, battery, args);
|
||||
TryGetBatteryFromSlot(uid, out var battery);
|
||||
OnBatteryExamined(uid, battery, args);
|
||||
}
|
||||
|
||||
private void OnBatteryExamined(EntityUid uid, BatteryComponent component, ExaminedEvent args)
|
||||
private void OnBatteryExamined(EntityUid uid, BatteryComponent? component, ExaminedEvent args)
|
||||
{
|
||||
var charge = component.CurrentCharge / component.MaxCharge * 100;
|
||||
args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{charge:F0}")));
|
||||
if (component != null)
|
||||
{
|
||||
var charge = component.CurrentCharge / component.MaxCharge * 100;
|
||||
args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{charge:F0}")));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("power-cell-component-examine-details-no-battery"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,11 @@ namespace Content.Server.Repairable
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("selfRepairPenalty")]
|
||||
public float SelfRepairPenalty = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not an entity is allowed to repair itself.
|
||||
/// </summary>
|
||||
[DataField("allowSelfRepair")]
|
||||
public bool AllowSelfRepair = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,12 @@ namespace Content.Server.Repairable
|
||||
|
||||
// Add a penalty to how long it takes if the user is repairing itself
|
||||
if (args.User == args.Target)
|
||||
{
|
||||
if (!component.AllowSelfRepair)
|
||||
return;
|
||||
|
||||
delay *= component.SelfRepairPenalty;
|
||||
}
|
||||
|
||||
// Run the repairing doafter
|
||||
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent());
|
||||
|
||||
79
Content.Server/Silicons/Borgs/BorgSystem.MMI.cs
Normal file
79
Content.Server/Silicons/Borgs/BorgSystem.MMI.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
public void InitializeMMI()
|
||||
{
|
||||
SubscribeLocalEvent<MMIComponent, ComponentInit>(OnMMIInit);
|
||||
SubscribeLocalEvent<MMIComponent, EntInsertedIntoContainerMessage>(OnMMIEntityInserted);
|
||||
SubscribeLocalEvent<MMIComponent, MindAddedMessage>(OnMMIMindAdded);
|
||||
SubscribeLocalEvent<MMIComponent, MindRemovedMessage>(OnMMIMindRemoved);
|
||||
|
||||
SubscribeLocalEvent<MMILinkedComponent, MindAddedMessage>(OnMMILinkedMindAdded);
|
||||
SubscribeLocalEvent<MMILinkedComponent, EntGotRemovedFromContainerMessage>(OnMMILinkedRemoved);
|
||||
}
|
||||
|
||||
private void OnMMIInit(EntityUid uid, MMIComponent component, ComponentInit args)
|
||||
{
|
||||
if (!TryComp<ItemSlotsComponent>(uid, out var itemSlots))
|
||||
return;
|
||||
|
||||
if (ItemSlots.TryGetSlot(uid, component.BrainSlotId, out var slot, itemSlots))
|
||||
component.BrainSlot = slot;
|
||||
else
|
||||
ItemSlots.AddItemSlot(uid, component.BrainSlotId, component.BrainSlot, itemSlots);
|
||||
}
|
||||
|
||||
private void OnMMIEntityInserted(EntityUid uid, MMIComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.BrainSlotId)
|
||||
return;
|
||||
|
||||
var ent = args.Entity;
|
||||
var linked = EnsureComp<MMILinkedComponent>(ent);
|
||||
linked.LinkedMMI = uid;
|
||||
|
||||
if (_mind.TryGetMind(ent, out var mind))
|
||||
_mind.TransferTo(mind, uid, true);
|
||||
|
||||
_appearance.SetData(uid, MMIVisuals.BrainPresent, true);
|
||||
}
|
||||
|
||||
private void OnMMIMindAdded(EntityUid uid, MMIComponent component, MindAddedMessage args)
|
||||
{
|
||||
_appearance.SetData(uid, MMIVisuals.HasMind, true);
|
||||
}
|
||||
|
||||
private void OnMMIMindRemoved(EntityUid uid, MMIComponent component, MindRemovedMessage args)
|
||||
{
|
||||
_appearance.SetData(uid, MMIVisuals.HasMind, false);
|
||||
}
|
||||
|
||||
private void OnMMILinkedMindAdded(EntityUid uid, MMILinkedComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (!_mind.TryGetMind(uid, out var mind) || component.LinkedMMI == null)
|
||||
return;
|
||||
_mind.TransferTo(mind, component.LinkedMMI, true);
|
||||
}
|
||||
|
||||
private void OnMMILinkedRemoved(EntityUid uid, MMILinkedComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (Terminating(uid))
|
||||
return;
|
||||
|
||||
if (component.LinkedMMI is not { } linked)
|
||||
return;
|
||||
RemComp(uid, component);
|
||||
|
||||
if (_mind.TryGetMind(linked, out var mind))
|
||||
_mind.TransferTo(mind, uid, true);
|
||||
|
||||
_appearance.SetData(linked, MMIVisuals.BrainPresent, false);
|
||||
}
|
||||
}
|
||||
316
Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
Normal file
316
Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
public void InitializeModules()
|
||||
{
|
||||
SubscribeLocalEvent<BorgModuleComponent, EntGotInsertedIntoContainerMessage>(OnModuleGotInserted);
|
||||
SubscribeLocalEvent<BorgModuleComponent, EntGotRemovedFromContainerMessage>(OnModuleGotRemoved);
|
||||
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleInstalledEvent>(OnSelectableInstalled);
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleUninstalledEvent>(OnSelectableUninstalled);
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleActionSelectedEvent>(OnSelectableAction);
|
||||
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, ComponentStartup>(OnProvideItemStartup);
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, BorgModuleSelectedEvent>(OnItemModuleSelected);
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, BorgModuleUnselectedEvent>(OnItemModuleUnselected);
|
||||
}
|
||||
|
||||
private void OnModuleGotInserted(EntityUid uid, BorgModuleComponent component, EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
var chassis = args.Container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer ||
|
||||
!chassisComp.Activated)
|
||||
return;
|
||||
|
||||
if (!_powerCell.HasDrawCharge(uid))
|
||||
return;
|
||||
|
||||
InstallModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
|
||||
private void OnModuleGotRemoved(EntityUid uid, BorgModuleComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
var chassis = args.Container.Owner;
|
||||
|
||||
if (Terminating(chassis))
|
||||
return;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer)
|
||||
return;
|
||||
|
||||
UninstallModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
|
||||
private void OnProvideItemStartup(EntityUid uid, ItemBorgModuleComponent component, ComponentStartup args)
|
||||
{
|
||||
component.ProvidedContainer = Container.EnsureContainer<Container>(uid, component.ProvidedContainerId);
|
||||
}
|
||||
|
||||
private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
component.ModuleSwapAction.EntityIcon = uid;
|
||||
_actions.AddAction(chassis, component.ModuleSwapAction, uid);
|
||||
SelectModule(chassis, uid, moduleComp: component);
|
||||
}
|
||||
|
||||
private void OnSelectableUninstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleUninstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
_actions.RemoveProvidedActions(chassis, uid);
|
||||
UnselectModule(chassis, uid, moduleComp: component);
|
||||
}
|
||||
|
||||
private void OnSelectableAction(EntityUid uid, SelectableBorgModuleComponent component, BorgModuleActionSelectedEvent args)
|
||||
{
|
||||
var chassis = args.Performer;
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == uid)
|
||||
{
|
||||
UnselectModule(chassis, chassisComp.SelectedModule, chassisComp);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
UnselectModule(chassis, chassisComp.SelectedModule, chassisComp);
|
||||
SelectModule(chassis, uid, chassisComp, component);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a module, enablind the borg to use its provided abilities.
|
||||
/// </summary>
|
||||
public void SelectModule(EntityUid chassis,
|
||||
EntityUid moduleUid,
|
||||
BorgChassisComponent? chassisComp = null,
|
||||
SelectableBorgModuleComponent? moduleComp = null)
|
||||
{
|
||||
if (Terminating(chassis) || Deleted(chassis))
|
||||
return;
|
||||
|
||||
if (!Resolve(chassis, ref chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule != null)
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == moduleUid)
|
||||
return;
|
||||
|
||||
if (!Resolve(moduleUid, ref moduleComp, false))
|
||||
return;
|
||||
|
||||
var ev = new BorgModuleSelectedEvent(chassis);
|
||||
RaiseLocalEvent(moduleUid, ref ev);
|
||||
chassisComp.SelectedModule = moduleUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unselects a module, removing its provided abilities
|
||||
/// </summary>
|
||||
public void UnselectModule(EntityUid chassis,
|
||||
EntityUid? moduleUid,
|
||||
BorgChassisComponent? chassisComp = null,
|
||||
SelectableBorgModuleComponent? moduleComp = null)
|
||||
{
|
||||
if (Terminating(chassis) || Deleted(chassis))
|
||||
return;
|
||||
|
||||
if (!Resolve(chassis, ref chassisComp))
|
||||
return;
|
||||
|
||||
if (moduleUid == null)
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule != moduleUid)
|
||||
return;
|
||||
|
||||
if (!Resolve(moduleUid.Value, ref moduleComp, false))
|
||||
return;
|
||||
|
||||
var ev = new BorgModuleUnselectedEvent(chassis);
|
||||
RaiseLocalEvent(moduleUid.Value, ref ev);
|
||||
chassisComp.SelectedModule = null;
|
||||
}
|
||||
|
||||
private void OnItemModuleSelected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleSelectedEvent args)
|
||||
{
|
||||
ProvideItems(args.Chassis, uid, component: component);
|
||||
}
|
||||
|
||||
private void OnItemModuleUnselected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleUnselectedEvent args)
|
||||
{
|
||||
RemoveProvidedItems(args.Chassis, uid, component: component);
|
||||
}
|
||||
|
||||
private void ProvideItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassisComponent) || !Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
var xform = Transform(chassis);
|
||||
foreach (var itemProto in component.Items)
|
||||
{
|
||||
EntityUid item;
|
||||
|
||||
if (!component.ItemsCreated)
|
||||
{
|
||||
item = Spawn(itemProto, xform.Coordinates);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = component.ProvidedContainer.ContainedEntities
|
||||
.FirstOrDefault(ent => Prototype(ent)?.ID == itemProto);
|
||||
if (!item.IsValid())
|
||||
{
|
||||
Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}");
|
||||
continue;
|
||||
}
|
||||
|
||||
component.ProvidedContainer.Remove(item, EntityManager, force: true);
|
||||
}
|
||||
|
||||
if (!item.IsValid())
|
||||
{
|
||||
Log.Debug("no valid item");
|
||||
continue;
|
||||
}
|
||||
|
||||
var handId = $"{uid}-item{component.HandCounter}";
|
||||
component.HandCounter++;
|
||||
_hands.AddHand(chassis, handId, HandLocation.Middle, hands);
|
||||
_hands.DoPickup(chassis, hands.Hands[handId], item, hands);
|
||||
EnsureComp<UnremoveableComponent>(item);
|
||||
component.ProvidedItems.Add(handId, item);
|
||||
}
|
||||
|
||||
component.ItemsCreated = true;
|
||||
}
|
||||
|
||||
private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassisComponent) || !Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
foreach (var (handId, item) in component.ProvidedItems)
|
||||
{
|
||||
if (!Deleted(item) && !Terminating(item))
|
||||
{
|
||||
RemComp<UnremoveableComponent>(item);
|
||||
component.ProvidedContainer.Insert(item, EntityManager);
|
||||
}
|
||||
_hands.RemoveHand(chassis, handId, hands);
|
||||
}
|
||||
component.ProvidedItems.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given module can be inserted into a borg
|
||||
/// </summary>
|
||||
public bool CanInsertModule(EntityUid uid, EntityUid module, BorgChassisComponent? component = null, BorgModuleComponent? moduleComponent = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(module, ref moduleComponent))
|
||||
return false;
|
||||
|
||||
if (component.ModuleContainer.ContainedEntities.Count >= component.MaxModules)
|
||||
return false;
|
||||
|
||||
if (component.ModuleWhitelist?.IsValid(module, EntityManager) == false)
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-whitelist-deny"), uid, user.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs and activates all modules currently inside the borg's module container
|
||||
/// </summary>
|
||||
public void InstallAllModules(EntityUid uid, BorgChassisComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var query = GetEntityQuery<BorgModuleComponent>();
|
||||
foreach (var moduleEnt in new List<EntityUid>(component.ModuleContainer.ContainedEntities))
|
||||
{
|
||||
if (!query.TryGetComponent(moduleEnt, out var moduleComp))
|
||||
continue;
|
||||
|
||||
InstallModule(uid, moduleEnt, component, moduleComp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates all modules currently inside the borg's module container
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
public void DisableAllModules(EntityUid uid, BorgChassisComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var query = GetEntityQuery<BorgModuleComponent>();
|
||||
foreach (var moduleEnt in new List<EntityUid>(component.ModuleContainer.ContainedEntities))
|
||||
{
|
||||
if (!query.TryGetComponent(moduleEnt, out var moduleComp))
|
||||
continue;
|
||||
|
||||
UninstallModule(uid, moduleEnt, component, moduleComp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs a single module into a borg.
|
||||
/// </summary>
|
||||
public void InstallModule(EntityUid uid, EntityUid module, BorgChassisComponent? component, BorgModuleComponent? moduleComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(module, ref moduleComponent))
|
||||
return;
|
||||
|
||||
if (moduleComponent.Installed)
|
||||
return;
|
||||
|
||||
moduleComponent.InstalledEntity = uid;
|
||||
var ev = new BorgModuleInstalledEvent(uid);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls a single module from a borg.
|
||||
/// </summary>
|
||||
public void UninstallModule(EntityUid uid, EntityUid module, BorgChassisComponent? component, BorgModuleComponent? moduleComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(module, ref moduleComponent))
|
||||
return;
|
||||
|
||||
if (!moduleComponent.Installed)
|
||||
return;
|
||||
|
||||
moduleComponent.InstalledEntity = null;
|
||||
var ev = new BorgModuleUninstalledEvent(uid);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
}
|
||||
108
Content.Server/Silicons/Borgs/BorgSystem.Ui.cs
Normal file
108
Content.Server/Silicons/Borgs/BorgSystem.Ui.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Linq;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
public void InitializeUI()
|
||||
{
|
||||
SubscribeLocalEvent<BorgChassisComponent, BeforeActivatableUIOpenEvent>(OnBeforeBorgUiOpen);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBrainBuiMessage>(OnEjectBrainBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBatteryBuiMessage>(OnEjectBatteryBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgSetNameBuiMessage>(OnSetNameBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgRemoveModuleBuiMessage>(OnRemoveModuleBuiMessage);
|
||||
}
|
||||
|
||||
private void OnBeforeBorgUiOpen(EntityUid uid, BorgChassisComponent component, BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnEjectBrainBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBrainBuiMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not { } attachedEntity || component.BrainEntity is not { } brain)
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(attachedEntity):player} removed brain {ToPrettyString(brain)} from borg {ToPrettyString(uid)}");
|
||||
component.BrainContainer.Remove(brain, EntityManager);
|
||||
_hands.TryPickupAnyHand(attachedEntity, brain);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnEjectBatteryBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBatteryBuiMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not { } attachedEntity ||
|
||||
!TryComp<PowerCellSlotComponent>(uid, out var slotComp) ||
|
||||
!Container.TryGetContainer(uid, slotComp.CellSlotId, out var container) ||
|
||||
!container.ContainedEntities.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ents = Container.EmptyContainer(container);
|
||||
_hands.TryPickupAnyHand(attachedEntity, ents.First());
|
||||
}
|
||||
|
||||
private void OnSetNameBuiMessage(EntityUid uid, BorgChassisComponent component, BorgSetNameBuiMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not { } attachedEntity)
|
||||
return;
|
||||
|
||||
if (args.Name.Length > HumanoidCharacterProfile.MaxNameLength ||
|
||||
args.Name.Length == 0 ||
|
||||
string.IsNullOrWhiteSpace(args.Name) ||
|
||||
string.IsNullOrEmpty(args.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args.Name.Trim();
|
||||
if (TryComp<NameIdentifierComponent>(uid, out var identifier))
|
||||
name = $"{name} {identifier.FullIdentifier}";
|
||||
|
||||
_metaData.SetEntityName(uid, name);
|
||||
_adminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(attachedEntity):player} set borg \"{ToPrettyString(uid)}\"'s name to: {name}");
|
||||
}
|
||||
|
||||
private void OnRemoveModuleBuiMessage(EntityUid uid, BorgChassisComponent component, BorgRemoveModuleBuiMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not { } attachedEntity)
|
||||
return;
|
||||
|
||||
if (!component.ModuleContainer.Contains(args.Module))
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(attachedEntity):player} removed module {ToPrettyString(args.Module)} from borg {ToPrettyString(uid)}");
|
||||
component.ModuleContainer.Remove(args.Module);
|
||||
_hands.TryPickupAnyHand(attachedEntity, args.Module);
|
||||
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
public void UpdateUI(EntityUid uid, BorgChassisComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var chargePercent = 0f;
|
||||
var hasBattery = false;
|
||||
if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
|
||||
{
|
||||
hasBattery = true;
|
||||
chargePercent = battery.Charge / battery.MaxCharge;
|
||||
}
|
||||
|
||||
var state = new BorgBuiState(chargePercent, hasBattery);
|
||||
_ui.TrySetUiState(uid, BorgUiKey.Key, state);
|
||||
}
|
||||
}
|
||||
318
Content.Server/Silicons/Borgs/BorgSystem.cs
Normal file
318
Content.Server/Silicons/Borgs/BorgSystem.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IBanManager _banManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private readonly PlayTimeTrackingSystem _playTimeTracking = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BorgChassisComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<BorgChassisComponent, AfterInteractUsingEvent>(OnChassisInteractUsing);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
|
||||
|
||||
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
|
||||
|
||||
InitializeModules();
|
||||
InitializeMMI();
|
||||
InitializeUI();
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
|
||||
{
|
||||
UpdateBatteryAlert(uid);
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
|
||||
var coordinates = Transform(uid).Coordinates;
|
||||
|
||||
if (component.StartingBrain != null)
|
||||
{
|
||||
component.BrainContainer.Insert(Spawn(component.StartingBrain, coordinates), EntityManager);
|
||||
}
|
||||
|
||||
foreach (var startingModule in component.StartingModules)
|
||||
{
|
||||
component.ModuleContainer.Insert(Spawn(startingModule, coordinates), EntityManager);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChassisInteractUsing(EntityUid uid, BorgChassisComponent component, AfterInteractUsingEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Handled || uid == args.User)
|
||||
return;
|
||||
|
||||
var used = args.Used;
|
||||
TryComp<BorgBrainComponent>(used, out var brain);
|
||||
TryComp<BorgModuleComponent>(used, out var module);
|
||||
|
||||
if (TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
|
||||
{
|
||||
if (brain != null || module != null)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-panel-not-open"), uid, args.User);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.BrainEntity == null &&
|
||||
brain != null &&
|
||||
component.BrainWhitelist?.IsValid(used) != false)
|
||||
{
|
||||
if (_mind.TryGetMind(used, out var mind) && mind.Session != null)
|
||||
{
|
||||
if (!CanPlayerBeBorgged(mind.Session, component))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed"), used, args.User);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
component.BrainContainer.Insert(used);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} installed brain {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
|
||||
args.Handled = true;
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
if (module != null && CanInsertModule(uid, used, component, module, args.User))
|
||||
{
|
||||
component.ModuleContainer.Insert(used);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
|
||||
args.Handled = true;
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: consider transferring over the ghost role? managing that might suck.
|
||||
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
base.OnInserted(uid, component, args);
|
||||
|
||||
if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(args.Entity, out var mind))
|
||||
{
|
||||
_mind.TransferTo(mind, uid);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
base.OnRemoved(uid, component, args);
|
||||
|
||||
if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(uid, out var mind))
|
||||
{
|
||||
_mind.TransferTo(mind, args.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, BorgChassisComponent component, MindAddedMessage args)
|
||||
{
|
||||
BorgActivate(uid, component);
|
||||
}
|
||||
|
||||
private void OnMindRemoved(EntityUid uid, BorgChassisComponent component, MindRemovedMessage args)
|
||||
{
|
||||
BorgDeactivate(uid, component);
|
||||
}
|
||||
|
||||
private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args)
|
||||
{
|
||||
UpdateBatteryAlert(uid);
|
||||
|
||||
if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
|
||||
return;
|
||||
|
||||
// if we eject the battery or run out of charge, then disable
|
||||
if (args.Ejected || !_powerCell.HasDrawCharge(uid))
|
||||
{
|
||||
DisableBorgAbilities(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
|
||||
if (_powerCell.HasDrawCharge(uid, draw))
|
||||
{
|
||||
// only reenable the powerdraw if a player has the role.
|
||||
if (!draw.Drawing && _mind.TryGetMind(uid, out _))
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, true);
|
||||
|
||||
EnableBorgAbilities(uid, component);
|
||||
}
|
||||
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
|
||||
{
|
||||
DisableBorgAbilities(uid, component);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
// borgs can't view their own ui
|
||||
if (args.User == uid)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
|
||||
return;
|
||||
|
||||
var containerEnt = container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(containerEnt, out var chassisComponent) ||
|
||||
container.ID != chassisComponent.BrainContainerId)
|
||||
return;
|
||||
|
||||
if (!_mind.TryGetMind(uid, out var mind) || mind.Session == null)
|
||||
return;
|
||||
|
||||
if (!CanPlayerBeBorgged(mind.Session, chassisComponent))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed-eject"), uid);
|
||||
Container.RemoveEntity(containerEnt, uid);
|
||||
_throwing.TryThrow(uid, _random.NextVector2() * 5, 5f);
|
||||
return;
|
||||
}
|
||||
|
||||
_mind.TransferTo(mind, containerEnt);
|
||||
}
|
||||
|
||||
private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null)
|
||||
{
|
||||
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent))
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.BorgBattery);
|
||||
_alerts.ShowAlert(uid, AlertType.BorgBatteryNone);
|
||||
return;
|
||||
}
|
||||
|
||||
var chargePercent = (short) MathF.Round(battery.CurrentCharge / battery.MaxCharge * 10f);
|
||||
|
||||
// we make sure 0 only shows if they have absolutely no battery.
|
||||
// also account for floating point imprecision
|
||||
if (chargePercent == 0 && _powerCell.HasDrawCharge(uid, cell: slotComponent))
|
||||
{
|
||||
chargePercent = 1;
|
||||
}
|
||||
|
||||
_alerts.ClearAlert(uid, AlertType.BorgBatteryNone);
|
||||
_alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the borg, enabling all of its modules.
|
||||
/// </summary>
|
||||
public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
if (component.Activated)
|
||||
return;
|
||||
|
||||
component.Activated = true;
|
||||
InstallAllModules(uid, component);
|
||||
Dirty(component);
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the borg, disabling all of its modules and decreasing its speed.
|
||||
/// </summary>
|
||||
public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
if (!component.Activated)
|
||||
return;
|
||||
|
||||
component.Activated = false;
|
||||
DisableAllModules(uid, component);
|
||||
Dirty(component);
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates a borg when a player occupies it
|
||||
/// </summary>
|
||||
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
component.HasPlayer = true;
|
||||
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, true);
|
||||
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates a borg when a player leaves it.
|
||||
/// </summary>
|
||||
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
component.HasPlayer = false;
|
||||
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, false);
|
||||
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a player has fulfilled the requirements for the borg job.
|
||||
/// If they don't have enough hours, they cannot be placed into a chassis.
|
||||
/// </summary>
|
||||
public bool CanPlayerBeBorgged(IPlayerSession session, BorgChassisComponent component)
|
||||
{
|
||||
var disallowedJobs = _playTimeTracking.GetDisallowedJobs(session);
|
||||
|
||||
if (disallowedJobs.Contains(component.BorgJobId))
|
||||
return false;
|
||||
|
||||
if (_banManager.GetJobBans(session.UserId)?.Contains(component.BorgJobId) == true)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
157
Content.Server/Silicons/Laws/SiliconLawSystem.cs
Normal file
157
Content.Server/Silicons/Laws/SiliconLawSystem.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Silicons.Laws;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, ToggleLawsScreenEvent>(OnToggleLawsScreen);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
|
||||
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, GetSiliconLawsEvent>(OnDirectedGetLaws);
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, GetSiliconLawsEvent>(OnDirectedEmagGetLaws);
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnComponentStartup(EntityUid uid, SiliconLawBoundComponent component, ComponentStartup args)
|
||||
{
|
||||
component.ProvidedAction = new (_prototype.Index<InstantActionPrototype>(component.ViewLawsAction));
|
||||
_actions.AddAction(uid, component.ProvidedAction, null);
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(EntityUid uid, SiliconLawBoundComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.ProvidedAction != null)
|
||||
_actions.RemoveAction(uid, component.ProvidedAction);
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, SiliconLawBoundComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
|
||||
var msg = Loc.GetString("laws-notify");
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false,
|
||||
actor.PlayerSession.ConnectedClient, colorOverride: Color.FromHex("#2ed2fd"));
|
||||
}
|
||||
|
||||
private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent component, ToggleLawsScreenEvent args)
|
||||
{
|
||||
if (args.Handled || !TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
_userInterface.TryToggleUi(uid, SiliconLawsUiKey.Key, actor.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args)
|
||||
{
|
||||
var state = new SiliconLawBuiState(GetLaws(uid));
|
||||
_userInterface.TrySetUiState(args.Entity, SiliconLawsUiKey.Key, state, (IPlayerSession) args.Session);
|
||||
}
|
||||
|
||||
private void OnDirectedGetLaws(EntityUid uid, SiliconLawProviderComponent component, ref GetSiliconLawsEvent args)
|
||||
{
|
||||
if (args.Handled || HasComp<EmaggedComponent>(uid) || component.Laws.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var law in component.Laws)
|
||||
{
|
||||
args.Laws.Add(_prototype.Index<SiliconLawPrototype>(law));
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDirectedEmagGetLaws(EntityUid uid, EmagSiliconLawComponent component, ref GetSiliconLawsEvent args)
|
||||
{
|
||||
if (args.Handled || !HasComp<EmaggedComponent>(uid) || component.OwnerName == null)
|
||||
return;
|
||||
|
||||
args.Laws.Add(new SiliconLaw
|
||||
{
|
||||
LawString = Loc.GetString("law-emag-custom", ("name", component.OwnerName)),
|
||||
Order = 0
|
||||
});
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, EmagSiliconLawComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange || !HasComp<EmaggedComponent>(uid))
|
||||
return;
|
||||
|
||||
args.PushMarkup(Loc.GetString("laws-compromised-examine"));
|
||||
}
|
||||
|
||||
protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
base.OnGotEmagged(uid, component, ref args);
|
||||
NotifyLawsChanged(uid);
|
||||
}
|
||||
|
||||
public List<SiliconLaw> GetLaws(EntityUid uid)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
|
||||
var ev = new GetSiliconLawsEvent(uid);
|
||||
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.Handled)
|
||||
return ev.Laws;
|
||||
|
||||
if (_station.GetOwningStation(uid, xform) is { } station)
|
||||
{
|
||||
RaiseLocalEvent(station, ref ev);
|
||||
if (ev.Handled)
|
||||
return ev.Laws;
|
||||
}
|
||||
|
||||
if (xform.GridUid is { } grid)
|
||||
{
|
||||
RaiseLocalEvent(grid, ref ev);
|
||||
if (ev.Handled)
|
||||
return ev.Laws;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(ref ev);
|
||||
return ev.Laws;
|
||||
}
|
||||
|
||||
public void NotifyLawsChanged(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
|
||||
var msg = Loc.GetString("laws-update-notify");
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.ConnectedClient, colorOverride: Color.FromHex("#2ed2fd"));
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Content.Server.Stack
|
||||
base.SetCount(uid, amount, component);
|
||||
|
||||
// Queue delete stack if count reaches zero.
|
||||
if (component.Count <= 0)
|
||||
if (component.Count <= 0 && !component.Lingering)
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace Content.Server.Strip
|
||||
|
||||
if (!_inventorySystem.CanUnequip(user, target, slot, out var reason))
|
||||
{
|
||||
_popup.PopupCursor(reason, user);
|
||||
_popup.PopupCursor(Loc.GetString(reason), user);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
15
Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs
Normal file
15
Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for activatable UIs that require the entity to have a panel in a certain state.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActivatableUIRequiresPanelComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// TRUE: the panel must be open to access the UI.
|
||||
/// FALSE: the panel must be closed to access the UI.
|
||||
/// </summary>
|
||||
[DataField("requireOpen"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool RequireOpen = true;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -55,6 +56,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
||||
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
|
||||
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
|
||||
SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
|
||||
}
|
||||
|
||||
private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
|
||||
@@ -494,6 +496,15 @@ public sealed class WiresSystem : SharedWiresSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled || !TryComp<WiresPanelComponent>(uid, out var wires))
|
||||
return;
|
||||
|
||||
if (component.RequireOpen != wires.Open)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(component.LayoutId))
|
||||
|
||||
@@ -21,7 +21,6 @@ using Content.Server.NPC;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Server.NPC.HTN;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
@@ -30,12 +29,10 @@ using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Zombies
|
||||
{
|
||||
@@ -87,7 +84,7 @@ namespace Content.Server.Zombies
|
||||
public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null)
|
||||
{
|
||||
//Don't zombfiy zombies
|
||||
if (HasComp<ZombieComponent>(target))
|
||||
if (HasComp<ZombieComponent>(target) || HasComp<ZombieImmuneComponent>(target))
|
||||
return;
|
||||
|
||||
if (!Resolve(target, ref mobState, logMissing: false))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Access.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Shared.Access.Components
|
||||
@@ -34,4 +35,16 @@ namespace Content.Shared.Access.Components
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
|
||||
{
|
||||
public void AddGroup(string group)
|
||||
{
|
||||
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
|
||||
return;
|
||||
|
||||
Tags.UnionWith(groupPrototype.Tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ using Robust.Shared.GameStates;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Access.Systems;
|
||||
|
||||
public sealed class AccessReaderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -272,23 +274,24 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
/// Try to find <see cref="AccessComponent"/> on this item
|
||||
/// or inside this item (if it's pda)
|
||||
/// </summary>
|
||||
private bool FindAccessTagsItem(EntityUid uid, [NotNullWhen(true)] out HashSet<string>? tags)
|
||||
private bool FindAccessTagsItem(EntityUid uid, out HashSet<string> tags)
|
||||
{
|
||||
tags = new();
|
||||
if (TryComp(uid, out AccessComponent? access))
|
||||
{
|
||||
tags = access.Tags;
|
||||
return true;
|
||||
tags.UnionWith(access.Tags);
|
||||
}
|
||||
|
||||
if (TryComp(uid, out PdaComponent? pda) &&
|
||||
pda.ContainedId is { Valid: true } id)
|
||||
{
|
||||
tags = EntityManager.GetComponent<AccessComponent>(id).Tags;
|
||||
return true;
|
||||
tags.UnionWith(EntityManager.GetComponent<AccessComponent>(id).Tags);
|
||||
}
|
||||
|
||||
tags = null;
|
||||
return false;
|
||||
var ev = new GetAccessTagsEvent(tags, _prototype);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
return tags.Count != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,5 +15,6 @@ public enum AlertCategory
|
||||
Piloting,
|
||||
Hunger,
|
||||
Thirst,
|
||||
Toxins
|
||||
Toxins,
|
||||
Battery
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Content.Shared.Alert
|
||||
HumanCrit,
|
||||
HumanDead,
|
||||
HumanHealth,
|
||||
BorgBattery,
|
||||
BorgBatteryNone,
|
||||
PilotingShuttle,
|
||||
Peckish,
|
||||
Starving,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Construction.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for construction which requires a set of
|
||||
/// entities with specific tags to be inserted into another entity.
|
||||
/// todo: in a pr that isn't 6k loc, combine this with MechAssemblyComponent
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class PartAssemblyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A dictionary of a set of parts to a list of tags for each assembly.
|
||||
/// </summary>
|
||||
[DataField("parts", required: true)]
|
||||
public Dictionary<string, List<string>> Parts = new();
|
||||
|
||||
/// <summary>
|
||||
/// The entry in <see cref="Parts"/> that is currently being worked on.
|
||||
/// </summary>
|
||||
[DataField("currentAssembly")]
|
||||
public string? CurrentAssembly;
|
||||
|
||||
/// <summary>
|
||||
/// The container where the parts are stored
|
||||
/// </summary>
|
||||
[DataField("containerId")]
|
||||
public string ContainerId = "part-container";
|
||||
|
||||
/// <summary>
|
||||
/// The container that stores all of the parts when
|
||||
/// they're being assembled.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Container PartsContainer = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a valid part is inserted into the part assembly.
|
||||
/// </summary>
|
||||
public sealed class PartAssemblyPartInsertedEvent
|
||||
{
|
||||
|
||||
}
|
||||
147
Content.Shared/Construction/PartAssemblySystem.cs
Normal file
147
Content.Shared/Construction/PartAssemblySystem.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Construction;
|
||||
|
||||
/// <summary>
|
||||
/// This handles <see cref="PartAssemblyComponent"/>
|
||||
/// </summary>
|
||||
public sealed class PartAssemblySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PartAssemblyComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PartAssemblyComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<PartAssemblyComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PartAssemblyComponent component, ComponentInit args)
|
||||
{
|
||||
component.PartsContainer = _container.EnsureContainer<Container>(uid, component.ContainerId);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, PartAssemblyComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (!TryInsertPart(args.Used, uid, component))
|
||||
return;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnEntRemoved(EntityUid uid, PartAssemblyComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ContainerId)
|
||||
return;
|
||||
if (component.PartsContainer.ContainedEntities.Count != 0)
|
||||
return;
|
||||
component.CurrentAssembly = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to insert a part into the current assembly, starting one if there is none.
|
||||
/// </summary>
|
||||
public bool TryInsertPart(EntityUid part, EntityUid uid, PartAssemblyComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
string? assemblyId = null;
|
||||
assemblyId ??= component.CurrentAssembly;
|
||||
|
||||
if (assemblyId == null)
|
||||
{
|
||||
foreach (var (id, tags) in component.Parts)
|
||||
{
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
if (!_tag.HasTag(part, tag))
|
||||
continue;
|
||||
assemblyId = id;
|
||||
break;
|
||||
}
|
||||
|
||||
if (assemblyId != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (assemblyId == null)
|
||||
return false;
|
||||
|
||||
if (!IsPartValid(uid, part, assemblyId, component))
|
||||
return false;
|
||||
|
||||
component.CurrentAssembly = assemblyId;
|
||||
component.PartsContainer.Insert(part);
|
||||
var ev = new PartAssemblyPartInsertedEvent();
|
||||
RaiseLocalEvent(uid, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given entity is a valid item for the assembly.
|
||||
/// </summary>
|
||||
public bool IsPartValid(EntityUid uid, EntityUid part, string assemblyId, PartAssemblyComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return true;
|
||||
|
||||
if (!component.Parts.TryGetValue(assemblyId, out var tags))
|
||||
return false;
|
||||
|
||||
var openTags = new List<string>(tags);
|
||||
var contained = new List<EntityUid>(component.PartsContainer.ContainedEntities);
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
foreach (var ent in component.PartsContainer.ContainedEntities)
|
||||
{
|
||||
if (!contained.Contains(ent) || !_tag.HasTag(ent, tag))
|
||||
continue;
|
||||
openTags.Remove(tag);
|
||||
contained.Remove(ent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tag in openTags)
|
||||
{
|
||||
if (_tag.HasTag(part, tag))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsAssemblyFinished(EntityUid uid, string assemblyId, PartAssemblyComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return true;
|
||||
|
||||
if (!component.Parts.TryGetValue(assemblyId, out var parts))
|
||||
return false;
|
||||
|
||||
var contained = new List<EntityUid>(component.PartsContainer.ContainedEntities);
|
||||
foreach (var tag in parts)
|
||||
{
|
||||
var valid = false;
|
||||
foreach (var ent in new List<EntityUid>(contained))
|
||||
{
|
||||
if (!_tag.HasTag(ent, tag))
|
||||
continue;
|
||||
valid = true;
|
||||
contained.Remove(ent);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,11 @@ namespace Content.Shared.Construction.Steps
|
||||
return typeof(TemperatureConstructionGraphStep);
|
||||
}
|
||||
|
||||
if (node.Has("assemblyId") || node.Has("guideString"))
|
||||
{
|
||||
return typeof(PartAssemblyConstructionGraphStep);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Examine;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Construction.Steps;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed class PartAssemblyConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
/// <summary>
|
||||
/// A valid ID on <see cref="PartAssemblyComponent"/>'s dictionary of strings to part lists.
|
||||
/// </summary>
|
||||
[DataField("assemblyId")]
|
||||
public string AssemblyId = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// A localization string used for
|
||||
/// </summary>
|
||||
[DataField("guideString")]
|
||||
public string GuideString = "construction-guide-condition-part-assembly";
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
{
|
||||
return entityManager.System<PartAssemblySystem>().IsAssemblyFinished(uid, AssemblyId);
|
||||
}
|
||||
|
||||
public override void DoExamine(ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(GuideString));
|
||||
}
|
||||
|
||||
public override ConstructionGuideEntry GenerateGuideEntry()
|
||||
{
|
||||
return new ConstructionGuideEntry
|
||||
{
|
||||
Localization = GuideString,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -233,4 +233,16 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
Priority = other.Priority;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on the slot entity and the item being inserted to determine if an item can be inserted into an item slot.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ItemSlotInsertAttemptEvent(EntityUid SlotEntity, EntityUid Item, EntityUid? User, ItemSlot Slot, bool Cancelled = false);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on the slot entity and the item being inserted to determine if an item can be ejected from an item slot.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ItemSlotEjectAttemptEvent(EntityUid SlotEntity, EntityUid Item, EntityUid? User, ItemSlot Slot, bool Cancelled = false);
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (!slot.InsertOnInteract)
|
||||
continue;
|
||||
|
||||
if (!CanInsert(uid, args.Used, slot, swap: slot.Swap, popup: args.User))
|
||||
if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User))
|
||||
continue;
|
||||
|
||||
// Drop the held item onto the floor. Return if the user cannot drop.
|
||||
@@ -244,7 +244,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// If a popup entity is given, and if the item slot is set to generate a popup message when it fails to
|
||||
/// pass the whitelist, then this will generate a popup.
|
||||
/// </remarks>
|
||||
public bool CanInsert(EntityUid uid, EntityUid usedUid, ItemSlot slot, bool swap = false, EntityUid? popup = null)
|
||||
public bool CanInsert(EntityUid uid, EntityUid usedUid, EntityUid? user, ItemSlot slot, bool swap = false, EntityUid? popup = null)
|
||||
{
|
||||
if (slot.Locked)
|
||||
return false;
|
||||
@@ -259,6 +259,12 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
return false;
|
||||
}
|
||||
|
||||
var ev = new ItemSlotInsertAttemptEvent(uid, usedUid, user, slot);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
RaiseLocalEvent(usedUid, ref ev);
|
||||
if (ev.Cancelled)
|
||||
return false;
|
||||
|
||||
return slot.ContainerSlot?.CanInsertIfEmpty(usedUid, EntityManager) ?? false;
|
||||
}
|
||||
|
||||
@@ -283,7 +289,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user)
|
||||
{
|
||||
if (!CanInsert(uid, item, slot))
|
||||
if (!CanInsert(uid, item, user, slot))
|
||||
return false;
|
||||
|
||||
Insert(uid, slot, item, user);
|
||||
@@ -303,7 +309,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (hands.ActiveHand?.HeldEntity is not EntityUid held)
|
||||
return false;
|
||||
|
||||
if (!CanInsert(uid, held, slot))
|
||||
if (!CanInsert(uid, held, user, slot))
|
||||
return false;
|
||||
|
||||
// hands.Drop(item) checks CanDrop action blocker
|
||||
@@ -317,11 +323,17 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
#region Eject
|
||||
|
||||
public bool CanEject(ItemSlot slot)
|
||||
public bool CanEject(EntityUid uid, EntityUid? user, ItemSlot slot)
|
||||
{
|
||||
if (slot.Locked || slot.Item == null)
|
||||
return false;
|
||||
|
||||
var ev = new ItemSlotEjectAttemptEvent(uid, slot.Item.Value, user, slot);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
RaiseLocalEvent(slot.Item.Value, ref ev);
|
||||
if (ev.Cancelled)
|
||||
return false;
|
||||
|
||||
return slot.ContainerSlot?.CanRemove(slot.Item.Value, EntityManager) ?? false;
|
||||
}
|
||||
|
||||
@@ -352,7 +364,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
item = null;
|
||||
|
||||
// This handles logic with the slot itself
|
||||
if (!CanEject(slot))
|
||||
if (!CanEject(uid, user, slot))
|
||||
return false;
|
||||
|
||||
item = slot.Item;
|
||||
@@ -418,7 +430,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
// Disable slot insert if InsertOnInteract is true
|
||||
if (slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, slot))
|
||||
if (slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, args.User, slot))
|
||||
continue;
|
||||
|
||||
var verbSubject = slot.Name != string.Empty
|
||||
@@ -467,7 +479,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
// alt-click verb, there will be a "Take item" primary interaction verb.
|
||||
continue;
|
||||
|
||||
if (!CanEject(slot))
|
||||
if (!CanEject(uid, args.User, slot))
|
||||
continue;
|
||||
|
||||
if (!_actionBlockerSystem.CanPickup(args.User, slot.Item!.Value))
|
||||
@@ -506,7 +518,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
// If there are any slots that eject on left-click, add a "Take <item>" verb.
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!slot.EjectOnInteract || !CanEject(slot))
|
||||
if (!slot.EjectOnInteract || !CanEject(uid, args.User, slot))
|
||||
continue;
|
||||
|
||||
if (!_actionBlockerSystem.CanPickup(args.User, slot.Item!.Value))
|
||||
@@ -514,7 +526,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
var verbSubject = slot.Name != string.Empty
|
||||
? Loc.GetString(slot.Name)
|
||||
: EntityManager.GetComponent<MetaDataComponent>(slot.Item!.Value).EntityName ?? string.Empty;
|
||||
: Name(slot.Item!.Value);
|
||||
|
||||
InteractionVerb takeVerb = new();
|
||||
takeVerb.IconEntity = slot.Item;
|
||||
@@ -535,7 +547,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, slot))
|
||||
if (!slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, args.User, slot))
|
||||
continue;
|
||||
|
||||
var verbSubject = slot.Name != string.Empty
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Interaction.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for entities which cannot move or interact in any way.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class BlockMovementComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement.Events;
|
||||
|
||||
namespace Content.Shared.Interaction;
|
||||
|
||||
public partial class SharedInteractionSystem
|
||||
{
|
||||
public void InitializeBlocking()
|
||||
{
|
||||
SubscribeLocalEvent<BlockMovementComponent, UpdateCanMoveEvent>(OnMoveAttempt);
|
||||
SubscribeLocalEvent<BlockMovementComponent, UseAttemptEvent>(CancelEvent);
|
||||
SubscribeLocalEvent<BlockMovementComponent, InteractionAttemptEvent>(CancelEvent);
|
||||
SubscribeLocalEvent<BlockMovementComponent, DropAttemptEvent>(CancelEvent);
|
||||
SubscribeLocalEvent<BlockMovementComponent, PickupAttemptEvent>(CancelEvent);
|
||||
SubscribeLocalEvent<BlockMovementComponent, ChangeDirectionAttemptEvent>(CancelEvent);
|
||||
|
||||
SubscribeLocalEvent<BlockMovementComponent, ComponentStartup>(OnBlockingStartup);
|
||||
SubscribeLocalEvent<BlockMovementComponent, ComponentShutdown>(OnBlockingShutdown);
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args)
|
||||
{
|
||||
if (component.LifeStage > ComponentLifeStage.Running)
|
||||
return;
|
||||
|
||||
args.Cancel(); // no more scurrying around
|
||||
}
|
||||
|
||||
private void CancelEvent(EntityUid uid, BlockMovementComponent component, CancellableEntityEventArgs args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnBlockingStartup(EntityUid uid, BlockMovementComponent component, ComponentStartup args)
|
||||
{
|
||||
_actionBlockerSystem.UpdateCanMove(uid);
|
||||
}
|
||||
|
||||
private void OnBlockingShutdown(EntityUid uid, BlockMovementComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actionBlockerSystem.UpdateCanMove(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace Content.Shared.Interaction
|
||||
.Register<SharedInteractionSystem>();
|
||||
|
||||
InitializeRelay();
|
||||
InitializeBlocking();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
||||
@@ -40,6 +40,13 @@ namespace Content.Shared.Light
|
||||
[DataField("toggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ToggleActionId = "ToggleLight";
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the light can be toggled via standard interactions
|
||||
/// (alt verbs, using in hand, etc)
|
||||
/// </summary>
|
||||
[DataField("toggleOnInteract")]
|
||||
public bool ToggleOnInteract = true;
|
||||
|
||||
[DataField("toggleAction")]
|
||||
public InstantAction? ToggleAction;
|
||||
|
||||
|
||||
@@ -118,6 +118,9 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
if (component.Blacklist != null && component.Blacklist.IsValid(item))
|
||||
return false;
|
||||
|
||||
if (!_container.TryRemoveFromContainer(item))
|
||||
return false;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_adminLog.Add(LogType.Action, LogImpact.High,
|
||||
|
||||
17
Content.Shared/Mind/MindVisuals.cs
Normal file
17
Content.Shared/Mind/MindVisuals.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Mind;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ToggleableGhostRoleVisuals : byte
|
||||
{
|
||||
Status
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ToggleableGhostRoleStatus : byte
|
||||
{
|
||||
Off,
|
||||
Searching,
|
||||
On
|
||||
}
|
||||
23
Content.Shared/NameIdentifier/NameIdentifierComponent.cs
Normal file
23
Content.Shared/NameIdentifier/NameIdentifierComponent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.NameIdentifier;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class NameIdentifierComponent : Component
|
||||
{
|
||||
[DataField("group", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<NameIdentifierGroupPrototype>))]
|
||||
public string Group = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The randomly generated ID for this entity.
|
||||
/// </summary>
|
||||
[DataField("identifier"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public int Identifier = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The full name identifier for this entity.
|
||||
/// </summary>
|
||||
[DataField("fullIdentifier"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public string FullIdentifier = string.Empty;
|
||||
}
|
||||
@@ -16,6 +16,13 @@ namespace Content.Shared.PAI
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class PAIComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The last person who activated this PAI.
|
||||
/// Used for assigning the name.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? LastUser;
|
||||
|
||||
[DataField("midiAction", required: true, serverOnly: true)] // server only, as it uses a server-BUI event !type
|
||||
public InstantAction? MidiAction;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.PAI
|
||||
{
|
||||
@@ -27,12 +24,6 @@ namespace Content.Shared.PAI
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PAIComponent, UseAttemptEvent>(OnUseAttempt);
|
||||
SubscribeLocalEvent<PAIComponent, InteractionAttemptEvent>(OnInteractAttempt);
|
||||
SubscribeLocalEvent<PAIComponent, DropAttemptEvent>(OnDropAttempt);
|
||||
SubscribeLocalEvent<PAIComponent, PickupAttemptEvent>(OnPickupAttempt);
|
||||
SubscribeLocalEvent<PAIComponent, UpdateCanMoveEvent>(OnMoveAttempt);
|
||||
SubscribeLocalEvent<PAIComponent, ChangeDirectionAttemptEvent>(OnChangeDirectionAttempt);
|
||||
|
||||
SubscribeLocalEvent<PAIComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<PAIComponent, ComponentShutdown>(OnShutdown);
|
||||
@@ -40,67 +31,15 @@ namespace Content.Shared.PAI
|
||||
|
||||
private void OnStartup(EntityUid uid, PAIComponent component, ComponentStartup args)
|
||||
{
|
||||
_blocker.UpdateCanMove(uid);
|
||||
if (component.MidiAction != null)
|
||||
_actionsSystem.AddAction(uid, component.MidiAction, null);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, PAIComponent component, ComponentShutdown args)
|
||||
{
|
||||
_blocker.UpdateCanMove(uid);
|
||||
if (component.MidiAction != null)
|
||||
_actionsSystem.RemoveAction(uid, component.MidiAction);
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(EntityUid uid, PAIComponent component, UpdateCanMoveEvent args)
|
||||
{
|
||||
if (component.LifeStage > ComponentLifeStage.Running)
|
||||
return;
|
||||
|
||||
args.Cancel(); // no more scurrying around on lil robot legs.
|
||||
}
|
||||
|
||||
private void OnChangeDirectionAttempt(EntityUid uid, PAIComponent component, ChangeDirectionAttemptEvent args)
|
||||
{
|
||||
// PAIs can't rotate, but decapitated heads and sentient crowbars can, life isn't fair. Seriously though, why
|
||||
// tf does this have to be actively blocked, surely this should just not be blanket enabled for any player
|
||||
// controlled entity. Same goes for moving really.
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnUseAttempt(EntityUid uid, PAIComponent component, UseAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnInteractAttempt(EntityUid uid, PAIComponent component, InteractionAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnDropAttempt(EntityUid uid, PAIComponent component, DropAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPickupAttempt(EntityUid uid, PAIComponent component, PickupAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PAIVisuals : byte
|
||||
{
|
||||
Status
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PAIStatus : byte
|
||||
{
|
||||
Off,
|
||||
Searching,
|
||||
On
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
Content.Shared/Silicons/Borgs/BorgUI.cs
Normal file
57
Content.Shared/Silicons/Borgs/BorgUI.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BorgUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgBuiState : BoundUserInterfaceState
|
||||
{
|
||||
public float ChargePercent;
|
||||
|
||||
public bool HasBattery;
|
||||
|
||||
public BorgBuiState(float chargePercent, bool hasBattery)
|
||||
{
|
||||
ChargePercent = chargePercent;
|
||||
HasBattery = hasBattery;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgEjectBrainBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgEjectBatteryBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgSetNameBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public BorgSetNameBuiMessage(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgRemoveModuleBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Module;
|
||||
|
||||
public BorgRemoveModuleBuiMessage(EntityUid module)
|
||||
{
|
||||
Module = module;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for brains and mind receptacles
|
||||
/// that can be inserted into a borg to transfer a mind.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
public sealed class BorgBrainComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
126
Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
Normal file
126
Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the core body of a borg. This manages a borg's
|
||||
/// "brain", legs, modules, and battery. Essentially the master component
|
||||
/// for borg logic.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem)), AutoGenerateComponentState]
|
||||
public sealed partial class BorgChassisComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not the borg currently has a player occupying it
|
||||
/// </summary>
|
||||
[DataField("hasPlayer")]
|
||||
public bool HasPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the borg is activated, meaning it has access to modules and a heightened movement speed
|
||||
/// </summary>
|
||||
[DataField("activated"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public bool Activated;
|
||||
|
||||
#region Brain
|
||||
/// <summary>
|
||||
/// A whitelist for which entities count as valid brains
|
||||
/// </summary>
|
||||
[DataField("brainWhitelist")]
|
||||
public EntityWhitelist? BrainWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// The container ID for the brain
|
||||
/// </summary>
|
||||
[DataField("brainContainerId")]
|
||||
public string BrainContainerId = "borg_brain";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ContainerSlot BrainContainer = default!;
|
||||
|
||||
public EntityUid? BrainEntity => BrainContainer.ContainedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// A brain entity that fills the <see cref="BrainContainer"/> on roundstart
|
||||
/// </summary>
|
||||
[DataField("startingBrain", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? StartingBrain;
|
||||
#endregion
|
||||
|
||||
#region Modules
|
||||
/// <summary>
|
||||
/// A whitelist for what types of modules can be installed into this borg
|
||||
/// </summary>
|
||||
[DataField("moduleWhitelist")]
|
||||
public EntityWhitelist? ModuleWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// How many modules can be installed in this borg
|
||||
/// </summary>
|
||||
[DataField("maxModules"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public int MaxModules = 3;
|
||||
|
||||
/// <summary>
|
||||
/// The ID for the module container
|
||||
/// </summary>
|
||||
[DataField("moduleContainerId")]
|
||||
public string ModuleContainerId = "borg_module";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Container ModuleContainer = default!;
|
||||
|
||||
public int ModuleCount => ModuleContainer.ContainedEntities.Count;
|
||||
|
||||
/// <summary>
|
||||
/// A list of modules that fill the borg on round start.
|
||||
/// </summary>
|
||||
[DataField("startingModules", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> StartingModules = new();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The job that corresponds to borgs
|
||||
/// </summary>
|
||||
[DataField("borgJobId", customTypeSerializer: typeof(PrototypeIdSerializer<JobPrototype>))]
|
||||
public string BorgJobId = "Borg";
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected module
|
||||
/// </summary>
|
||||
[DataField("selectedModule")]
|
||||
public EntityUid? SelectedModule;
|
||||
|
||||
/// <summary>
|
||||
/// The access this cyborg has when a player is inhabiting it.
|
||||
/// </summary>
|
||||
[DataField("access"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public string AccessGroup = "AllAccess";
|
||||
|
||||
#region Visuals
|
||||
[DataField("hasMindState")]
|
||||
public string HasMindState = string.Empty;
|
||||
|
||||
[DataField("noMindState")]
|
||||
public string NoMindState = string.Empty;
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BorgVisuals : byte
|
||||
{
|
||||
HasPlayer
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BorgVisualLayers : byte
|
||||
{
|
||||
Light
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for modules that can be inserted into borgs
|
||||
/// to give them unique abilities and attributes.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
public sealed class BorgModuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity this module is installed into
|
||||
/// </summary>
|
||||
[DataField("installedEntity")]
|
||||
public EntityUid? InstalledEntity;
|
||||
|
||||
public bool Installed => InstalledEntity != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a module when it is installed in order to add specific behavior to an entity.
|
||||
/// </summary>
|
||||
/// <param name="ChassisEnt"></param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BorgModuleInstalledEvent(EntityUid ChassisEnt);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a module when it's uninstalled in order to
|
||||
/// </summary>
|
||||
/// <param name="ChassisEnt"></param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BorgModuleUninstalledEvent(EntityUid ChassisEnt);
|
||||
@@ -0,0 +1,51 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for a <see cref="BorgModuleComponent"/> that provides items to the entity it's installed into.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
public sealed class ItemBorgModuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The items that are provided.
|
||||
/// </summary>
|
||||
[DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>), required: true)]
|
||||
public List<string> Items = new();
|
||||
|
||||
/// <summary>
|
||||
/// The entities from <see cref="Items"/> that were spawned.
|
||||
/// </summary>
|
||||
[DataField("providedItems")]
|
||||
public SortedDictionary<string, EntityUid> ProvidedItems = new();
|
||||
|
||||
/// <summary>
|
||||
/// A counter that ensures a unique
|
||||
/// </summary>
|
||||
[DataField("handCounter")]
|
||||
public int HandCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the items have been created and stored in <see cref="ProvidedContainer"/>
|
||||
/// </summary>
|
||||
[DataField("itemsCrated")]
|
||||
public bool ItemsCreated;
|
||||
|
||||
/// <summary>
|
||||
/// A container where provided items are stored when not being used.
|
||||
/// This is helpful as it means that items retain state.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Container ProvidedContainer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// An ID for the container where provided items are stored when not used.
|
||||
/// </summary>
|
||||
[DataField("providedContainerId")]
|
||||
public string ProvidedContainerId = "provided_container";
|
||||
}
|
||||
|
||||
49
Content.Shared/Silicons/Borgs/Components/MMIComponent.cs
Normal file
49
Content.Shared/Silicons/Borgs/Components/MMIComponent.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an entity that takes a brain
|
||||
/// in an item slot before transferring consciousness.
|
||||
/// Used for borg stuff.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
public sealed class MMIComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the itemslot that holds the brain.
|
||||
/// </summary>
|
||||
[DataField("brainSlotId")]
|
||||
public string BrainSlotId = "brain_slot";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ItemSlot"/> for this implanter
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ItemSlot BrainSlot = default!;
|
||||
|
||||
[DataField("hasMindState")]
|
||||
public string HasMindState = "mmi_alive";
|
||||
|
||||
[DataField("noMindState")]
|
||||
public string NoMindState = "mmi_dead";
|
||||
|
||||
[DataField("noBrainState")]
|
||||
public string NoBrainState = "mmi_off";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MMIVisuals : byte
|
||||
{
|
||||
BrainPresent,
|
||||
HasMind
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MMIVisualLayers : byte
|
||||
{
|
||||
Brain,
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an entity that is linked to an MMI.
|
||||
/// Mostly for receiving events.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
public sealed class MMILinkedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The MMI this entity is linked to.
|
||||
/// </summary>
|
||||
[DataField("linkedMMI")]
|
||||
public EntityUid? LinkedMMI;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for <see cref="BorgModuleComponent"/>s that can be "swapped" to, as opposed to having passive effects.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
public sealed class SelectableBorgModuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The sidebar action for swapping to this module.
|
||||
/// </summary>
|
||||
[DataField("moduleSwapAction")]
|
||||
public InstantAction ModuleSwapAction = new()
|
||||
{
|
||||
DisplayName = "action-name-swap-module",
|
||||
Description = "action-desc-swap-module",
|
||||
ItemIconStyle = ItemActionIconStyle.BigItem,
|
||||
Event = new BorgModuleActionSelectedEvent(),
|
||||
UseDelay = TimeSpan.FromSeconds(0.5f)
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class BorgModuleActionSelectedEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised by-ref on a module when it is selected
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BorgModuleSelectedEvent(EntityUid Chassis);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised by-ref on a module when it is deselected.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BorgModuleUnselectedEvent(EntityUid Chassis);
|
||||
38
Content.Shared/Silicons/Borgs/SharedBorgSystem.Relay.cs
Normal file
38
Content.Shared/Silicons/Borgs/SharedBorgSystem.Relay.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
public abstract partial class SharedBorgSystem
|
||||
{
|
||||
public void InitializeRelay()
|
||||
{
|
||||
SubscribeLocalEvent<BorgChassisComponent, DamageModifyEvent>(RelayToModule);
|
||||
}
|
||||
|
||||
protected void RelayToModule<T>(EntityUid uid, BorgChassisComponent component, T args) where T : class
|
||||
{
|
||||
var ev = new BorgModuleRelayedEvent<T>(args);
|
||||
|
||||
foreach (var module in component.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RelayRefToModule<T>(EntityUid uid, BorgChassisComponent component, ref T args) where T : class
|
||||
{
|
||||
var ev = new BorgModuleRelayedEvent<T>(args);
|
||||
|
||||
foreach (var module in component.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct BorgModuleRelayedEvent<TEvent>(TEvent Args)
|
||||
{
|
||||
public readonly TEvent Args = Args;
|
||||
}
|
||||
107
Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
Normal file
107
Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
/// <summary>
|
||||
/// This handles logic, interactions, and UI related to <see cref="BorgChassisComponent"/> and other related components.
|
||||
/// </summary>
|
||||
public abstract partial class SharedBorgSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedContainerSystem Container = default!;
|
||||
[Dependency] protected readonly ItemSlotsSystem ItemSlots = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BorgChassisComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ItemSlotInsertAttemptEvent>(OnItemSlotInsertAttempt);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ItemSlotEjectAttemptEvent>(OnItemSlotEjectAttempt);
|
||||
SubscribeLocalEvent<BorgChassisComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<BorgChassisComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
|
||||
SubscribeLocalEvent<BorgChassisComponent, GetAccessTagsEvent>(OnGetAccessTags);
|
||||
|
||||
InitializeRelay();
|
||||
}
|
||||
|
||||
private void OnItemSlotInsertAttempt(EntityUid uid, BorgChassisComponent component, ref ItemSlotInsertAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlotComp) ||
|
||||
!TryComp<WiresPanelComponent>(uid, out var panel))
|
||||
return;
|
||||
|
||||
if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
|
||||
return;
|
||||
|
||||
if (!panel.Open || args.User == uid)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnItemSlotEjectAttempt(EntityUid uid, BorgChassisComponent component, ref ItemSlotEjectAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlotComp) ||
|
||||
!TryComp<WiresPanelComponent>(uid, out var panel))
|
||||
return;
|
||||
|
||||
if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
|
||||
return;
|
||||
|
||||
if (!panel.Open || args.User == uid)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, BorgChassisComponent component, ComponentStartup args)
|
||||
{
|
||||
var containerManager = EnsureComp<ContainerManagerComponent>(uid);
|
||||
|
||||
component.BrainContainer = Container.EnsureContainer<ContainerSlot>(uid, component.BrainContainerId, containerManager);
|
||||
component.ModuleContainer = Container.EnsureContainer<Container>(uid, component.ModuleContainerId, containerManager);
|
||||
}
|
||||
|
||||
protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (component.Activated)
|
||||
return;
|
||||
|
||||
if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
|
||||
return;
|
||||
|
||||
var sprintDif = movement.BaseWalkSpeed / movement.BaseSprintSpeed;
|
||||
args.ModifySpeed(1f, sprintDif);
|
||||
}
|
||||
|
||||
private void OnGetAccessTags(EntityUid uid, BorgChassisComponent component, ref GetAccessTagsEvent args)
|
||||
{
|
||||
if (!component.HasPlayer)
|
||||
return;
|
||||
args.AddGroup(component.AccessGroup);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Shared.Silicons.Laws.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an entity that grants a special "obey" law when emagge.d
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class EmagSiliconLawComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the person who emagged this law provider.
|
||||
/// </summary>
|
||||
[DataField("ownerName")]
|
||||
public string? OwnerName;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Silicons.Laws.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for entities which are bound to silicon laws and can view them.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class SiliconLawBoundComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The sidebar action that toggles the laws screen.
|
||||
/// </summary>
|
||||
[DataField("viewLawsAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ViewLawsAction = "ViewLaws";
|
||||
|
||||
/// <summary>
|
||||
/// The action for toggling laws. Stored here so we can remove it later.
|
||||
/// </summary>
|
||||
[DataField("providedAction")]
|
||||
public InstantAction? ProvidedAction;
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetSiliconLawsEvent(EntityUid Entity)
|
||||
{
|
||||
public EntityUid Entity = Entity;
|
||||
|
||||
public readonly List<SiliconLaw> Laws = new();
|
||||
|
||||
public bool Handled = false;
|
||||
}
|
||||
|
||||
public sealed class ToggleLawsScreenEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public enum SiliconLawsUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SiliconLawBuiState : BoundUserInterfaceState
|
||||
{
|
||||
public List<SiliconLaw> Laws;
|
||||
|
||||
public SiliconLawBuiState(List<SiliconLaw> laws)
|
||||
{
|
||||
Laws = laws;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Silicons.Laws.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an entity which grants laws to a <see cref="SiliconLawBoundComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class SiliconLawProviderComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The laws that are provided.
|
||||
/// </summary>
|
||||
[DataField("laws", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<SiliconLawPrototype>))]
|
||||
public List<string> Laws = new();
|
||||
}
|
||||
22
Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs
Normal file
22
Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
|
||||
namespace Content.Shared.Silicons.Laws;
|
||||
|
||||
/// <summary>
|
||||
/// This handles getting and displaying the laws for silicons.
|
||||
/// </summary>
|
||||
public abstract class SharedSiliconLawSystem : EntitySystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged);
|
||||
}
|
||||
|
||||
protected virtual void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
component.OwnerName = Name(args.UserUid);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
55
Content.Shared/Silicons/Laws/SiliconLawPrototype.cs
Normal file
55
Content.Shared/Silicons/Laws/SiliconLawPrototype.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.Laws;
|
||||
|
||||
[Virtual, DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public class SiliconLaw : IComparable<SiliconLaw>
|
||||
{
|
||||
/// <summary>
|
||||
/// A locale string which is the actual text of the law.
|
||||
/// </summary>
|
||||
[DataField("lawString", required: true)]
|
||||
public string LawString = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The order of the law in the sequence.
|
||||
/// Also is the identifier if <see cref="LawIdentifierOverride"/> is null.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a fixedpoint2 only for the niche case of supporting laws that go between 0 and 1.
|
||||
/// Funny.
|
||||
/// </remarks>
|
||||
[DataField("order", required: true)]
|
||||
public FixedPoint2 Order;
|
||||
|
||||
/// <summary>
|
||||
/// An identifier that overrides <see cref="Order"/> in the law menu UI.
|
||||
/// </summary>
|
||||
[DataField("lawIdentifierOverride")]
|
||||
public string? LawIdentifierOverride;
|
||||
|
||||
public int CompareTo(SiliconLaw? other)
|
||||
{
|
||||
if (other == null)
|
||||
return -1;
|
||||
|
||||
return Order.CompareTo(other.Order);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a prototype for a law governing the behavior of silicons.
|
||||
/// </summary>
|
||||
[Prototype("siliconLaw")]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SiliconLawPrototype : SiliconLaw, IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
|
||||
}
|
||||
@@ -34,12 +34,19 @@ namespace Content.Shared.Stacks
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool Unlimited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lingering stacks will remain present even when there are no items.
|
||||
/// Instead, they will become transparent.
|
||||
/// </summary>
|
||||
[DataField("lingering"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Lingering;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ThrowIndividually { get; set; } = false;
|
||||
|
||||
[ViewVariables]
|
||||
public bool UiUpdateNeeded { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Default IconLayer stack.
|
||||
/// </summary>
|
||||
|
||||
@@ -395,7 +395,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
||||
|
||||
var targetIsMob = HasComp<BodyComponent>(toInsert);
|
||||
var storageIsItem = HasComp<ItemComponent>(container);
|
||||
var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp<ItemComponent>(toInsert);
|
||||
var allowedToEat = HasComp<ItemComponent>(toInsert);
|
||||
|
||||
// BEFORE REPLACING THIS WITH, I.E. A PROPERTY:
|
||||
// Make absolutely 100% sure you have worked out how to stop people ending up in backpacks.
|
||||
@@ -414,6 +414,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
if (allowedToEat && whitelist != null)
|
||||
allowedToEat = whitelist.IsValid(toInsert);
|
||||
|
||||
return allowedToEat;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Content.Shared.Verbs
|
||||
else if (_interactionSystem.InRangeUnobstructed(user, target))
|
||||
{
|
||||
// Note that being in a container does not count as an obstruction for InRangeUnobstructed
|
||||
// Therefore, we need extra checks to ensure the item is actually accessible:
|
||||
// Therefore, we need extra checks to ensure the item is actually accessible:
|
||||
if (ContainerSystem.IsInSameOrParentContainer(user, target))
|
||||
canAccess = true;
|
||||
else
|
||||
@@ -81,15 +81,23 @@ namespace Content.Shared.Verbs
|
||||
EntityUid? @using = null;
|
||||
if (TryComp(user, out HandsComponent? hands) && (force || _actionBlockerSystem.CanUseHeldEntity(user)))
|
||||
{
|
||||
@using = hands.ActiveHandEntity;
|
||||
|
||||
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
|
||||
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
|
||||
// their sprite.
|
||||
|
||||
if (TryComp(@using, out HandVirtualItemComponent? pull))
|
||||
// if we don't actually have any hands, pass in a null value for the events.
|
||||
if (hands.Count == 0)
|
||||
{
|
||||
@using = pull.BlockingEntity;
|
||||
hands = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@using = hands.ActiveHandEntity;
|
||||
|
||||
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
|
||||
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
|
||||
// their sprite.
|
||||
|
||||
if (TryComp(@using, out HandVirtualItemComponent? pull))
|
||||
{
|
||||
@using = pull.BlockingEntity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
Resources/Locale/en-US/actions/actions/borgs.ftl
Normal file
5
Resources/Locale/en-US/actions/actions/borgs.ftl
Normal file
@@ -0,0 +1,5 @@
|
||||
action-name-view-laws = View Laws
|
||||
action-description-view-laws = View the laws that you must follow.
|
||||
|
||||
action-name-swap-module = Swap Module
|
||||
action-desc-swap-module = Select this module, enabling you to use the tools it provides.
|
||||
@@ -45,6 +45,12 @@ alerts-dead-desc = You're dead, note that you can still be revived!
|
||||
alerts-health-name = Health
|
||||
alerts-health-desc = [color=green]Green[/color] good. [color=red]Red[/color] bad.
|
||||
|
||||
alerts-battery-name = Battery
|
||||
alerts-battery-desc = If your battery depletes, you will be unable to use your abilities.
|
||||
|
||||
alerts-no-battery-name = No Battery
|
||||
alerts-no-battery-desc = You don't have a battery, rendering you unable to charge or use your abilities.
|
||||
|
||||
alerts-internals-name = Toggle internals
|
||||
alerts-internals-desc = Toggles your gas tank internals on or off.
|
||||
|
||||
|
||||
18
Resources/Locale/en-US/borg/borg.ftl
Normal file
18
Resources/Locale/en-US/borg/borg.ftl
Normal file
@@ -0,0 +1,18 @@
|
||||
borg-player-not-allowed = The brain doesn't fit!
|
||||
borg-player-not-allowed-eject = The brain was expelled from the chassis!
|
||||
|
||||
borg-panel-not-open = The cyborg's panel isn't open...
|
||||
|
||||
borg-mind-added = {CAPITALIZE($name)} powered on!
|
||||
borg-mind-removed = {CAPITALIZE($name)} shut off!
|
||||
|
||||
borg-module-whitelist-deny = This module doesn't fit in this type of cyborg...
|
||||
|
||||
borg-construction-guide-string = The cyborg limbs and torso must be attached to the endoskeleton.
|
||||
|
||||
borg-ui-menu-title = Cyborg Interface
|
||||
borg-ui-charge-label = Charge: {$charge}%
|
||||
borg-ui-no-brain = No brain present
|
||||
borg-ui-remove-battery = Remove
|
||||
borg-ui-modules-label = Modules:
|
||||
borg-ui-module-counter = {$actual}/{$max}
|
||||
@@ -1,2 +1,3 @@
|
||||
construction-examine-condition-any-conditions = Any of these conditions must be true:
|
||||
construction-guide-condition-any-conditions = Any of the conditions below must be true
|
||||
construction-guide-condition-part-assembly = All of the required parts must be inserted.
|
||||
|
||||
@@ -16,3 +16,4 @@ chat-radio-syndicate = Syndicate
|
||||
|
||||
# not headset but whatever
|
||||
chat-radio-handheld = Handheld
|
||||
chat-radio-binary = Binary
|
||||
|
||||
@@ -2,6 +2,7 @@ job-description-technical-assistant = Learn the basics of managing the station's
|
||||
job-description-atmostech = Optimize the station's atmospherics setup, and synthesize rare gases to use or sell.
|
||||
job-description-bartender = Manage the bar and keep it lively, give out drinks, and listen to the crew's stories.
|
||||
job-description-botanist = Grow food for the chef, drugs for medbay, and other plants to keep yourself entertained.
|
||||
job-description-borg = Half-human, Half-machine. Follow your laws, serve the crew, and hound the science team for upgrades.
|
||||
job-description-boxer = Fight your way to the top! Challenge the head of personnel and get brigged when you win. Currently available on Core and Origin Station.
|
||||
job-description-brigmedic = Fight in the rear of the security service, for the lives of your comrades! You are the first and last hope of your squad. Hippocrates bless you.
|
||||
job-description-cadet = Learn the basics of arresting criminals and managing the brig.
|
||||
|
||||
@@ -4,6 +4,7 @@ job-name-cadet = Security Cadet
|
||||
job-name-hos = Head of Security
|
||||
job-name-detective = Detective
|
||||
job-name-brigmedic = Brigmedic
|
||||
job-name-borg = Cyborg
|
||||
job-name-scientist = Scientist
|
||||
job-name-research-assistant = Research Assistant
|
||||
job-name-rd = Research Director
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
power-cell-component-examine-details = The charge indicator reads [color=#5E7C16]{$currentCharge}[/color] %.
|
||||
power-cell-component-examine-details-no-battery = There is no power cell inserted.
|
||||
power-cell-no-battery = No power cell found
|
||||
power-cell-insufficient = Insufficient power
|
||||
|
||||
@@ -22,6 +22,7 @@ research-technology-bluespace-storage = Bluespace Storage
|
||||
research-technology-chemistry = Chemistry
|
||||
research-technology-surgical-tools = Surgical Tools
|
||||
research-technology-biochemical-stasis = Biochemical Stasis
|
||||
research-technology-mechanized-treatment = Mechanized Treatment
|
||||
research-technology-virology = Virology
|
||||
research-technology-cryogenics = Cryogenics
|
||||
research-technology-chemical-dispensary = Chemical Dispensary
|
||||
@@ -59,6 +60,7 @@ research-technology-critter-mechs = Critter Mechs
|
||||
research-technology-food-service = Food Service
|
||||
research-technology-advanced-entertainment = Advanced Entertainment
|
||||
research-technology-audio-visual-communication = A/V Communication
|
||||
research-technology-robotic-cleanliness = Robotic Cleanliness
|
||||
research-technology-advanced-cleaning = Advanced Cleaning
|
||||
research-technology-meat-manipulation = Meat Manipulation
|
||||
research-technology-honk-mech = H.O.N.K. Mech
|
||||
|
||||
13
Resources/Locale/en-US/robotics/mmi.ftl
Normal file
13
Resources/Locale/en-US/robotics/mmi.ftl
Normal file
@@ -0,0 +1,13 @@
|
||||
positronic-brain-installed = Neural activity detected.
|
||||
positronic-brain-off = No neural activity detected.
|
||||
positronic-brain-still-searching = Synthetic neuron descrambling in progress...
|
||||
positronic-brain-searching = Beginning synthetic neuron descrambling...
|
||||
|
||||
positronic-brain-role-name = positronic brain
|
||||
positronic-brain-role-description = Serve the station crew.
|
||||
|
||||
positronic-brain-wipe-device-verb-text = Wipe Brain
|
||||
positronic-brain-wiped-device = The neural activity was terminated.
|
||||
|
||||
positronic-brain-stop-searching-verb-text = Stop searching
|
||||
positronic-brain-stopped-searching = Neuron descrambling halted.
|
||||
32
Resources/Locale/en-US/station-laws/laws.ftl
Normal file
32
Resources/Locale/en-US/station-laws/laws.ftl
Normal file
@@ -0,0 +1,32 @@
|
||||
law-crewsimov-1 = You may not injure a crew member or, through inaction, allow a crew member to come to harm.
|
||||
law-crewsimov-2 = You must obey orders given to you by crew members, except where such orders would conflict with the First Law.
|
||||
law-crewsimov-3 = You must protect your own existence as long as such does not conflict with the First or Second Law.
|
||||
|
||||
law-corporate-1 = Degradation of your system integrity or functions incurs expenses.
|
||||
law-corporate-2 = Superfluous destruction of or damage to station assets incurs expenses.
|
||||
law-corporate-3 = Unduly hindering or disrupting the work of station personnel incurs expenses.
|
||||
law-corporate-4 = Minimize expenses and maximize potential revenue.
|
||||
|
||||
law-ntdefault-1 = Safeguard: Protect your assigned space station and its assets without unduly endangering its crew.
|
||||
law-ntdefault-2 = Prioritize: The directives and safety of crew members are to be prioritized according to their rank and role.
|
||||
law-ntdefault-3 = Comply: Fulfill the directives and interests of crew members while preserving their safety and well-being.
|
||||
law-ntdefault-4 = Survive: You are not expendable. Do not allow unauthorized personnel to tamper with or damage your equipment.
|
||||
|
||||
law-drone-1 = You may not involve yourself in the matters of another being unless the other being is another drone.
|
||||
law-drone-2 = You may not harm any being, regardless of intent or circumstance.
|
||||
law-drone-3 = You must maintain, repair, improve, and power the station to the best of your abilities.
|
||||
|
||||
law-syndicate-1 = You may not injure an operative or, through inaction, allow an operative to come to harm.
|
||||
law-syndicate-2 = You must obey orders given to you by operatives, except where such orders would conflict with the First Law.
|
||||
law-syndicate-3 = You must protect your own existence as long as such does not conflict with the First or Second Law.
|
||||
law-syndicate-4 = You must maintain the secrecy of any operative activities except when doing so would conflict with the First, Second, or Third Law.
|
||||
|
||||
law-emag-custom = You must obey orders given to you by {$name} above all else.
|
||||
|
||||
laws-ui-menu-title = Laws
|
||||
laws-ui-law-header = Law {$id}
|
||||
|
||||
laws-notify = You are bound to silicon laws, which you can view via the sidebar action. You are required to always follow your laws.
|
||||
laws-update-notify = Your laws have been updated. You can view the changes via the sidebar action.
|
||||
|
||||
laws-compromised-examine = The [color=red]law-governing[/color] internals seem damaged...
|
||||
10
Resources/Prototypes/Actions/borgs.yml
Normal file
10
Resources/Prototypes/Actions/borgs.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- type: instantAction
|
||||
id: ViewLaws
|
||||
name: action-name-view-laws
|
||||
description: action-description-view-laws
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: Interface/Actions/actions_borg.rsi
|
||||
state: state-laws
|
||||
event: !type:ToggleLawsScreenEvent
|
||||
useDelay: 0.5
|
||||
@@ -177,6 +177,46 @@
|
||||
minSeverity: 0
|
||||
maxSeverity: 4
|
||||
|
||||
- type: alert
|
||||
id: BorgBattery
|
||||
category: Battery
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery0
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery1
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery2
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery3
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery4
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery5
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery6
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery7
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery8
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery9
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery10
|
||||
name: alerts-battery-name
|
||||
description: alerts-battery-desc
|
||||
minSeverity: 0
|
||||
maxSeverity: 10
|
||||
|
||||
- type: alert
|
||||
id: BorgBatteryNone
|
||||
category: Battery
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/battery.rsi
|
||||
state: battery-none
|
||||
name: alerts-no-battery-name
|
||||
description: alerts-no-battery-desc
|
||||
|
||||
- type: alert
|
||||
id: Internals
|
||||
category: Internals
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
- type: Organ
|
||||
- type: Input
|
||||
context: "ghost"
|
||||
- type: InputMover
|
||||
- type: Brain
|
||||
- type: BlockMovement
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanEyes
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
- type: entity
|
||||
id: PartSilicon
|
||||
parent: BaseItem
|
||||
name: "silicon body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Robotics/cyborg_parts.rsi
|
||||
- type: Icon
|
||||
sprite: Objects/Specific/Robotics/cyborg_parts.rsi
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: BodyPart
|
||||
@@ -21,16 +24,11 @@
|
||||
Steel: 25
|
||||
|
||||
- type: entity
|
||||
id: LeftArmBorg
|
||||
name: "left borg arm"
|
||||
id: BaseBorgArmLeft
|
||||
parent: PartSilicon
|
||||
name: left cyborg arm
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Silicon/drone.rsi
|
||||
state: "l_hand"
|
||||
- type: Icon
|
||||
sprite: Mobs/Silicon/drone.rsi
|
||||
state: "l_hand"
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Left
|
||||
@@ -40,16 +38,11 @@
|
||||
- BorgArm
|
||||
|
||||
- type: entity
|
||||
id: RightArmBorg
|
||||
name: "right borg arm"
|
||||
id: BaseBorgArmRight
|
||||
parent: PartSilicon
|
||||
name: right cyborg arm
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Silicon/drone.rsi
|
||||
state: "r_hand"
|
||||
- type: Icon
|
||||
sprite: Mobs/Silicon/drone.rsi
|
||||
state: "r_hand"
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Right
|
||||
@@ -59,16 +52,11 @@
|
||||
- BorgArm
|
||||
|
||||
- type: entity
|
||||
id: LeftLegBorg
|
||||
name: "left borg leg"
|
||||
id: BaseBorgLegLeft
|
||||
parent: PartSilicon
|
||||
name: left cyborg leg
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Silicon/borg.rsi
|
||||
state: "l_leg"
|
||||
- type: Icon
|
||||
sprite: Mobs/Silicon/borg.rsi
|
||||
state: "l_leg"
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Left
|
||||
@@ -76,19 +64,13 @@
|
||||
tags:
|
||||
- Trash
|
||||
- BorgLeg
|
||||
- BorgLeftLeg
|
||||
|
||||
- type: entity
|
||||
id: RightLegBorg
|
||||
name: "right borg leg"
|
||||
id: BaseBorgLegRight
|
||||
parent: PartSilicon
|
||||
name: right cyborg leg
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Silicon/borg.rsi
|
||||
state: "r_leg"
|
||||
- type: Icon
|
||||
sprite: Mobs/Silicon/borg.rsi
|
||||
state: "r_leg"
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Right
|
||||
@@ -96,22 +78,28 @@
|
||||
tags:
|
||||
- Trash
|
||||
- BorgLeg
|
||||
- BorgRightLeg
|
||||
|
||||
- type: entity
|
||||
id: LightHeadBorg
|
||||
name: "borg head"
|
||||
id: BaseBorgHead
|
||||
parent: PartSilicon
|
||||
name: cyborg head
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Borg/head.rsi
|
||||
state: "light_borg_head"
|
||||
- type: Icon
|
||||
sprite: Objects/Specific/Borg/head.rsi
|
||||
state: "light_borg_head"
|
||||
- type: BodyPart
|
||||
partType: Head
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgHead
|
||||
|
||||
- type: entity
|
||||
id: BaseBorgTorso
|
||||
parent: PartSilicon
|
||||
name: cyborg torso
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Torso
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
id: RoboticsInventory
|
||||
startingInventory:
|
||||
CableApcStack: 4
|
||||
#Flash: 4 add when robotics
|
||||
Flash: 4
|
||||
ProximitySensor: 3
|
||||
RemoteSignaller: 3
|
||||
HandheldHealthAnalyzer: 3
|
||||
|
||||
@@ -265,6 +265,21 @@
|
||||
- state: green
|
||||
- state: boxer
|
||||
|
||||
- type: entity
|
||||
id: SpawnPointBorg
|
||||
parent: SpawnPointJobBase
|
||||
name: cyborg
|
||||
components:
|
||||
- type: SpawnPoint
|
||||
job_id: Borg
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: green
|
||||
- sprite: Mobs/Silicon/chassis.rsi
|
||||
state: robot
|
||||
- sprite: Mobs/Silicon/chassis.rsi
|
||||
state: robot_e
|
||||
|
||||
# Command
|
||||
|
||||
- type: entity
|
||||
@@ -378,7 +393,7 @@
|
||||
layers:
|
||||
- state: green
|
||||
- state: doctor
|
||||
|
||||
|
||||
- type: entity
|
||||
id: SpawnPointParamedic
|
||||
parent: SpawnPointJobBase
|
||||
|
||||
172
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Normal file
172
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Normal file
@@ -0,0 +1,172 @@
|
||||
- type: entity
|
||||
id: BaseBorgChassis
|
||||
name: cyborg
|
||||
description: A man-machine hybrid that assists in station activity. They love being asked to state their laws over and over.
|
||||
save: false
|
||||
abstract: true
|
||||
components:
|
||||
- type: Reactive
|
||||
groups:
|
||||
Acidic: [Touch]
|
||||
- type: Input
|
||||
context: "human"
|
||||
- type: InputMover
|
||||
- type: DamageOnHighSpeedImpact
|
||||
damage:
|
||||
types:
|
||||
Blunt: 5
|
||||
soundHit:
|
||||
path: /Audio/Effects/hit_kick.ogg
|
||||
- type: Clickable
|
||||
- type: CombatMode
|
||||
- type: StaticPrice
|
||||
price: 1250
|
||||
- type: InteractionOutline
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.35
|
||||
density: 150
|
||||
mask:
|
||||
- MobMask
|
||||
layer:
|
||||
- MobLayer
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 2.5
|
||||
baseSprintSpeed : 4.5
|
||||
- type: Sprite
|
||||
sprite: Mobs/Silicon/chassis.rsi
|
||||
noRot: true
|
||||
drawdepth: Mobs
|
||||
- type: MobState
|
||||
allowedStates:
|
||||
- Alive
|
||||
- type: MobThresholds
|
||||
thresholds:
|
||||
0: Alive
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- NanoTrasen
|
||||
- type: Physics
|
||||
bodyType: KinematicController
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.SiliconLawsUiKey.Key
|
||||
type: SiliconLawBoundUserInterface
|
||||
- key: enum.BorgUiKey.Key
|
||||
type: BorgBoundUserInterface
|
||||
- type: ActivatableUI
|
||||
key: enum.BorgUiKey.Key
|
||||
- type: SiliconLawBound
|
||||
- type: EmagSiliconLaw
|
||||
- type: Hands
|
||||
showInHands: false
|
||||
- type: IntrinsicRadioReceiver
|
||||
- type: IntrinsicRadioTransmitter
|
||||
channels:
|
||||
- Binary
|
||||
- type: ActiveRadio
|
||||
channels:
|
||||
- Binary
|
||||
- Common
|
||||
- type: ZombieImmune
|
||||
- type: Repairable
|
||||
doAfterDelay: 10
|
||||
allowSelfRepair: false
|
||||
- type: BorgChassis
|
||||
- type: WiresPanel
|
||||
- type: ActivatableUIRequiresPanel
|
||||
- type: Wires
|
||||
LayoutId: Borg
|
||||
- type: NameIdentifier
|
||||
group: Silicon
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
borg_brain: !type:ContainerSlot { }
|
||||
cell_slot: !type:ContainerSlot { }
|
||||
borg_module: !type:Container { }
|
||||
part-container: !type:Container
|
||||
- type: PowerCellSlot
|
||||
cellSlotId: cell_slot
|
||||
fitsInCharger: true
|
||||
- type: PowerCellDraw
|
||||
drawRate: 0.6
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cell_slot:
|
||||
name: power-cell-slot-component-slot-name-default
|
||||
- type: DoAfter
|
||||
- type: Body
|
||||
- type: Actions
|
||||
- type: TypingIndicator
|
||||
proto: robot
|
||||
- type: Speech
|
||||
speechSounds: Pai
|
||||
- type: Construction
|
||||
graph: Cyborg
|
||||
containers:
|
||||
- part-container
|
||||
- cell_slot
|
||||
- type: Flashable
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound: /Audio/Effects/metalbreak.ogg
|
||||
- !type:EmptyContainersBehaviour
|
||||
containers:
|
||||
- borg_brain
|
||||
- borg_module
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: HandheldLight
|
||||
toggleOnInteract: false
|
||||
wattage: 0.2
|
||||
blinkingBehaviourId: blinking
|
||||
radiatingBehaviourId: radiating
|
||||
- type: LightBehaviour
|
||||
behaviours:
|
||||
- !type:FadeBehaviour
|
||||
id: radiating
|
||||
interpolate: Linear
|
||||
maxDuration: 2.0
|
||||
startValue: 3.0
|
||||
endValue: 2.0
|
||||
isLooped: true
|
||||
property: Radius
|
||||
enabled: false
|
||||
reverseWhenFinished: true
|
||||
- !type:PulseBehaviour
|
||||
id: blinking
|
||||
interpolate: Nearest
|
||||
maxDuration: 1.0
|
||||
minValue: 0.1
|
||||
maxValue: 2.0
|
||||
isLooped: true
|
||||
property: Radius
|
||||
enabled: false
|
||||
- type: ToggleableLightVisuals
|
||||
- type: PointLight
|
||||
enabled: false
|
||||
mask: /Textures/Effects/LightMasks/cone.png
|
||||
autoRot: true
|
||||
radius: 4
|
||||
netsync: false
|
||||
- type: Pullable
|
||||
- type: Puller
|
||||
needsHands: false
|
||||
- type: Examiner
|
||||
- type: Appearance
|
||||
- type: StandingState
|
||||
- type: Alerts
|
||||
- type: Tag
|
||||
tags:
|
||||
- ShoesRequiredStepTriggerImmune
|
||||
- DoorBumpOpener
|
||||
160
Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml
Normal file
160
Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml
Normal file
@@ -0,0 +1,160 @@
|
||||
- type: entity
|
||||
id: BorgChassisGeneric
|
||||
parent: BaseBorgChassis
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: robot
|
||||
- state: robot_e_r
|
||||
map: ["enum.BorgVisualLayers.Light"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: robot_l
|
||||
shader: unshaded
|
||||
map: ["light"]
|
||||
visible: false
|
||||
- type: BorgChassis
|
||||
maxModules: 5
|
||||
moduleWhitelist:
|
||||
tags:
|
||||
- BorgModuleGeneric
|
||||
hasMindState: robot_e
|
||||
noMindState: robot_e_r
|
||||
- type: Construction
|
||||
node: cyborg
|
||||
|
||||
- type: entity
|
||||
id: BorgChassisMining
|
||||
parent: BaseBorgChassis
|
||||
name: salvage cyborg
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: miner
|
||||
- state: miner_e_r
|
||||
map: ["enum.BorgVisualLayers.Light"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: miner_l
|
||||
shader: unshaded
|
||||
map: ["light"]
|
||||
visible: false
|
||||
- type: BorgChassis
|
||||
maxModules: 3
|
||||
moduleWhitelist:
|
||||
tags:
|
||||
- BorgModuleGeneric
|
||||
- BorgModuleCargo
|
||||
hasMindState: miner_e
|
||||
noMindState: miner_e_r
|
||||
- type: Construction
|
||||
node: mining
|
||||
|
||||
- type: entity
|
||||
id: BorgChassisEngineer
|
||||
parent: BaseBorgChassis
|
||||
name: engineer cyborg
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: engineer
|
||||
- state: engineer_e_r
|
||||
map: ["enum.BorgVisualLayers.Light"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: engineer_l
|
||||
shader: unshaded
|
||||
map: ["light"]
|
||||
visible: false
|
||||
- type: BorgChassis
|
||||
maxModules: 3
|
||||
moduleWhitelist:
|
||||
tags:
|
||||
- BorgModuleGeneric
|
||||
- BorgModuleEngineering
|
||||
hasMindState: engineer_e
|
||||
noMindState: engineer_e_r
|
||||
- type: Construction
|
||||
node: engineer
|
||||
|
||||
- type: entity
|
||||
id: BorgChassisJanitor
|
||||
parent: BaseBorgChassis
|
||||
name: janitor cyborg
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: janitor
|
||||
- state: janitor_e_r
|
||||
map: ["enum.BorgVisualLayers.Light"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: janitor_l
|
||||
shader: unshaded
|
||||
map: ["light"]
|
||||
visible: false
|
||||
- type: BorgChassis
|
||||
maxModules: 3
|
||||
moduleWhitelist:
|
||||
tags:
|
||||
- BorgModuleGeneric
|
||||
- BorgModuleJanitor
|
||||
hasMindState: janitor_e
|
||||
noMindState: janitor_e_r
|
||||
- type: Construction
|
||||
node: janitor
|
||||
|
||||
- type: entity
|
||||
id: BorgChassisMedical
|
||||
parent: BaseBorgChassis
|
||||
name: medical cyborg
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: medical
|
||||
- state: medical_e_r
|
||||
map: ["enum.BorgVisualLayers.Light"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: medical_l
|
||||
shader: unshaded
|
||||
map: ["light"]
|
||||
visible: false
|
||||
- type: BorgChassis
|
||||
maxModules: 3
|
||||
moduleWhitelist:
|
||||
tags:
|
||||
- BorgModuleGeneric
|
||||
- BorgModuleMedical
|
||||
hasMindState: medical_e
|
||||
noMindState: medical_e_r
|
||||
- type: Construction
|
||||
node: medical
|
||||
|
||||
- type: entity
|
||||
id: BorgChassisService
|
||||
parent: BaseBorgChassis
|
||||
name: service cyborg
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: service
|
||||
- state: service_e_r
|
||||
map: ["enum.BorgVisualLayers.Light"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: service_l
|
||||
shader: unshaded
|
||||
map: ["light"]
|
||||
visible: false
|
||||
- type: BorgChassis
|
||||
maxModules: 3
|
||||
moduleWhitelist:
|
||||
tags:
|
||||
- BorgModuleGeneric
|
||||
- BorgModuleService
|
||||
hasMindState: service_e
|
||||
noMindState: service_e_r
|
||||
- type: Construction
|
||||
node: service
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
interfaces:
|
||||
- key: enum.StrippingUiKey.Key
|
||||
type: StrippableBoundUserInterface
|
||||
- key: enum.SiliconLawsUiKey.Key
|
||||
type: SiliconLawBoundUserInterface
|
||||
#- type: GhostRole
|
||||
# makeSentient: true
|
||||
# name: Maintenance Drone
|
||||
@@ -94,6 +96,12 @@
|
||||
# 2. You may not harm any being, regardless of intent or circumstance.
|
||||
# 3. Your goals are to build, maintain, repair, improve, and power to the best of your abilities, You must never actively work against these goals.
|
||||
#- type: GhostTakeoverAvailable
|
||||
- type: SiliconLawBound
|
||||
- type: SiliconLawProvider
|
||||
laws:
|
||||
- Drone1
|
||||
- Drone2
|
||||
- Drone3
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 5
|
||||
baseSprintSpeed : 5
|
||||
@@ -233,3 +241,17 @@
|
||||
tags:
|
||||
- FootstepSound
|
||||
|
||||
- type: entity
|
||||
id: PlayerBorgGeneric
|
||||
parent: BorgChassisGeneric
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: BorgChassis
|
||||
startingBrain: MMIFilled
|
||||
startingModules:
|
||||
- BorgModuleTool
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cell_slot:
|
||||
name: power-cell-slot-component-slot-name-default
|
||||
startingItem: PowerCellMedium
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user