Хирургия (#607)
* Surgery * translate strings * ru translate * second ru translate * fix * fix pizza icon * пу-пу-пу * fix damageeeee * fix * fix x2)
This commit is contained in:
parent
8bec8a7b02
commit
ad190f1865
156 changed files with 5118 additions and 137 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -306,3 +306,4 @@ Resources/MapImages
|
|||
|
||||
# Direnv stuff
|
||||
.direnv/
|
||||
.idea/
|
||||
17
Content.Client/_Sunrise/ChoiceControl.xaml
Normal file
17
Content.Client/_Sunrise/ChoiceControl.xaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<ui:ChoiceControl
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client._Sunrise">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="Button" Access="Public"
|
||||
HorizontalExpand="True" VerticalExpand="False"
|
||||
StyleClasses="ButtonSquare" Margin="0">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0">
|
||||
<TextureRect Name="Texture" Access="Public"
|
||||
HorizontalExpand="False" VerticalExpand="False"
|
||||
Margin="1"/>
|
||||
<Control MinWidth="5"/>
|
||||
<RichTextLabel Name="NameLabel" Access="Public" VerticalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</ui:ChoiceControl>
|
||||
27
Content.Client/_Sunrise/ChoiceControl.xaml.cs
Normal file
27
Content.Client/_Sunrise/ChoiceControl.xaml.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
// Taken from RMC14 build.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Client._Sunrise;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
[Virtual]
|
||||
public partial class ChoiceControl : Control
|
||||
{
|
||||
public ChoiceControl() => RobustXamlLoader.Load(this);
|
||||
|
||||
public void Set(string name, Texture? texture)
|
||||
{
|
||||
NameLabel.SetMessage(name);
|
||||
Texture.Texture = texture;
|
||||
}
|
||||
|
||||
public void Set(FormattedMessage msg, Texture? texture)
|
||||
{
|
||||
NameLabel.SetMessage(msg);
|
||||
Texture.Texture = texture;
|
||||
}
|
||||
}
|
||||
443
Content.Client/_Sunrise/Medical/Surgery/SurgeryBui.cs
Normal file
443
Content.Client/_Sunrise/Medical/Surgery/SurgeryBui.cs
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
using Content.Client.Administration.UI.CustomControls;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared._Sunrise.Medical.Surgery;
|
||||
using Content.Shared.Body.Part;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Control;
|
||||
|
||||
namespace Content.Client._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14 build.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SurgeryBui : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
private readonly SurgerySystem _system;
|
||||
private readonly HandsSystem _hands;
|
||||
|
||||
[ViewVariables]
|
||||
private SurgeryWindow? _window;
|
||||
|
||||
private EntityUid? _part;
|
||||
private (EntityUid Ent, EntProtoId Proto)? _surgery;
|
||||
private readonly List<EntProtoId> _previousSurgeries = new();
|
||||
|
||||
public SurgeryBui(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_system = _entities.System<SurgerySystem>();
|
||||
_hands = _entities.System<HandsSystem>();
|
||||
|
||||
_system.OnRefresh += UpdateDisabledPanel;
|
||||
_hands.OnPlayerItemAdded += OnPlayerItemAdded;
|
||||
}
|
||||
private DateTime _lastRefresh = DateTime.UtcNow;
|
||||
private (string k1, EntityUid k2) _throttling = ("", new EntityUid());
|
||||
private void OnPlayerItemAdded(string k1, EntityUid k2)
|
||||
{
|
||||
if (_throttling.k1.Equals(k1) && _throttling.k2.Equals(k2) && DateTime.UtcNow - _lastRefresh < TimeSpan.FromSeconds(1)) return;
|
||||
_throttling = (k1, k2);
|
||||
_lastRefresh = DateTime.UtcNow;
|
||||
RefreshUI();
|
||||
}
|
||||
protected override void Open() => UpdateState(State);
|
||||
protected override void UpdateState(BoundUserInterfaceState? state)
|
||||
{
|
||||
if (state is SurgeryBuiState s)
|
||||
Update(s);
|
||||
}
|
||||
|
||||
private void Update(SurgeryBuiState state)
|
||||
{
|
||||
TryInitWindow();
|
||||
|
||||
_window!.Surgeries.DisposeAllChildren();
|
||||
_window.Steps.DisposeAllChildren();
|
||||
_window.Parts.DisposeAllChildren();
|
||||
|
||||
View(ViewType.Parts);
|
||||
|
||||
var oldSurgery = _surgery;
|
||||
var oldPart = _part;
|
||||
_part = null;
|
||||
_surgery = null;
|
||||
|
||||
var parts = new List<Entity<BodyPartComponent>>(state.Choices.Keys.Count);
|
||||
foreach (var choice in state.Choices.Keys)
|
||||
{
|
||||
if (_entities.TryGetEntity(choice, out var ent) &&
|
||||
_entities.TryGetComponent(ent, out BodyPartComponent? part))
|
||||
{
|
||||
parts.Add((ent.Value, part));
|
||||
}
|
||||
}
|
||||
|
||||
parts.Sort((a, b) =>
|
||||
{
|
||||
static int GetScore(Entity<BodyPartComponent> part)
|
||||
=> part.Comp.PartType switch
|
||||
{
|
||||
BodyPartType.Head => 1,
|
||||
BodyPartType.Torso => 2,
|
||||
BodyPartType.Arm => 3,
|
||||
BodyPartType.Hand => 4,
|
||||
BodyPartType.Leg => 5,
|
||||
BodyPartType.Foot => 6,
|
||||
BodyPartType.Tail => 7,
|
||||
BodyPartType.Other => 8,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
return GetScore(a) - GetScore(b);
|
||||
});
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var netPart = _entities.GetNetEntity(part.Owner);
|
||||
var surgeries = state.Choices[netPart];
|
||||
var partName = _entities.GetComponent<MetaDataComponent>(part).EntityName;
|
||||
var partButton = new ChoiceControl();
|
||||
|
||||
partButton.Set(partName, null);
|
||||
partButton.Button.OnPressed += _ => OnPartPressed(netPart, surgeries);
|
||||
|
||||
_window.Parts.AddChild(partButton);
|
||||
|
||||
foreach (var (surgeryId, suffix, isCompleted) in surgeries)
|
||||
{
|
||||
if (_system.GetSingleton(surgeryId) is not { } surgery ||
|
||||
!_entities.TryGetComponent(surgery, out SurgeryComponent? surgeryComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldPart == part && oldSurgery?.Proto == surgeryId)
|
||||
OnSurgeryPressed((surgery, surgeryComp), netPart, surgeryId);
|
||||
}
|
||||
|
||||
if (oldPart == part && oldSurgery == null)
|
||||
OnPartPressed(netPart, surgeries);
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
|
||||
if (!_window.IsOpen)
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
private void TryInitWindow()
|
||||
{
|
||||
if (_window != null) return;
|
||||
_window = new SurgeryWindow();
|
||||
_window.OnClose += Close;
|
||||
_window.Title = Loc.GetString("surgery-window-name");
|
||||
|
||||
_window.PartsButton.OnPressed += _ =>
|
||||
{
|
||||
_part = null;
|
||||
_surgery = null;
|
||||
_previousSurgeries.Clear();
|
||||
View(ViewType.Parts);
|
||||
};
|
||||
|
||||
_window.SurgeriesButton.OnPressed += _ =>
|
||||
{
|
||||
_surgery = null;
|
||||
_previousSurgeries.Clear();
|
||||
|
||||
if (!_entities.TryGetNetEntity(_part, out var netPart) ||
|
||||
State is not SurgeryBuiState s ||
|
||||
!s.Choices.TryGetValue(netPart.Value, out var surgeries))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnPartPressed(netPart.Value, surgeries);
|
||||
};
|
||||
|
||||
_window.StepsButton.OnPressed += _ =>
|
||||
{
|
||||
if (!_entities.TryGetNetEntity(_part, out var netPart) ||
|
||||
_previousSurgeries.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var last = _previousSurgeries[^1];
|
||||
_previousSurgeries.RemoveAt(_previousSurgeries.Count - 1);
|
||||
|
||||
if (_system.GetSingleton(last) is not { } previousId ||
|
||||
!_entities.TryGetComponent(previousId, out SurgeryComponent? previous))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnSurgeryPressed((previousId, previous), netPart.Value, last);
|
||||
};
|
||||
}
|
||||
|
||||
private void AddStep(EntProtoId stepId, NetEntity netPart, EntProtoId surgeryId)
|
||||
{
|
||||
if (_window == null ||
|
||||
_system.GetSingleton(stepId) is not { } step)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stepName = new FormattedMessage();
|
||||
stepName.AddText(_entities.GetComponent<MetaDataComponent>(step).EntityName);
|
||||
|
||||
var stepButton = new SurgeryStepButton { Step = step };
|
||||
stepButton.Button.OnPressed += _ => SendMessage(new SurgeryStepChosenBuiMsg()
|
||||
{
|
||||
Step = stepId,
|
||||
Part = netPart,
|
||||
Surgery = surgeryId,
|
||||
});
|
||||
|
||||
_window.Steps.AddChild(stepButton);
|
||||
}
|
||||
|
||||
private void OnSurgeryPressed(Entity<SurgeryComponent> surgery, NetEntity netPart, EntProtoId surgeryId)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_part = _entities.GetEntity(netPart);
|
||||
_surgery = (surgery, surgeryId);
|
||||
|
||||
_window.Steps.DisposeAllChildren();
|
||||
|
||||
if (surgery.Comp.Requirement is { } requirementId && _system.GetSingleton(requirementId) is { } requirement)
|
||||
{
|
||||
var label = new ChoiceControl();
|
||||
label.Button.OnPressed += _ =>
|
||||
{
|
||||
_previousSurgeries.Add(surgeryId);
|
||||
|
||||
if (_entities.TryGetComponent(requirement, out SurgeryComponent? requirementComp))
|
||||
OnSurgeryPressed((requirement, requirementComp), netPart, requirementId);
|
||||
};
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
var surgeryName = _entities.GetComponent<MetaDataComponent>(requirement).EntityName;
|
||||
msg.AddMarkupOrThrow(Loc.GetString("surgery-window-reguires", ("surgeryname", surgeryName)));
|
||||
label.Set(msg, null);
|
||||
|
||||
_window.Steps.AddChild(label);
|
||||
_window.Steps.AddChild(new HSeparator(Color.FromHex("#4972A1")) { Margin = new Thickness(0, 0, 0, 1) });
|
||||
}
|
||||
|
||||
foreach (var stepId in surgery.Comp.Steps)
|
||||
{
|
||||
AddStep(stepId, netPart, surgeryId);
|
||||
}
|
||||
|
||||
View(ViewType.Steps);
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void OnPartPressed(NetEntity netPart, List<(EntProtoId, string, bool)> surgeryIds)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_part = _entities.GetEntity(netPart);
|
||||
|
||||
_window.Surgeries.DisposeAllChildren();
|
||||
|
||||
var surgeries = new List<(Entity<SurgeryComponent> Ent, EntProtoId Id, string Name, bool IsCompleted, Texture?)>();
|
||||
foreach (var (surgeryId, suffix, isCompleted) in surgeryIds)
|
||||
{
|
||||
if (_system.GetSingleton(surgeryId) is not { } surgery ||
|
||||
!_entities.TryGetComponent(surgery, out SurgeryComponent? surgeryComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var texture = _entities.GetComponentOrNull<SpriteComponent>(surgery)?.Icon?.Default;
|
||||
var name = $"{_entities.GetComponent<MetaDataComponent>(surgery).EntityName} {suffix}";
|
||||
surgeries.Add(((surgery, surgeryComp), surgeryId, name, isCompleted, texture));
|
||||
}
|
||||
|
||||
surgeries.Sort((a, b) =>
|
||||
{
|
||||
var priority = a.Ent.Comp.Priority.CompareTo(b.Ent.Comp.Priority);
|
||||
if (priority != 0)
|
||||
return priority;
|
||||
|
||||
return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
|
||||
});
|
||||
|
||||
foreach (var (Ent, Id, Name, IsCompleted, texture) in surgeries)
|
||||
{
|
||||
var surgeryButton = new ChoiceControl();
|
||||
|
||||
surgeryButton.Set(Name, texture);
|
||||
if(IsCompleted)
|
||||
surgeryButton.Button.Modulate = Color.Green;
|
||||
surgeryButton.Button.OnPressed += _ => OnSurgeryPressed(Ent, netPart, Id);
|
||||
_window.Surgeries.AddChild(surgeryButton);
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
View(ViewType.Surgeries);
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
if (_window == null ||
|
||||
!_entities.HasComponent<SurgeryComponent>(_surgery?.Ent) ||
|
||||
!_entities.TryGetComponent(_part, out BodyPartComponent? part))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var next = _system.GetNextStep(Owner, _part.Value, _surgery.Value.Ent);
|
||||
var i = 0;
|
||||
foreach (var child in _window.Steps.Children)
|
||||
{
|
||||
if (child is not SurgeryStepButton stepButton)
|
||||
continue;
|
||||
|
||||
var status = StepStatus.Incomplete;
|
||||
if (next == null)
|
||||
{
|
||||
status = StepStatus.Complete;
|
||||
}
|
||||
else if (next.Value.Surgery.Owner != _surgery.Value.Ent)
|
||||
{
|
||||
status = StepStatus.Incomplete;
|
||||
}
|
||||
else if (next.Value.Step == i)
|
||||
{
|
||||
status = StepStatus.Next;
|
||||
}
|
||||
else if (i < next.Value.Step)
|
||||
{
|
||||
status = StepStatus.Complete;
|
||||
}
|
||||
|
||||
stepButton.Button.Disabled = status != StepStatus.Next;
|
||||
|
||||
var stepName = new FormattedMessage();
|
||||
stepName.AddText(_entities.GetComponent<MetaDataComponent>(stepButton.Step).EntityName);
|
||||
|
||||
if (status == StepStatus.Complete)
|
||||
{
|
||||
stepButton.Button.Modulate = Color.Green;
|
||||
}
|
||||
else if (status == StepStatus.Next)
|
||||
{
|
||||
stepButton.Button.Modulate = Color.White;
|
||||
if (_player.LocalEntity is { } player &&
|
||||
!_system.CanPerformStep(player, Owner, part.PartType, stepButton.Step, false, out var popup, out var reason, out _))
|
||||
{
|
||||
stepButton.ToolTip = popup;
|
||||
stepButton.Button.Disabled = true;
|
||||
|
||||
switch (reason)
|
||||
{
|
||||
case StepInvalidReason.NeedsOperatingTable:
|
||||
stepName.AddMarkupOrThrow(Loc.GetString("surgery-window-reguires-table"));
|
||||
break;
|
||||
case StepInvalidReason.Armor:
|
||||
stepName.AddMarkupOrThrow(Loc.GetString("surgery-window-reguires-undress"));
|
||||
break;
|
||||
case StepInvalidReason.MissingTool:
|
||||
stepName.AddMarkupOrThrow(Loc.GetString("surgery-window-reguires-tool"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var texture = _entities.GetComponentOrNull<SpriteComponent>(stepButton.Step)?.Icon?.Default;
|
||||
stepButton.Set(stepName, texture);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDisabledPanel()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (_system.IsLyingDown(Owner))
|
||||
{
|
||||
_window.DisabledPanel.Visible = false;
|
||||
_window.DisabledPanel.MouseFilter = MouseFilterMode.Ignore;
|
||||
return;
|
||||
}
|
||||
|
||||
_window.DisabledPanel.Visible = true;
|
||||
if (_window.DisabledLabel.GetMessage() is null)
|
||||
{
|
||||
var text = new FormattedMessage();
|
||||
text.AddMarkupOrThrow(Loc.GetString("surgery-window-reguires-laydown"));
|
||||
_window.DisabledLabel.SetMessage(text);
|
||||
}
|
||||
|
||||
_window.DisabledPanel.MouseFilter = MouseFilterMode.Stop;
|
||||
}
|
||||
|
||||
private void View(ViewType type)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.PartsButton.Parent!.Margin = new Thickness(0, 0, 0, 10);
|
||||
|
||||
_window.Parts.Visible = type == ViewType.Parts;
|
||||
_window.PartsButton.Disabled = type == ViewType.Parts;
|
||||
|
||||
_window.Surgeries.Visible = type == ViewType.Surgeries;
|
||||
_window.SurgeriesButton.Disabled = type != ViewType.Steps;
|
||||
|
||||
_window.Steps.Visible = type == ViewType.Steps;
|
||||
_window.StepsButton.Disabled = type != ViewType.Steps || _previousSurgeries.Count == 0;
|
||||
|
||||
if (_entities.TryGetComponent(_part, out MetaDataComponent? partMeta) &&
|
||||
_entities.TryGetComponent(_surgery?.Ent, out MetaDataComponent? surgeryMeta))
|
||||
{
|
||||
_window.Title = $"{Loc.GetString("surgery-window-name")} - {partMeta.EntityName}, {surgeryMeta.EntityName}";
|
||||
}
|
||||
else if (partMeta != null)
|
||||
{
|
||||
_window.Title = $"{Loc.GetString("surgery-window-name")} - {partMeta.EntityName}";
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.Title = Loc.GetString("surgery-window-name");
|
||||
}
|
||||
}
|
||||
|
||||
private enum ViewType
|
||||
{
|
||||
Parts,
|
||||
Surgeries,
|
||||
Steps
|
||||
}
|
||||
|
||||
private enum StepStatus
|
||||
{
|
||||
Next,
|
||||
Complete,
|
||||
Incomplete
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_window?.Dispose();
|
||||
_system.OnRefresh -= UpdateDisabledPanel;
|
||||
_hands.OnPlayerItemAdded -= OnPlayerItemAdded;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<controls:SurgeryStepButton
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client._Sunrise.Medical.Surgery">
|
||||
</controls:SurgeryStepButton>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SurgeryStepButton : ChoiceControl
|
||||
{
|
||||
public EntityUid Step { get; set; }
|
||||
|
||||
public SurgeryStepButton() => RobustXamlLoader.Load(this);
|
||||
}
|
||||
17
Content.Client/_Sunrise/Medical/Surgery/SurgerySystem.cs
Normal file
17
Content.Client/_Sunrise/Medical/Surgery/SurgerySystem.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Content.Shared._Sunrise.Medical.Surgery;
|
||||
|
||||
namespace Content.Client._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public sealed class SurgerySystem : SharedSurgerySystem
|
||||
{
|
||||
public event Action? OnRefresh;
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_delayAccumulator += frameTime;
|
||||
if (_delayAccumulator > 1) {
|
||||
_delayAccumulator = 0;
|
||||
OnRefresh?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Content.Client/_Sunrise/Medical/Surgery/SurgeryWindow.xaml
Normal file
30
Content.Client/_Sunrise/Medical/Surgery/SurgeryWindow.xaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<controls:SurgeryWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client._Sunrise.Medical.Surgery"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
MinSize="400 400">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 10">
|
||||
<Button Name="PartsButton" Access="Public" Text="{Loc 'surgery-window-partsbutton-name'}"
|
||||
HorizontalExpand="True" StyleClasses="OpenBoth" />
|
||||
<Button Name="SurgeriesButton" Access="Public" Text="{Loc 'surgery-window-surgeriesbutton-name'}"
|
||||
HorizontalExpand="True" StyleClasses="OpenBoth" />
|
||||
<Button Name="StepsButton" Access="Public" Text="{Loc 'surgery-window-stepsbutton-name'}"
|
||||
HorizontalExpand="True" StyleClasses="OpenBoth" />
|
||||
</BoxContainer>
|
||||
<cc:HSeparator Color="#4972A1" />
|
||||
<ScrollContainer VScrollEnabled="True" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Name="Parts" Access="Public" Orientation="Vertical" Visible="False" />
|
||||
<BoxContainer Name="Surgeries" Access="Public" Orientation="Vertical" Visible="False" />
|
||||
<BoxContainer Name="Steps" Access="Public" Orientation="Vertical" Visible="False" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<PanelContainer Name="DisabledPanel" Access="Public" HorizontalExpand="True"
|
||||
VerticalExpand="True" Visible="False">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#000000BF" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<RichTextLabel Name="DisabledLabel" Access="Public" HorizontalAlignment="Center" />
|
||||
</PanelContainer>
|
||||
</controls:SurgeryWindow>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SurgeryWindow : DefaultWindow
|
||||
{
|
||||
public SurgeryWindow() => RobustXamlLoader.Load(this);
|
||||
}
|
||||
262
Content.Server/_Sunrise/Medical/Surgery/SurgerySystem.Steps.cs
Normal file
262
Content.Server/_Sunrise/Medical/Surgery/SurgerySystem.Steps.cs
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared._Sunrise.Medical.Surgery;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Steps.Parts;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Speech.Muting;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared._Sunrise;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
|
||||
namespace Content.Server._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public sealed partial class SurgerySystem : SharedSurgerySystem
|
||||
{
|
||||
public void InitializeSteps()
|
||||
{
|
||||
SubscribeLocalEvent<SurgeryStepBleedEffectComponent, SurgeryStepEvent>(OnStepBleedComplete);
|
||||
SubscribeLocalEvent<SurgeryClampBleedEffectComponent, SurgeryStepEvent>(OnStepClampBleedComplete);
|
||||
SubscribeLocalEvent<SurgeryStepEmoteEffectComponent, SurgeryStepEvent>(OnStepEmoteEffectComplete);
|
||||
SubscribeLocalEvent<SurgeryStepSpawnEffectComponent, SurgeryStepEvent>(OnStepSpawnComplete);
|
||||
|
||||
SubscribeLocalEvent<SurgeryStepOrganExtractComponent, SurgeryStepEvent>(OnStepOrganExtractComplete);
|
||||
SubscribeLocalEvent<SurgeryStepOrganInsertComponent, SurgeryStepEvent>(OnStepOrganInsertComplete);
|
||||
|
||||
SubscribeLocalEvent<SurgeryStepAttachLimbEffectComponent, SurgeryStepEvent>(OnStepAttachLimbComplete);
|
||||
SubscribeLocalEvent<SurgeryStepAmputationEffectComponent, SurgeryStepEvent>(OnStepAmputationComplete);
|
||||
|
||||
SubscribeLocalEvent<SurgeryRemoveAccentComponent, SurgeryStepEvent>(OnRemoveAccent);
|
||||
|
||||
}
|
||||
|
||||
private void OnStepBleedComplete(Entity<SurgeryStepBleedEffectComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if(ent.Comp.Damage is not null && TryComp<DamageableComponent>(args.Body, out var comp))
|
||||
_damageableSystem.SetDamage(args.Body, comp, ent.Comp.Damage);
|
||||
//todo add wound
|
||||
}
|
||||
|
||||
private void OnStepClampBleedComplete(Entity<SurgeryClampBleedEffectComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
//todo remove wound
|
||||
}
|
||||
private void OnStepOrganInsertComplete(Entity<SurgeryStepOrganInsertComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (args.Tools.Count == 0
|
||||
|| !(args.Tools.FirstOrDefault() is var organId)
|
||||
|| !TryComp<BodyPartComponent>(args.Part, out var bodyPart)
|
||||
|| !TryComp<OrganComponent>(organId, out var organComp))
|
||||
return;
|
||||
|
||||
var part = args.Part;
|
||||
var body = args.Body;
|
||||
_delayAccumulator = 0;
|
||||
_delayQueue.Enqueue(() =>
|
||||
{
|
||||
if (_body.InsertOrgan(part, organId, ent.Comp.Slot, bodyPart, organComp)
|
||||
&& TryComp<DamageableComponent>(organId, out var organDamageable)
|
||||
&& TryComp<DamageableComponent>(body, out var bodyDamageable))
|
||||
{
|
||||
if (TryComp<OrganEyesComponent>(organId, out var organEyes)
|
||||
&& TryComp<BlindableComponent>(body, out var blindable))
|
||||
{
|
||||
_blindable.SetMinDamage((body, blindable), organEyes.MinDamage ?? 0);
|
||||
_blindable.AdjustEyeDamage((body, blindable), (organEyes.EyeDamage ?? 0) - blindable.MaxDamage);
|
||||
}
|
||||
if (TryComp<OrganTongueComponent>(organId, out var organTongue)
|
||||
&& !organTongue.IsMuted)
|
||||
RemComp<MutedComponent>(body);
|
||||
|
||||
var change = _damageableSystem.TryChangeDamage(body, organDamageable.Damage, true, false, bodyDamageable);
|
||||
if (change is not null)
|
||||
_damageableSystem.TryChangeDamage(organId, change.Invert(), true, false, organDamageable);
|
||||
}
|
||||
});
|
||||
}
|
||||
private void OnStepOrganExtractComplete(Entity<SurgeryStepOrganExtractComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (ent.Comp.Organ?.Count != 1) return;
|
||||
var organs = _body.GetPartOrgans(args.Part, Comp<BodyPartComponent>(args.Part));
|
||||
var type = ent.Comp.Organ.Values.First().Component.GetType();
|
||||
foreach (var organ in organs)
|
||||
{
|
||||
if (HasComp(organ.Id, type))
|
||||
{
|
||||
if (_body.RemoveOrgan(organ.Id, organ.Component)
|
||||
&& TryComp<OrganDamageComponent>(organ.Id, out var damageRule)
|
||||
&& damageRule.Damage is not null
|
||||
&& TryComp<DamageableComponent>(organ.Id, out var organDamageable)
|
||||
&& TryComp<DamageableComponent>(args.Body, out var bodyDamageable))
|
||||
{
|
||||
if (TryComp<OrganEyesComponent>(organ.Id, out var organEyes)
|
||||
&& TryComp<BlindableComponent>(args.Body, out var blindable))
|
||||
{
|
||||
organEyes.EyeDamage = blindable.EyeDamage;
|
||||
organEyes.MinDamage = blindable.MinDamage;
|
||||
_blindable.UpdateIsBlind((args.Body, blindable));
|
||||
}
|
||||
if (TryComp<OrganTongueComponent>(organ.Id, out var organTongue))
|
||||
{
|
||||
organTongue.IsMuted = HasComp<MutedComponent>(args.Body);
|
||||
AddComp<MutedComponent>(args.Body);
|
||||
}
|
||||
var change = _damageableSystem.TryChangeDamage(args.Body, damageRule.Damage.Invert(), true, false, bodyDamageable);
|
||||
if (change is not null)
|
||||
_damageableSystem.TryChangeDamage(organ.Id, change.Invert(), true, false, organDamageable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemoveAccent(Entity<SurgeryRemoveAccentComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
foreach (var accent in _accents)
|
||||
if (HasComp(args.Body, accent))
|
||||
RemCompDeferred(args.Body, accent);
|
||||
}
|
||||
|
||||
private void OnStepEmoteEffectComplete(Entity<SurgeryStepEmoteEffectComponent> ent, ref SurgeryStepEvent args)
|
||||
=> _chat.TryEmoteWithChat(args.Body, ent.Comp.Emote);
|
||||
private void OnStepSpawnComplete(Entity<SurgeryStepSpawnEffectComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (TryComp(args.Body, out TransformComponent? xform))
|
||||
SpawnAtPosition(ent.Comp.Entity, xform.Coordinates);
|
||||
}
|
||||
private void OnStepAttachLimbComplete(Entity<SurgeryStepAttachLimbEffectComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (args.Tools.Count == 0
|
||||
|| !(args.Tools.FirstOrDefault() is var limbId)
|
||||
|| !TryComp<BodyPartComponent>(args.Part, out var bodyPart)
|
||||
|| !TryComp<BodyPartComponent>(limbId, out var limb))
|
||||
return;
|
||||
|
||||
var part = args.Part;
|
||||
var body = args.Body;
|
||||
|
||||
_delayAccumulator = 0;
|
||||
_delayQueue.Enqueue(() =>
|
||||
{
|
||||
var slot = "";
|
||||
foreach (var slotTemp in _body.TryGetFreePartSlots(part, bodyPart))
|
||||
{
|
||||
slot = slotTemp;
|
||||
if (_body.AttachPart(part, slot, limbId, bodyPart, limb))
|
||||
break;
|
||||
}
|
||||
|
||||
if (TryComp<HumanoidAppearanceComponent>(body, out var humanoid)) //todo move to system
|
||||
{
|
||||
var limbs = _body.GetBodyPartAdjacentParts(limbId, limb).Except([part]).Concat([limbId]);
|
||||
foreach (var partLimbId in limbs)
|
||||
{
|
||||
if (TryComp<BaseLayerIdComponent>(partLimbId, out var baseLayerStorage)
|
||||
&& TryComp(partLimbId, out BodyPartComponent? partLimb))
|
||||
{
|
||||
var layer = partLimb.ToHumanoidLayers();
|
||||
if (layer is null) continue;
|
||||
_humanoidAppearanceSystem.SetBaseLayerId(body, layer.Value, baseLayerStorage.Layer, true, humanoid);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (limb.PartType)
|
||||
{
|
||||
case BodyPartType.Arm: //todo move to systems
|
||||
if (limb.Children.Keys.Count == 0)
|
||||
{
|
||||
_body.TryCreatePartSlot(limbId, limb.Symmetry == BodyPartSymmetry.Left ? "left hand" : "right hand", BodyPartType.Hand, out var slotId);
|
||||
}
|
||||
foreach (var slotId in limb.Children.Keys)
|
||||
{
|
||||
if (slotId is null) continue;
|
||||
var slotFullId = BodySystem.GetPartSlotContainerId(slotId);
|
||||
var child = _containers.GetContainer(limbId, slotFullId);
|
||||
|
||||
foreach (var containedEnt in child.ContainedEntities)
|
||||
{
|
||||
if (TryComp(containedEnt, out BodyPartComponent? innerPart)
|
||||
&& innerPart.PartType == BodyPartType.Hand)
|
||||
_hands.AddHand(body, slotFullId, limb.Symmetry == BodyPartSymmetry.Left ? HandLocation.Left : HandLocation.Right);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BodyPartType.Hand:
|
||||
_hands.AddHand(body, BodySystem.GetPartSlotContainerId(slot), limb.Symmetry == BodyPartSymmetry.Left ? HandLocation.Left : HandLocation.Right);
|
||||
break;
|
||||
case BodyPartType.Leg:
|
||||
case BodyPartType.Foot:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
private void OnStepAmputationComplete(Entity<SurgeryStepAmputationEffectComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (TryComp(args.Body, out TransformComponent? xform)
|
||||
&& TryComp(args.Body, out BodyComponent? body)
|
||||
&& TryComp(args.Part, out BodyPartComponent? limb))
|
||||
{
|
||||
|
||||
if (!_containers.TryGetContainingContainer((args.Part, null, null), out var container)) return;
|
||||
if (_containers.Remove(args.Part, container, destination: xform.Coordinates))
|
||||
{
|
||||
if (TryComp<HumanoidAppearanceComponent>(args.Body, out var humanoid)) //todo move to system
|
||||
{
|
||||
var limbs = _body.GetBodyPartAdjacentParts(args.Part, limb).Concat([args.Part]); ;
|
||||
foreach (var partLimbId in limbs)
|
||||
{
|
||||
if (TryComp<BaseLayerIdComponent>(partLimbId, out var baseLayerStorage)
|
||||
&& TryComp(partLimbId, out BodyPartComponent? partLimb))
|
||||
{
|
||||
var layer = partLimb.ToHumanoidLayers();
|
||||
if (layer is null) continue;
|
||||
if (humanoid.CustomBaseLayers.TryGetValue(layer.Value, out var customBaseLayer))
|
||||
baseLayerStorage.Layer = customBaseLayer.Id;
|
||||
else
|
||||
{
|
||||
var speciesProto = _prototypes.Index(humanoid.Species);
|
||||
var baseSprites = _prototypes.Index<HumanoidSpeciesBaseSpritesPrototype>(speciesProto.SpriteSet);
|
||||
if (baseSprites.Sprites.TryGetValue(layer.Value, out var baseLayer))
|
||||
baseLayerStorage.Layer = baseLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (limb.PartType)
|
||||
{
|
||||
case BodyPartType.Arm: //todo move to systems
|
||||
foreach (var slotId in limb.Children.Keys)
|
||||
{
|
||||
if (slotId is null) continue;
|
||||
var child = _containers.GetContainer(args.Part, BodySystem.GetPartSlotContainerId(slotId));
|
||||
|
||||
foreach (var containedEnt in child.ContainedEntities)
|
||||
{
|
||||
if (TryComp(containedEnt, out BodyPartComponent? innerPart)
|
||||
&& innerPart.PartType == BodyPartType.Hand)
|
||||
_hands.RemoveHand(args.Body, BodySystem.GetPartSlotContainerId(slotId));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BodyPartType.Hand:
|
||||
var parentSlot = _body.GetParentPartAndSlotOrNull(args.Part);
|
||||
if (parentSlot is not null)
|
||||
_hands.RemoveHand(args.Body, BodySystem.GetPartSlotContainerId(parentSlot.Value.Slot));
|
||||
break;
|
||||
case BodyPartType.Leg:
|
||||
case BodyPartType.Foot:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Content.Server/_Sunrise/Medical/Surgery/SurgerySystem.cs
Normal file
135
Content.Server/_Sunrise/Medical/Surgery/SurgerySystem.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._Sunrise.Medical.Surgery;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.HealthExaminable;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public sealed partial class SurgerySystem : SharedSurgerySystem
|
||||
{
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearanceSystem = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly ContainerSystem _containers = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindable = default!;
|
||||
|
||||
private readonly List<EntProtoId> _surgeries = [];
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeSteps();
|
||||
|
||||
SubscribeLocalEvent<SurgeryToolComponent, AfterInteractEvent>(OnToolAfterInteract);
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
|
||||
LoadPrototypes();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_delayAccumulator += frameTime;
|
||||
if (_delayAccumulator > 0.7)
|
||||
{
|
||||
_delayAccumulator = 0;
|
||||
while (_delayQueue.TryDequeue(out var action))
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RefreshUI(EntityUid body)
|
||||
{
|
||||
if (!HasComp<SurgeryTargetComponent>(body))
|
||||
return;
|
||||
|
||||
var surgeries = new Dictionary<NetEntity, List<(EntProtoId, string suffix, bool isCompleted)>>();
|
||||
foreach (var part in _body.GetBodyChildren(body))
|
||||
{
|
||||
if (!TryComp<SurgeryProgressComponent>(part.Id, out var progress))
|
||||
{
|
||||
progress = new SurgeryProgressComponent();
|
||||
AddComp(part.Id, progress);
|
||||
}
|
||||
|
||||
foreach (var surgery in _surgeries)
|
||||
{
|
||||
if (GetSingleton(surgery) is not { } surgeryEnt
|
||||
|| !TryComp(surgeryEnt, out SurgeryComponent? surgeryComp)
|
||||
|| (surgeryComp.Requirement is not null && !progress.CompletedSurgeries.Contains(surgeryComp.Requirement.Value)))
|
||||
continue;
|
||||
|
||||
var ev = new SurgeryValidEvent(body, part.Id);
|
||||
|
||||
var isCompleted = progress.CompletedSurgeries.Contains(surgery);
|
||||
if (!progress.StartedSurgeries.Contains(surgery)
|
||||
&& !isCompleted)
|
||||
{
|
||||
RaiseLocalEvent(surgeryEnt, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
continue;
|
||||
}
|
||||
|
||||
surgeries.GetOrNew(GetNetEntity(part.Id)).Add((surgery, ev.Suffix, isCompleted));
|
||||
}
|
||||
}
|
||||
|
||||
_ui.SetUiState(body, SurgeryUIKey.Key, new SurgeryBuiState() { Choices = surgeries });
|
||||
}
|
||||
|
||||
private void OnToolAfterInteract(Entity<SurgeryToolComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
var user = args.User;
|
||||
if (args.Handled ||
|
||||
!args.CanReach ||
|
||||
args.Target == null ||
|
||||
_ui.IsUiOpen(user, SurgeryUIKey.Key, user) ||
|
||||
!HasComp<SurgeryTargetComponent>(args.Target)) return;
|
||||
|
||||
if (user == args.Target)
|
||||
{
|
||||
_popup.PopupEntity("You can't perform surgery on yourself!", user, user);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
_ui.OpenUi(args.Target.Value, SurgeryUIKey.Key, user);
|
||||
|
||||
RefreshUI(args.Target.Value);
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
if (args.WasModified<EntityPrototype>())
|
||||
LoadPrototypes();
|
||||
}
|
||||
|
||||
private void LoadPrototypes()
|
||||
{
|
||||
_surgeries.Clear();
|
||||
|
||||
foreach (var entity in _prototypes.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (entity.HasComponent<SurgeryComponent>())
|
||||
_surgeries.Add(new EntProtoId(entity.ID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -88,9 +88,12 @@ public sealed partial class BodyPartComponent : Component
|
|||
[DataRecord]
|
||||
public partial struct BodyPartSlot
|
||||
{
|
||||
public string Id;
|
||||
[DataField("id")]
|
||||
public string Id = "";
|
||||
[DataField("type")]
|
||||
public BodyPartType Type;
|
||||
|
||||
public BodyPartSlot() { }
|
||||
public BodyPartSlot(string id, BodyPartType type)
|
||||
{
|
||||
Id = id;
|
||||
|
|
|
|||
|
|
@ -790,5 +790,46 @@ public partial class SharedBodySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetFreePartSlot(EntityUid partId, [NotNullWhen(true)] out string? freeSlotId, BodyPartComponent? part = null)
|
||||
{
|
||||
freeSlotId = null;
|
||||
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
return false;
|
||||
|
||||
foreach (var (slotId, slot) in part.Children)
|
||||
{
|
||||
var containerId = GetPartSlotContainerId(slotId);
|
||||
|
||||
if (!Containers.TryGetContainer(partId, containerId, out var container))
|
||||
continue;
|
||||
|
||||
if (container.ContainedEntities.Count == 0)
|
||||
{
|
||||
freeSlotId = slotId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public IEnumerable<string> TryGetFreePartSlots(EntityUid partId, BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
yield break;
|
||||
|
||||
foreach (var (slotId, slot) in part.Children)
|
||||
{
|
||||
var containerId = GetPartSlotContainerId(slotId);
|
||||
|
||||
if (!Containers.TryGetContainer(partId, containerId, out var container))
|
||||
continue;
|
||||
|
||||
if (container.ContainedEntities.Count == 0)
|
||||
{
|
||||
yield return slotId;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Content.Shared.Damage
|
|||
[JsonPropertyName("types")]
|
||||
[DataField("types", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, DamageTypePrototype>))]
|
||||
[UsedImplicitly]
|
||||
private Dictionary<string,FixedPoint2>? _damageTypeDictionary;
|
||||
private Dictionary<string, FixedPoint2>? _damageTypeDictionary;
|
||||
|
||||
[JsonPropertyName("groups")]
|
||||
[DataField("groups", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, DamageGroupPrototype>))]
|
||||
|
|
@ -71,6 +71,14 @@ namespace Content.Shared.Damage
|
|||
return false;
|
||||
}
|
||||
|
||||
public DamageSpecifier Invert()
|
||||
{
|
||||
var copy = new DamageSpecifier(this);
|
||||
foreach (var key in copy.DamageDict.Keys)
|
||||
copy.DamageDict[key] *= -1;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this damage specifier has any entries.
|
||||
/// </summary>
|
||||
|
|
@ -152,7 +160,7 @@ namespace Content.Shared.Damage
|
|||
if (modifierSet.Coefficients.TryGetValue(key, out var coefficient))
|
||||
newValue *= coefficient; // coefficients can heal you, e.g. cauterizing bleeding
|
||||
|
||||
if(newValue != 0)
|
||||
if (newValue != 0)
|
||||
newDamage.DamageDict[key] = FixedPoint2.New(newValue);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ public sealed partial class DoAfterArgs
|
|||
[DataField]
|
||||
public bool Hidden;
|
||||
|
||||
[DataField]
|
||||
public bool ForceNet;
|
||||
|
||||
#region Event options
|
||||
/// <summary>
|
||||
/// The event that will get raised when the DoAfter has finished. If null, this will simply raise a <see cref="SimpleDoAfterEvent"/>
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
|||
{
|
||||
DebugTools.Assert(args.Broadcast || Exists(args.EventTarget) || args.Event.GetType() == typeof(AwaitedDoAfterEvent));
|
||||
DebugTools.Assert(args.Event.GetType().HasCustomAttribute<NetSerializableAttribute>()
|
||||
|| args.Event.GetType().Namespace is {} ns && ns.StartsWith("Content.IntegrationTests"), // classes defined in tests cannot be marked as serializable.
|
||||
|| args.Event.GetType().Namespace is { } ns && ns.StartsWith("Content.IntegrationTests"), // classes defined in tests cannot be marked as serializable.
|
||||
$"Do after event is not serializable. Event: {args.Event.GetType()}");
|
||||
|
||||
if (!Resolve(args.User, ref comp))
|
||||
|
|
@ -251,7 +251,10 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
|||
{
|
||||
RaiseDoAfterEvents(doAfter, comp);
|
||||
// We don't store instant do-afters. This is just a lazy way of hiding them from client-side visuals.
|
||||
return true;
|
||||
if (!args.ForceNet)
|
||||
return true;
|
||||
else
|
||||
args.Delay = TimeSpan.FromMilliseconds(100);
|
||||
}
|
||||
|
||||
comp.DoAfters.Add(doAfter.Index, doAfter);
|
||||
|
|
@ -295,7 +298,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
|||
return IsDuplicate(args, otherArgs, otherArgs.DuplicateCondition);
|
||||
}
|
||||
|
||||
private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs, DuplicateConditions conditions )
|
||||
private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs, DuplicateConditions conditions)
|
||||
{
|
||||
if ((conditions & DuplicateConditions.SameTarget) != 0
|
||||
&& args.Target != otherArgs.Target)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
using System.Linq;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Steps.Parts;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Rejuvenate;
|
||||
|
|
@ -9,6 +14,7 @@ public sealed class BlindableSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly BlurryVisionSystem _blurriness = default!;
|
||||
[Dependency] private readonly EyeClosingSystem _eyelids = default!;
|
||||
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -36,8 +42,15 @@ public sealed class BlindableSystem : EntitySystem
|
|||
|
||||
var old = blindable.Comp.IsBlind;
|
||||
|
||||
var forceBlind = false;
|
||||
if(TryComp<BodyComponent>(blindable.Owner, out var body))
|
||||
{
|
||||
var eyes = _bodySystem.GetBodyOrganEntityComps<OrganEyesComponent>((blindable.Owner, body));
|
||||
forceBlind = eyes.Count == 0;
|
||||
}
|
||||
|
||||
// Don't bother raising an event if the eye is too damaged.
|
||||
if (blindable.Comp.EyeDamage >= blindable.Comp.MaxDamage)
|
||||
if (blindable.Comp.EyeDamage >= blindable.Comp.MaxDamage || forceBlind)
|
||||
{
|
||||
blindable.Comp.IsBlind = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,5 +44,5 @@ public sealed partial class TechDisciplinePrototype : IPrototype
|
|||
/// Purchasing this tier of technology causes a server to become "locked" to this discipline.
|
||||
/// </summary>
|
||||
[DataField("lockoutTier")]
|
||||
public int LockoutTier = 3;
|
||||
public int LockoutTier = 4;
|
||||
}
|
||||
|
|
|
|||
14
Content.Shared/_Sunrise/BaseLayerIdComponent.cs
Normal file
14
Content.Shared/_Sunrise/BaseLayerIdComponent.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class BaseLayerIdComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? Layer;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedSurgerySystem))]
|
||||
[EntityCategory("Surgeries")]
|
||||
public sealed partial class SurgeryComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField, Access(typeof(SharedSurgerySystem), Other = AccessPermissions.ReadWriteExecute)]
|
||||
public int Priority;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntProtoId? Requirement;
|
||||
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public List<EntProtoId> Steps = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryProgressComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntProtoId> CompletedSteps = [];
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntProtoId> CompletedSurgeries = [];
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntProtoId> StartedSurgeries = [];
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Steps;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedSurgerySystem))]
|
||||
[EntityCategory("SurgerySteps")]
|
||||
public sealed partial class SurgeryStepComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Duration = 2;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? Tools;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? Add;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? BodyAdd;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? Remove;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? BodyRemove;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Robust.Shared.GameStates;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryTargetComponent : Component;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryAnyAccentConditionComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryAnyLimbSlotConditionComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryOperatingTableConditionComponent : Component;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryPartConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public List<BodyPartType> Parts = [];
|
||||
}
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryOrganExistConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ComponentRegistry? Organ;
|
||||
}
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryOrganDontExistConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ComponentRegistry? Organ;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Steps.Parts;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganBrainComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganAppendixComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganEarsComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganLungsComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganHeartComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganStomachComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganLiverComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class OrganKidneysComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class OrganTongueComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool IsMuted;
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class OrganEyesComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public int? EyeDamage;
|
||||
[DataField]
|
||||
public int? MinDamage;
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class OrganDamageComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public DamageSpecifier? Damage;
|
||||
}
|
||||
22
Content.Shared/_Sunrise/Medical/Surgery/Components/_Parts.cs
Normal file
22
Content.Shared/_Sunrise/Medical/Surgery/Components/_Parts.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Steps.Parts;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class IncisionOpenComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SkinRetractedComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class BleedersClampedComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryStepOrganExtractComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ComponentRegistry? Organ;
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryStepOrganInsertComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public string Slot;
|
||||
}
|
||||
32
Content.Shared/_Sunrise/Medical/Surgery/Components/_Steps.cs
Normal file
32
Content.Shared/_Sunrise/Medical/Surgery/Components/_Steps.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryClampBleedEffectComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryStepAttachLimbEffectComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryStepBleedEffectComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public DamageSpecifier? Damage;
|
||||
};
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryStepAmputationEffectComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryRemoveAccentComponent : Component;
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))] public sealed partial class SurgeryClearProgressComponent : Component;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryStepEmoteEffectComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<EmotePrototype> Emote = "Scream";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryStepSpawnEffectComponent : Component
|
||||
{
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public EntProtoId Entity;
|
||||
}
|
||||
71
Content.Shared/_Sunrise/Medical/Surgery/Components/_Tools.cs
Normal file
71
Content.Shared/_Sunrise/Medical/Surgery/Components/_Tools.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgeryToolComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Speed = 1;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundSpecifier? StartSound;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundSpecifier? EndSound;
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class OperatingTableComponent : Component;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class BoneGelComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "bone gel";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class BoneSawComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a bone saw";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class BoneSetterComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a bone setter";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class CauteryComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a cautery";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class HemostatComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a hemostat";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class RetractorComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a retractor";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class ScalpelComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a scalpel";
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSurgerySystem))]
|
||||
public sealed partial class SurgicalDrillComponent : Component, ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName => "a surgical drill";
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using Content.Shared.Inventory;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct SurgeryCanPerformStepEvent(
|
||||
EntityUid User,
|
||||
EntityUid Body,
|
||||
List<EntityUid> Tools,
|
||||
SlotFlags TargetSlots,
|
||||
string? Popup = null,
|
||||
StepInvalidReason Invalid = StepInvalidReason.None
|
||||
) : IInventoryRelayEvent
|
||||
{
|
||||
public HashSet<EntityUid> ValidTools = [];
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class SurgeryDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public readonly EntProtoId Surgery;
|
||||
public readonly EntProtoId Step;
|
||||
|
||||
public SurgeryDoAfterEvent(EntProtoId surgery, EntProtoId step)
|
||||
{
|
||||
Surgery = surgery;
|
||||
Step = step;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the step entity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SurgeryStepEvent(EntityUid User, EntityUid Body, EntityUid Part, List<EntityUid> Tools)
|
||||
{
|
||||
public required EntProtoId StepProto { get; init; }
|
||||
public required EntProtoId SurgeryProto { get; init; }
|
||||
public required bool IsFinal { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
/// <summary>
|
||||
/// Raised on the entity that is receiving surgery.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SurgeryValidEvent(EntityUid Body, EntityUid Part, bool Cancelled = false, string Suffix = "");
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public interface ISurgeryToolComponent
|
||||
{
|
||||
public string ToolName { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
using Content.Shared._Sunrise.Medical.Surgery.Steps;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public abstract partial class SharedSurgerySystem
|
||||
{
|
||||
|
||||
protected float _delayAccumulator = 0f;
|
||||
protected readonly Queue<Action> _delayQueue = new();
|
||||
private void InitializeSteps()
|
||||
{
|
||||
SubscribeLocalEvent<SurgeryStepComponent, SurgeryStepEvent>(OnStep);
|
||||
SubscribeLocalEvent<SurgeryClearProgressComponent, SurgeryStepEvent>(OnClearProgressStep);
|
||||
SubscribeLocalEvent<SurgeryTargetComponent, SurgeryDoAfterEvent>(OnTargetDoAfter);
|
||||
|
||||
SubscribeLocalEvent<SurgeryStepComponent, SurgeryCanPerformStepEvent>(OnCanPerformStep);
|
||||
|
||||
Subs.BuiEvents<SurgeryTargetComponent>(SurgeryUIKey.Key, subs => subs.Event<SurgeryStepChosenBuiMsg>(OnSurgeryTargetStepChosen));
|
||||
}
|
||||
private void OnTargetDoAfter(Entity<SurgeryTargetComponent> ent, ref SurgeryDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled ||
|
||||
args.Handled ||
|
||||
args.Target is not { } target ||
|
||||
!IsSurgeryValid(ent, target, args.Surgery, args.Step, out var surgery, out var part, out var step) ||
|
||||
!PreviousStepsComplete(ent, part, surgery, args.Step) ||
|
||||
!CanPerformStep(args.User, ent, part.Comp.PartType, step, false))
|
||||
{
|
||||
Log.Warning($"{ToPrettyString(args.User)} tried to start invalid surgery.");
|
||||
Dirty(ent);
|
||||
if (args.Target.HasValue && TryComp<BodyPartComponent>(args.Target.Value, out var dirtyPart))
|
||||
Dirty(args.Target.Value, dirtyPart, Comp<MetaDataComponent>(args.Target.Value));
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new SurgeryStepEvent(args.User, ent, part, GetTools(args.User))
|
||||
{
|
||||
StepProto = args.Step,
|
||||
SurgeryProto = args.Surgery,
|
||||
IsFinal = surgery.Comp.Steps[^1] == args.Step,
|
||||
};
|
||||
RaiseLocalEvent(step, ref ev);
|
||||
|
||||
if (_net.IsClient) return;
|
||||
_delayAccumulator = 0f;
|
||||
_delayQueue.Enqueue(() => RefreshUI(ent));
|
||||
}
|
||||
|
||||
private void OnClearProgressStep(Entity<SurgeryClearProgressComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
var progress = Comp<SurgeryProgressComponent>(args.Part);
|
||||
progress.CompletedSteps.Clear();
|
||||
progress.CompletedSurgeries.Clear();
|
||||
}
|
||||
|
||||
private void OnStep(Entity<SurgeryStepComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (!TryComp<SurgeryClearProgressComponent>(ent, out _))
|
||||
{
|
||||
if (TryComp<SurgeryProgressComponent>(args.Part, out var progress))
|
||||
{
|
||||
progress.CompletedSteps.Add($"{args.SurgeryProto}:{args.StepProto}");
|
||||
if(!progress.StartedSurgeries.Contains(args.SurgeryProto) && !args.IsFinal)
|
||||
progress.StartedSurgeries.Add(args.SurgeryProto);
|
||||
if (progress.StartedSurgeries.Contains(args.SurgeryProto) && args.IsFinal)
|
||||
progress.StartedSurgeries.Remove(args.SurgeryProto);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress = new SurgeryProgressComponent { CompletedSteps = [$"{args.SurgeryProto}:{args.StepProto}"] };
|
||||
AddComp(args.Part, progress);
|
||||
}
|
||||
if (args.IsFinal)
|
||||
progress.CompletedSurgeries.Add(args.SurgeryProto);
|
||||
}
|
||||
|
||||
foreach (var reg in (ent.Comp.Tools ?? []).Values)
|
||||
{
|
||||
var tool = args.Tools.FirstOrDefault(x => HasComp(x, reg.Component.GetType()));
|
||||
if (tool == default) return;
|
||||
|
||||
if (_net.IsServer && TryComp(tool, out SurgeryToolComponent? toolComp) && toolComp.EndSound != null)
|
||||
_audio.PlayPvs(toolComp.EndSound, tool);
|
||||
}
|
||||
|
||||
foreach (var reg in (ent.Comp.Add ?? []).Values)
|
||||
{
|
||||
var compType = reg.Component.GetType();
|
||||
if (HasComp(args.Part, compType))
|
||||
continue;
|
||||
var newComp = _compFactory.GetComponent(compType);
|
||||
_serialization.CopyTo(reg.Component, ref newComp, notNullableOverride: true);
|
||||
AddComp(args.Part, newComp);
|
||||
}
|
||||
|
||||
foreach (var reg in (ent.Comp.BodyAdd ?? []).Values)
|
||||
{
|
||||
var compType = reg.Component.GetType();
|
||||
if (HasComp(args.Body, compType))
|
||||
continue;
|
||||
|
||||
AddComp(args.Part, _compFactory.GetComponent(compType));
|
||||
}
|
||||
|
||||
foreach (var reg in (ent.Comp.Remove ?? []).Values)
|
||||
RemComp(args.Part, reg.Component.GetType());
|
||||
|
||||
foreach (var reg in (ent.Comp.BodyRemove ?? []).Values)
|
||||
RemComp(args.Body, reg.Component.GetType());
|
||||
}
|
||||
|
||||
private void OnCanPerformStep(Entity<SurgeryStepComponent> ent, ref SurgeryCanPerformStepEvent args)
|
||||
{
|
||||
if (HasComp<SurgeryOperatingTableConditionComponent>(ent)
|
||||
&& (!TryComp(args.Body, out BuckleComponent? buckle) || !HasComp<OperatingTableComponent>(buckle.BuckledTo)))
|
||||
{
|
||||
args.Invalid = StepInvalidReason.NeedsOperatingTable;
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(args.Body, ref args);
|
||||
|
||||
if (args.Invalid != StepInvalidReason.None || ent.Comp.Tools == null)
|
||||
return;
|
||||
|
||||
foreach (var reg in ent.Comp.Tools.Values)
|
||||
{
|
||||
var tool = args.Tools.FirstOrDefault(x => HasComp(x, reg.Component.GetType()));
|
||||
if (tool == default)
|
||||
{
|
||||
args.Invalid = StepInvalidReason.MissingTool;
|
||||
|
||||
if (reg.Component is ISurgeryToolComponent toolComp)
|
||||
args.Popup = $"You need {toolComp.ToolName} to perform this step!";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
args.ValidTools.Add(tool);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSurgeryTargetStepChosen(Entity<SurgeryTargetComponent> ent, ref SurgeryStepChosenBuiMsg args)
|
||||
{
|
||||
var user = args.Actor;
|
||||
if (GetEntity(args.Entity) is not { Valid: true } body
|
||||
|| GetEntity(args.Part) is not { Valid: true } targetPart
|
||||
|| !IsSurgeryValid(body, targetPart, args.Surgery, args.Step, out var surgery, out var part, out var step)
|
||||
|| GetSingleton(args.Step) is not { } stepEnt
|
||||
|| !TryComp(stepEnt, out SurgeryStepComponent? stepComp)
|
||||
|| !CanPerformStep(user, body, part.Comp.PartType, step, true, out _, out _, out var validTools))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!PreviousStepsComplete(body, part, surgery, args.Step) || IsStepComplete(part, args.Surgery, args.Step))
|
||||
{
|
||||
var progress = Comp<SurgeryProgressComponent>(part);
|
||||
Dirty(part, progress);
|
||||
_delayAccumulator = 0f;
|
||||
_delayQueue.Enqueue(() => RefreshUI(body));
|
||||
return;
|
||||
}
|
||||
|
||||
var duration = stepComp.Duration;
|
||||
|
||||
foreach (var tool in validTools)
|
||||
if (TryComp(tool, out SurgeryToolComponent? toolComp))
|
||||
{
|
||||
duration *= toolComp.Speed;
|
||||
if (toolComp.StartSound != null) _audio.PlayPvs(toolComp.StartSound, tool);
|
||||
}
|
||||
|
||||
if (TryComp(body, out TransformComponent? xform))
|
||||
_rotateToFace.TryFaceCoordinates(user, _transform.GetMapCoordinates(body, xform).Position);
|
||||
|
||||
var ev = new SurgeryDoAfterEvent(args.Surgery, args.Step);
|
||||
var doAfter = new DoAfterArgs(EntityManager, user, duration, ev, body, part)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
DuplicateCondition = DuplicateConditions.SameTarget,
|
||||
ForceNet = true
|
||||
};
|
||||
_doAfter.TryStartDoAfter(doAfter);
|
||||
}
|
||||
|
||||
public (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, EntityUid surgery) => GetNextStep(body, part, surgery, []);
|
||||
private (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, Entity<SurgeryComponent?> surgery, List<EntityUid> requirements)
|
||||
{
|
||||
if (!Resolve(surgery, ref surgery.Comp))
|
||||
return null;
|
||||
|
||||
if (requirements.Contains(surgery))
|
||||
throw new ArgumentException($"Surgery {surgery} has a requirement loop: {string.Join(", ", requirements)}");
|
||||
|
||||
requirements.Add(surgery);
|
||||
|
||||
if (surgery.Comp.Requirement is { } requirementId &&
|
||||
GetSingleton(requirementId) is { } requirement &&
|
||||
GetNextStep(body, part, requirement, requirements) is { } requiredNext)
|
||||
return requiredNext;
|
||||
|
||||
if (!TryComp<SurgeryProgressComponent>(part, out var progress))
|
||||
{
|
||||
AddComp<SurgeryProgressComponent>(part);
|
||||
return ((surgery, surgery.Comp), 0);
|
||||
}
|
||||
var surgeryProto = Prototype(surgery);
|
||||
for (var i = 0; i < surgery.Comp.Steps.Count; i++)
|
||||
if (!progress.CompletedSteps.Contains($"{surgeryProto?.ID}:{surgery.Comp.Steps[i]}"))
|
||||
return ((surgery, surgery.Comp), i);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool PreviousStepsComplete(EntityUid body, EntityUid part, Entity<SurgeryComponent> surgery, EntProtoId step)
|
||||
{
|
||||
if (surgery.Comp.Requirement is { } requirement)
|
||||
{
|
||||
if (GetSingleton(requirement) is not { } requiredEnt ||
|
||||
!TryComp(requiredEnt, out SurgeryComponent? requiredComp) ||
|
||||
!PreviousStepsComplete(body, part, (requiredEnt, requiredComp), step))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var surgeryStep in surgery.Comp.Steps)
|
||||
{
|
||||
if (surgeryStep == step)
|
||||
break;
|
||||
|
||||
if (Prototype(surgery.Owner) is not EntityPrototype surgProto || !IsStepComplete(part, surgProto.ID, surgeryStep))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanPerformStep(EntityUid user, EntityUid body, BodyPartType part, EntityUid step, bool doPopup) => CanPerformStep(user, body, part, step, doPopup, out _, out _, out _);
|
||||
public bool CanPerformStep(EntityUid user, EntityUid body, BodyPartType part, EntityUid step, bool doPopup, out string? popup, out StepInvalidReason reason, out HashSet<EntityUid> validTools)
|
||||
{
|
||||
var slot = part switch
|
||||
{
|
||||
BodyPartType.Head => SlotFlags.HEAD,
|
||||
BodyPartType.Torso => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING,
|
||||
BodyPartType.Arm => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING,
|
||||
BodyPartType.Hand => SlotFlags.GLOVES,
|
||||
BodyPartType.Leg => SlotFlags.OUTERCLOTHING | SlotFlags.LEGS,
|
||||
BodyPartType.Foot => SlotFlags.FEET,
|
||||
BodyPartType.Tail => SlotFlags.NONE,
|
||||
BodyPartType.Other => SlotFlags.NONE,
|
||||
_ => SlotFlags.NONE
|
||||
};
|
||||
|
||||
var check = new SurgeryCanPerformStepEvent(user, body, GetTools(user), slot);
|
||||
RaiseLocalEvent(step, ref check);
|
||||
popup = check.Popup;
|
||||
validTools = check.ValidTools;
|
||||
|
||||
if (check.Invalid != StepInvalidReason.None)
|
||||
{
|
||||
if (doPopup && check.Popup != null)
|
||||
_popup.PopupEntity(check.Popup, user, PopupType.SmallCaution);
|
||||
|
||||
reason = check.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsStepComplete(EntityUid part, EntProtoId surgery, EntProtoId step)
|
||||
{
|
||||
if (TryComp<SurgeryProgressComponent>(part, out var comp))
|
||||
return comp.CompletedSteps.Contains($"{surgery}:{step}");
|
||||
AddComp<SurgeryProgressComponent>(part);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using Content.Shared.Body.Part;
|
||||
using System.Linq;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Steps.Parts;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public abstract partial class SharedSurgerySystem
|
||||
{
|
||||
protected List<Type> _accents = [];
|
||||
private void InitializeConditions()
|
||||
{
|
||||
_accents = _reflectionManager.FindTypesWithAttribute<RegisterComponentAttribute>()
|
||||
.Where(type => type.Name.EndsWith("AccentComponent"))
|
||||
.ToList();
|
||||
|
||||
SubscribeLocalEvent<SurgeryPartConditionComponent, SurgeryValidEvent>(OnPartConditionValid);
|
||||
SubscribeLocalEvent<SurgeryOrganExistConditionComponent, SurgeryValidEvent>(OnOrganExistConditionValid);
|
||||
SubscribeLocalEvent<SurgeryOrganDontExistConditionComponent, SurgeryValidEvent>(OnOrganDontExistConditionValid);
|
||||
SubscribeLocalEvent<SurgeryAnyAccentConditionComponent, SurgeryValidEvent>(OnAnyAccentConditionValid);
|
||||
SubscribeLocalEvent<SurgeryAnyLimbSlotConditionComponent, SurgeryValidEvent>(OnAnyLimbSlotConditionValid);
|
||||
}
|
||||
private void OnOrganDontExistConditionValid(Entity<SurgeryOrganDontExistConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
if (ent.Comp.Organ?.Count != 1) return;
|
||||
var type = ent.Comp.Organ.Values.First().Component.GetType();
|
||||
|
||||
var organs = _body.GetPartOrgans(args.Part, Comp<BodyPartComponent>(args.Part));
|
||||
foreach (var organ in organs)
|
||||
if (HasComp(organ.Id, type))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
private void OnOrganExistConditionValid(Entity<SurgeryOrganExistConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
if (ent.Comp.Organ?.Count != 1) return;
|
||||
var organs = _body.GetPartOrgans(args.Part, Comp<BodyPartComponent>(args.Part));
|
||||
var type = ent.Comp.Organ.Values.First().Component.GetType();
|
||||
foreach (var organ in organs)
|
||||
if (HasComp(organ.Id, type))
|
||||
return;
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnPartConditionValid(Entity<SurgeryPartConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
if (ent.Comp.Parts.Count == 0)
|
||||
return;
|
||||
|
||||
if (CompOrNull<BodyPartComponent>(args.Part)?.PartType is BodyPartType part && !ent.Comp.Parts.Contains(part))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
private void OnAnyAccentConditionValid(Entity<SurgeryAnyAccentConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
foreach (var accent in _accents)
|
||||
if (HasComp(args.Body, accent))
|
||||
return;
|
||||
args.Cancelled = true;
|
||||
}
|
||||
private void OnAnyLimbSlotConditionValid(Entity<SurgeryAnyLimbSlotConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
if (CompOrNull<BodyPartComponent>(args.Part) is not BodyPartComponent bodyPartComponent)
|
||||
return;
|
||||
|
||||
if (_body.TryGetFreePartSlot(args.Part, out var slotId, bodyPartComponent))
|
||||
args.Suffix = slotId;
|
||||
else
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
142
Content.Shared/_Sunrise/Medical/Surgery/SharedSurgerySystem.cs
Normal file
142
Content.Shared/_Sunrise/Medical/Surgery/SharedSurgerySystem.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Effects.Step;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Events;
|
||||
using Content.Shared._Sunrise.Medical.Surgery.Steps.Parts;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public abstract partial class SharedSurgerySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotateToFace = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standing = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
|
||||
private readonly Dictionary<EntProtoId, EntityUid> _surgeries = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
|
||||
|
||||
InitializeSteps();
|
||||
InitializeConditions();
|
||||
}
|
||||
|
||||
private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_surgeries.Clear();
|
||||
}
|
||||
|
||||
protected bool IsSurgeryValid(EntityUid body, EntityUid targetPart, EntProtoId surgery, EntProtoId stepId, out Entity<SurgeryComponent> surgeryEnt, out Entity<BodyPartComponent> part, out EntityUid step)
|
||||
{
|
||||
surgeryEnt = default;
|
||||
part = default;
|
||||
step = default;
|
||||
|
||||
if (!HasComp<SurgeryTargetComponent>(body) ||
|
||||
!IsLyingDown(body) ||
|
||||
!TryComp(targetPart, out BodyPartComponent? partComp) ||
|
||||
GetSingleton(surgery) is not { } surgeryEntId ||
|
||||
!TryComp(surgeryEntId, out SurgeryComponent? surgeryComp) ||
|
||||
!surgeryComp.Steps.Contains(stepId) ||
|
||||
GetSingleton(stepId) is not { } stepEnt) return false;
|
||||
|
||||
var ev = new SurgeryValidEvent(body, targetPart);
|
||||
|
||||
if (!TryComp<SurgeryProgressComponent>(targetPart, out var progress))
|
||||
{
|
||||
progress = new SurgeryProgressComponent();
|
||||
AddComp(targetPart, progress);
|
||||
}
|
||||
|
||||
if (!progress.StartedSurgeries.Contains(surgery))
|
||||
{
|
||||
RaiseLocalEvent(stepEnt, ref ev);
|
||||
RaiseLocalEvent(surgeryEntId, ref ev);
|
||||
}
|
||||
|
||||
if (ev.Cancelled)
|
||||
return false;
|
||||
|
||||
surgeryEnt = (surgeryEntId, surgeryComp);
|
||||
part = (targetPart, partComp);
|
||||
step = stepEnt;
|
||||
return true;
|
||||
}
|
||||
|
||||
public EntityUid? GetSingleton(EntProtoId surgeryOrStep)
|
||||
{
|
||||
if (!_prototypes.HasIndex(surgeryOrStep))
|
||||
return null;
|
||||
|
||||
// This (for now) assumes that surgery entity data remains unchanged between client
|
||||
// and server
|
||||
// if it does not you get the bullet
|
||||
if (!_surgeries.TryGetValue(surgeryOrStep, out var ent) || TerminatingOrDeleted(ent))
|
||||
{
|
||||
ent = Spawn(surgeryOrStep, MapCoordinates.Nullspace);
|
||||
_surgeries[surgeryOrStep] = ent;
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
protected List<EntityUid> GetTools(EntityUid surgeon)
|
||||
{
|
||||
return _hands.EnumerateHeld(surgeon).ToList();
|
||||
}
|
||||
|
||||
public bool IsLyingDown(EntityUid entity)
|
||||
{
|
||||
if (_standing.IsDown(entity))
|
||||
return true;
|
||||
|
||||
if (TryComp(entity, out BuckleComponent? buckle) &&
|
||||
TryComp(buckle.BuckledTo, out StrapComponent? strap))
|
||||
{
|
||||
var rotation = strap.Rotation;
|
||||
if (rotation.GetCardinalDir() is Direction.West or Direction.East)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void RefreshUI(EntityUid body)
|
||||
{
|
||||
}
|
||||
}
|
||||
10
Content.Shared/_Sunrise/Medical/Surgery/StepInvalidReason.cs
Normal file
10
Content.Shared/_Sunrise/Medical/Surgery/StepInvalidReason.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
public enum StepInvalidReason
|
||||
{
|
||||
None,
|
||||
NeedsOperatingTable,
|
||||
Armor,
|
||||
MissingTool,
|
||||
}
|
||||
25
Content.Shared/_Sunrise/Medical/Surgery/SurgeryUI.cs
Normal file
25
Content.Shared/_Sunrise/Medical/Surgery/SurgeryUI.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Sunrise.Medical.Surgery;
|
||||
// Based on the RMC14.
|
||||
// https://github.com/RMC-14/RMC-14
|
||||
[Serializable, NetSerializable]
|
||||
public enum SurgeryUIKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryBuiState : BoundUserInterfaceState
|
||||
{
|
||||
public required Dictionary<NetEntity, List<(EntProtoId, string, bool)>> Choices { get; init; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryStepChosenBuiMsg : BoundUserInterfaceMessage
|
||||
{
|
||||
public required NetEntity Part { get; init; }
|
||||
public required EntProtoId Surgery { get; init; }
|
||||
public required EntProtoId Step { get; init; }
|
||||
}
|
||||
49
Resources/Audio/_Sunrise/Medical/Surgery/attributions.yml
Normal file
49
Resources/Audio/_Sunrise/Medical/Surgery/attributions.yml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
- files: ["cautery1.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/cautery1.ogg"
|
||||
|
||||
- files: ["cautery2.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/cautery2.ogg"
|
||||
|
||||
- files: ["hemostat.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/hemostat.ogg"
|
||||
|
||||
- files: ["organ1.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/organ1.ogg"
|
||||
|
||||
- files: ["organ2.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/organ2.ogg"
|
||||
|
||||
- files: ["retractor1.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/retractor1.ogg"
|
||||
|
||||
- files: ["retractor2.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/retractor2.ogg"
|
||||
|
||||
- files: ["saw.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/saw.ogg"
|
||||
|
||||
- files: ["scalpel1.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/scalpel1.ogg"
|
||||
|
||||
- files: ["scalpel2.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from cmss13"
|
||||
source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/scalpel2.ogg"
|
||||
BIN
Resources/Audio/_Sunrise/Medical/Surgery/cautery1.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/cautery1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/cautery2.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/cautery2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/hemostat1.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/hemostat1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/organ1.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/organ1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/organ2.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/organ2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/retractor1.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/retractor1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/retractor2.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/retractor2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/saw.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/saw.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/scalpel1.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/scalpel1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Medical/Surgery/scalpel2.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Medical/Surgery/scalpel2.ogg
Normal file
Binary file not shown.
2
Resources/Locale/en-US/_strings/_sunrise/categories.ftl
Normal file
2
Resources/Locale/en-US/_strings/_sunrise/categories.ftl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
categories-surgeries = surgeries
|
||||
categories-surgery-steps = surgery steps
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
lathe-category-surgery = Surgery
|
||||
lathe-category-cyberlimbs = Limbs
|
||||
lathe-category-reports = Reports
|
||||
lathe-category-statements = Statements
|
||||
lathe-category-inquiries-and-appeals = Inquiries and appeals
|
||||
|
|
|
|||
|
|
@ -1,2 +1,8 @@
|
|||
research-discipline-biochemical = Biochemical
|
||||
|
||||
research-technology-basic-surgery = Basic surgery
|
||||
research-technology-basic-cyberlimbs = Basic cyberlimbs
|
||||
research-technology-advanced-surgery = Advanced surgery
|
||||
|
||||
research-technology-handcraft-nvd = Кустарные ПНВ
|
||||
research-technology-basic-nvd = Продвинутое ПНВ
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
ent-PartCyber = кибернетическая часть тела
|
||||
.desc = { ent-BaseItem.desc }
|
||||
ent-LeftArmCyber = левая кибернетическая рука
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-RightArmCyber = правая кибернетическая рука
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-LeftHandCyber = левая кибернетическая кисть
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-RightHandCyber = правая кибернетическая кисть
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-LeftLegCyber = левая кибернетическая нога
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-RightLegCyber = правая кибернетическая нога
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-LeftFootCyber = левая кибернетическая ступня
|
||||
.desc = { ent-PartCyber.desc }
|
||||
ent-RightFootCyber = правая кибернетическая ступня
|
||||
.desc = { ent-PartCyber.desc }
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
ent-ExpCollarsKit = Коробка с взрывными ошейниками
|
||||
ent-ExpCollarsKit = Коробка с взрывными ошейниками
|
||||
.desc = Ужасающе
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ ent-ClothingBeltMedicalCMO = медицинский пояс главного в
|
|||
.desc = Стерильный пояс со множеством карманов под таблетки и другие лекарства, подчёркивающий, что лечить нужно со стилем.
|
||||
ent-ClothingBeltReaperWebbing = разгрузочный жилет
|
||||
.desc = Тактическая разгрузка, которую носят десантники Синдиката.
|
||||
ent-ClothingBeltSheathSyndicateFilled = {ent-ClothingBeltSheathSyndicate}
|
||||
ent-ClothingBeltSheathSyndicateFilled = { ent-ClothingBeltSheathSyndicate }
|
||||
.desc = { ent-ClothingBeltSheathSyndicate.desc }
|
||||
ent-ClothingBeltSheathSyndicate = ножны для рапиры
|
||||
.desc = Зловещие тонкие ножны, подходящие для рапиры.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
ent-ClothingEyesUniversalMedicalHud = универсальный медицинский визор
|
||||
.desc = Дисплей, сочетающий в себе преимущества медицинского сканера и очков для химического анализа.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ent-ClothingHandsClockDivine = божественные часы
|
||||
.desc = Эти часы может носить только божественная личность.
|
||||
.desc = Эти часы может носить только божественная личность.
|
||||
|
|
|
|||
|
|
@ -49,4 +49,4 @@ ent-ClothingHeadHatGarrisonCapAdjutant = пилотка адъютанта
|
|||
ent-ClothingHeadHatCapAdjutant = фуражка адъютанта
|
||||
.desc = Синяя с белым, подозрительно напоминает капитанскую.
|
||||
ent-ClothingHeadHatWithPaces = Шляпа с пейсами
|
||||
.desc = Чёрная шляпа с пейсами, обычно такую носят евреи.
|
||||
.desc = Чёрная шляпа с пейсами, обычно такую носят евреи.
|
||||
|
|
|
|||
|
|
@ -42,4 +42,3 @@ ent-ClothingCloakAtmosian = плащ атмосианина
|
|||
.desc = Плащ легендарного атмосианина.
|
||||
ent-ClothingNeckCloakUeg = плащ десантника ОПЗ
|
||||
.desc = Плащ знаменитых космических десантников Объединённого Правительства Земли. Его носят только поистине те, кто смог удостоится чести быть космическим десантником ОПЗ. Этот плащ олицетворяет свободу и демократию, к которым должна стремиться цивилизация. В ином случае - демократию и освобождение принесёт Правительство Земли.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ent-ClothingOuterSuitCargo = грузовой костюм
|
||||
.desc = Специальный костюм для работников карго, немного напоминающий старый костюм из другой вселенной.
|
||||
.desc = Специальный костюм для работников карго, немного напоминающий старый костюм из другой вселенной.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ent-ClothingShoesBootsMagCombat = боевые магнитные ботинки
|
||||
.desc = Боевые магнитные ботинки, часто используемые во время выхода в открытый космос, чтобы гарантировать, что пользователь остается безопасно прикрепленным к станции.
|
||||
ent-ClothingShoesBootsMagPirate = магнитные ботинки пирата
|
||||
.desc = Боевые магнитки перекрашенные в пирацкие цвета
|
||||
.desc = Боевые магнитки перекрашенные в пирацкие цвета
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ent-PortalGate = { ent-BasePortal }
|
||||
.desc = { ent-BasePortal.desc }
|
||||
.desc = { ent-BasePortal.desc }
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ ent-ClothingOuterHardsuitSyndieCommanderBiocode = { ent-ClothingOuterHardsuitSyn
|
|||
.desc = { ent-ClothingOuterHardsuitSyndieCommander.desc }
|
||||
ent-ClothingOuterHardsuitJuggernautBiocode = { ent-ClothingOuterHardsuitJuggernaut }
|
||||
.suffix = БИОКОД
|
||||
.desc = { ent-ClothingOuterHardsuitJuggernaut.desc }
|
||||
.desc = { ent-ClothingOuterHardsuitJuggernaut.desc }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ent-PrinterDocMachineCircuitboard = принтер документов (машинная плата)
|
||||
.desc = Машинная плата принтера документов.
|
||||
ent-PacificatorCircuitboard = генератор пацифизма (машинная плата)
|
||||
.desc = { ent-BaseMachineCircuitboard.desc }
|
||||
.desc = { ent-BaseMachineCircuitboard.desc }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ent-ExplosiveCollarBase = ошейник
|
||||
ent-ExplosiveCollarBase = ошейник
|
||||
.desc = Высокотехнологичный ошейник, основанный на инвертере квантового спина, использующийся синдикатом для взятия в заложники важных лиц.
|
||||
ent-ExplosiveCollarRed = красный { ent-ExplosiveCollarBase }
|
||||
.desc = { ent-ExplosiveCollarBase.desc }
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ent-bouquet = Букет
|
||||
ent-bouquet = Букет
|
||||
.desc = Букет душистых цветов.
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ent-BluespaceBox = блюспейс коробка
|
||||
.desc = коробка, использующая блюспейс технологии для умещения любого предмета и увеличения вместимости.
|
||||
.desc = коробка, использующая блюспейс технологии для умещения любого предмета и увеличения вместимости.
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ ent-EnergyDomeGeneratorBackpackSyndie = кроваво-красный наспи
|
|||
ent-EnergyDomeGeneratorBackpackNT = BR-50c "Бастион"
|
||||
.desc = Наспинный генератор щита, защищающий владельца от лазеров и пуль, но не позволяющий самому использовать оружие дальнего боя. Использует батареи.
|
||||
ent-EnergyDomeGeneratorPersonalNT = BT-21b "Барьер"
|
||||
.desc = Генератор щита, защищающий владельца от лазеров и пуль, но не позволяющий самому использовать оружие дальнего боя. Использует батареи.
|
||||
.desc = Генератор щита, защищающий владельца от лазеров и пуль, но не позволяющий самому использовать оружие дальнего боя. Использует батареи.
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ent-Pacificator = генератор пацифизма
|
||||
.desc = Делает всех разумных существ в радиусе действия пацифистами.
|
||||
.desc = Делает всех разумных существ в радиусе действия пацифистами.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
ent-Reflector = рефлектор
|
||||
.desc = { "" }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
ent-SurgeryStepExposeVocalCords = Раскрыть голосовые связки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepAdjustVocalCords = Отрегулировать голосовые связки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSutureIncision = Наложить швы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ent-SurgeryAmputationStep = Пропилить конечность
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
ent-SurgeryStepExposeNerves = Раскрыть нервы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepExposeBloodVessels = Открыть кровеносные сосуды
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryLimbAttachmentStep = Прикрепить конечность
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRejoinNerves = Восстановить нервы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRejoinBloodVessels = Восстановить кровеносные сосуды
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRestoreCartilage = Восстановить хрящи
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
ent-SurgeryStepLocateLiver = Найти печень
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampLiverVessels = Зажать сосуды печени
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveLiver = Убрать печень
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepLocateAppendix = Найти аппендикс
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampAppendix = Зажать аппендикс
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveAppendix = Убрать аппендикс
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepLocateKidneys = Найти почки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampKidneysVessels = Зажать сосуды почек
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveKidneys = Убрать почки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepLocateStomach = Найти желудок
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampStomachVessels = Зажать сосуды желудка
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveStomach = Убрать желудок
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepLocateLungs = Найти лёгкие
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampLungVessels = Зажать сосуды лёгких
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveLungs = Убрать лёгкие
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepLocateHeart = Найти сердце
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampHeartVessels = Зажать сосуды сердца
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveHeart = Убрать сердце
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepLocateEyes = Найти глаза
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampOpticNerve = Зажать оптический нерв
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveEyes = Убрать глаза
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepGrabTongue = Взять язык
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepCutTongue = Отрезать язык
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPreparePatient = Подготовить пациента
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepShaveHead = Побрить голову
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepDisinfectScalp = Дезинфицировать кожу головы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepMakeIncisionScalp = Сделать надрез на коже головы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRetractScalp = Втянуть кожу лоскута головы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepDrillBurrHoles = Просверлить отверстие
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepCutSkull = Распилить череп
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRemoveBoneFlap = Удалить костный лоскут
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampDuraMater = Зажать мозговую оболочку
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInciseDuraMater = Надрезать мозговую оболочку
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRetractDuraMater = Втянуть мозговую оболочку
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSeverCranialNerves = Перерезать черепные нервы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepExtractBrain = Достать мозг
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteLiver = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertLiver = Вставить печень
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepConnectLiverVessels = Соединить сосуды печени
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteKidneys = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertKidneys = Вставить почки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepConnectKidneysVessels = Соединить сосуды почек
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteStomach = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertStomach = Вставить желудок
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepConnectStomachVessels = Соединить сосуды желудка
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteLungs = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertLungs = Вставить лёгкие
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepConnectLungVessels = Соединить сосуды лёгких
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteHeart = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertHeart = Вставить сердце
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepConnectHeartVessels = Соединить сосуды сердца
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteEyes = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertEyes = Вставить глаз
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepConnectOpticNerve = Соединить оптический нерв
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareImplantSiteTongue = Подготовить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPositionTongue = Положить язык
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepAttachTongue = Прикрепить язык
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepCleanImplantSite = Очистить место для имплантата
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareScalp = Подготовить кожу головы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPrepareDuraMater = Подготовить мозговую оболочку
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepInsertBrain = Вставить мозг
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepReconnectCranialNerves = Соединить черепные нервы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepReplaceDuraMater = Заменить мозговую оболочку
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSealDuraMater = Уплотнить мозговую оболочку
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepReplaceBoneFlap = Заменить костный лоскут
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSecureBoneFlap = Закрепить костный лоскут
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepReplaceScalp = Заменить кожу головы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSutureScalp = Наложить швы на кожу головы
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
ent-SurgeryOpenIncision = Открыть порез
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryCloseIncision = Закрыть порез
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryOpenRibcage = Открыть грудную клетку
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryOpenAbdomen = Открыть брюшную полость
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryEliminateVocalCordDefects = Устранить дефекты голосовых связок
|
||||
.desc = Устранить дефекты речи, акценты.
|
||||
ent-SurgeryAmputation = Ампутация
|
||||
.desc = Хирургическое удаление конечности.
|
||||
ent-SurgeryLimbAttachment = Прикрепить конечность
|
||||
.desc = Хирургическое прикрепление конечности.
|
||||
ent-SurgeryExtractLiver = Достать печень
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantLiver = Имплантировать печень
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractAppendix = Достать аппендикс
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractKidneys = Достать почки
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantKidneys = Имплантировать почки
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractStomach = Достать желудок
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantStomach = Имплантировать желудок
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractLungs = Достать лёгкие
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantLungs = Имплантировать лёгкие
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractHeart = Достать сердце
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantHeart = Имплантировать сердце
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractEyes = Достать глаза
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantEyes = Имплантировать глаза
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractTongue = Достать язык
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantTongue = Имплантировать язык
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryExtractBrain = Достать мозг
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
ent-SurgeryImplantBrain = Имплантировать мозг
|
||||
.desc = { ent-SurgeryBase.desc }
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
ent-SurgeryStepOpenIncisionScalpel = Разрезать скальпелем
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepClampBleeders = Зажать источник кровотечения
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRetractSkin = Втяните кожу
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSawBones = Распилить кости
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepPriseOpenBones = Вскрыть кости
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepCloseBones = Закрыть кости
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepMendRibcage = Укрепить кости
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepCloseIncision = Закрыть разрез
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepCutAbdominalMuscles = Разрезать мышцы живота
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRetractAbdominalWalls = Втягнуть брюшевые стенки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepSutureMuscles = Сшыть мышцы живота
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
ent-SurgeryStepRestoreAbdominalWalls = Восстановить брюшные стенки
|
||||
.desc = { ent-SurgeryStepBase.desc }
|
||||
|
|
@ -15,4 +15,4 @@ ent-ActionVampireBatform = Форма летучей мыши
|
|||
ent-ActionVampireMouseform = Мышиная форма
|
||||
.desc = Примите облик мыши. Быстрая, маленькая, невосприимчива к дверям. Стоимость активации: 20 крови. Время перезарядки: 30 секунд
|
||||
ent-ActionVampireCloakOfDarkness = Плащ тьмы
|
||||
.desc = Замаскируйте себя от глаз смертных, делая вас невидимым в неподвижном состоянии. Стоимость активации: 30 крови. Трата: 1 кровь/секунда Время перезарядки: 10 секунд
|
||||
.desc = Замаскируйте себя от глаз смертных, делая вас невидимым в неподвижном состоянии. Стоимость активации: 30 крови. Трата: 1 кровь/секунда Время перезарядки: 10 секунд
|
||||
|
|
|
|||
20
Resources/Locale/ru-RU/_prototypes/body/organs/base.ftl
Normal file
20
Resources/Locale/ru-RU/_prototypes/body/organs/base.ftl
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ent-BaseOrganBrain = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganEyes = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganTongue = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganAppendix = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganEars = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganLungs = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganHeart = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganStomach = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganLiver = { "" }
|
||||
.desc = { "" }
|
||||
ent-BaseOrganKidneys = { "" }
|
||||
.desc = { "" }
|
||||
|
|
@ -13,4 +13,4 @@ ent-MechAirTank = воздушный баллон экзокостюма
|
|||
ent-MechThruster = ускоритель экзокостюма
|
||||
.desc = Ускоритель, который позволяет экзокостюму безопасно двигаться при отсутствии гравитации.
|
||||
ent-MechPhasicScanningModule = фазовый сканирующий модуль
|
||||
.desc = Высокотехнологичный сканирующий модуль, позволяющий прорывать пространство и проходить сквозь твердые объекты.
|
||||
.desc = Высокотехнологичный сканирующий модуль, позволяющий прорывать пространство и проходить сквозь твердые объекты.
|
||||
|
|
|
|||
|
|
@ -14,8 +14,16 @@ ent-ScalpelLaser = лазерный скальпель
|
|||
.desc = Скальпель, в котором вместо лезвия используется направленный лазер для разрезания, что обеспечивает более точную работу, а также прижигание при разрезе.
|
||||
ent-Retractor = ретрактор
|
||||
.desc = Хирургический инструмент, используемый для фиксации открытых разрезов.
|
||||
ent-RetractorAdvanced = улучшенный ретрактор
|
||||
.desc = { ent-Retractor.desc }
|
||||
ent-Hemostat = гемостат
|
||||
.desc = Хирургический инструмент, используемый для сжатия кровеносных сосудов с целью предотвращения кровотечения.
|
||||
ent-HemostatAdvanced = улучшенный гемостат
|
||||
.desc = { ent-Hemostat.desc }
|
||||
ent-BoneSetter = костоправ
|
||||
.desc = Хирургический инструмент, используемый для вправления костей.
|
||||
ent-BoneSetterAdvanced = улучшенный костоправ
|
||||
.desc = Хирургический инструмент, используемый для вправления костей. Кроме этого, он отлично разбивает их.
|
||||
ent-Saw = пила по металлу
|
||||
.desc = Для распиливания дерева и других предметов на куски. Или для распиливания костей, в случае крайней необходимости.
|
||||
ent-SawImprov = чоппа
|
||||
|
|
@ -24,3 +32,5 @@ ent-SawElectric = дисковая пила
|
|||
.desc = Для интенсивной резки.
|
||||
ent-SawAdvanced = улучшенная циркулярная пила
|
||||
.desc = Вы уверены, что с её помощью сможете разрезать всё, что угодно.
|
||||
ent-BoneGel = бутылочка костного геля
|
||||
.desc = Контейнер для костного геля, который часто необходимо пополнять из специализированного аппарата.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ent-VampireSurviveObjective = Выжить
|
||||
.desc = Я должен выжить, чего бы этого мне не стоило.
|
||||
ent-VampireEscapeObjective = Улететь со станции живым и свободным.
|
||||
.desc = Я должен улететь на эвакуационном шаттле. Свободным.
|
||||
.desc = Я должен улететь на эвакуационном шаттле. Свободным.
|
||||
|
|
|
|||
|
|
@ -1,56 +1,41 @@
|
|||
vampires-title = Вампиры
|
||||
|
||||
vampire-fangs-extended-examine = Вы видите блеск [color=white]острых клыков[/color].
|
||||
vampire-fangs-extended = Вы вытягиваете свои клыки
|
||||
vampire-fangs-retracted = Вы втягиваете свои клыки
|
||||
|
||||
vampire-blooddrink-empty = Это тело лишено крови
|
||||
vampire-blooddrink-rotted = Их тела гниют, а кровь запятнана
|
||||
vampire-blooddrink-zombie = Их кровь запятнана смертью
|
||||
|
||||
vampire-startlight-burning = Вы чувствуете, как ваша кожа горит в свете тысячи солнц
|
||||
|
||||
vampire-not-enough-blood = У вас недостаточно крови
|
||||
vampire-cuffed = Вам нужны свободные руки!
|
||||
vampire-stunned = Вы не сможете сосредоточиться!
|
||||
vampire-muffled = Ваш рот закрыт намордником
|
||||
vampire-full-stomach = Вас раздуло от крови
|
||||
|
||||
vampire-deathsembrace-bind = Чувствуешь себя как дома
|
||||
|
||||
vampire-ingest-holyblood = Ваш рот горит!
|
||||
|
||||
vampire-cloak-enable = Вы окутываете тенью свою форму
|
||||
vampire-cloak-disable = Вы ослабляете хватку теней.
|
||||
|
||||
vampire-bloodsteal-other = Вы чувствуете, как кровь вырывается из вашего тела!
|
||||
vampire-hypnotise-other = {CAPITALIZE(THE($user))} пристально вглядывается в {THE($target)} глаза!
|
||||
vampire-unnaturalstrength = Верхние мышцы {CAPITALIZE(THE($user))} увеличиваються делая его сильнее!
|
||||
vampire-supernaturalstrength = Верхние мышцы {CAPITALIZE(THE($user))} набухают от мощи делая его сверхсильным!
|
||||
|
||||
vampire-hypnotise-other = { CAPITALIZE(THE($user)) } пристально вглядывается в { THE($target) } глаза!
|
||||
vampire-unnaturalstrength = Верхние мышцы { CAPITALIZE(THE($user)) } увеличиваються делая его сильнее!
|
||||
vampire-supernaturalstrength = Верхние мышцы { CAPITALIZE(THE($user)) } набухают от мощи делая его сверхсильным!
|
||||
store-currency-display-blood-essence = Кровавая эссенция
|
||||
store-category-vampirepowers = Силы
|
||||
store-category-vampirepassives = Пассивные
|
||||
|
||||
#Powers
|
||||
|
||||
#Passives
|
||||
vampire-passive-unholystrength = Нечестивая сила
|
||||
vampire-passive-unholystrength-description = Наполните мышцы верхней части тела кровью, наделяя вас когтями и повышенной силой. Эффект: 10 порезов за удар
|
||||
|
||||
vampire-passive-supernaturalstrength = Сверхъестественная сила
|
||||
vampire-passive-supernaturalstrength-description = Увеличьте силу мышц верхней части тела, и ни одна преграда не встанет на вашем пути. Эффект: 15 порезов за удар, возможность открывать двери руками.
|
||||
|
||||
vampire-passive-deathsembrace = Объятия смерти
|
||||
vampire-passive-deathsembrace-description = Примите смерть, и она обойдет вас стороной. Эффект: исцеление в гробу, автоматическое возвращение в гроб после смерти за 100 эссенции крови.
|
||||
|
||||
#Mutation menu
|
||||
|
||||
vampire-mutation-menu-ui-window-name = Меню мутаций
|
||||
|
||||
vampire-mutation-none-info = Ничего не выбрано
|
||||
|
||||
vampire-mutation-hemomancer-info =
|
||||
vampire-mutation-hemomancer-info =
|
||||
Гемомансер
|
||||
|
||||
Фокусируеться на кровавой магии и манипуляции крови вокруг себя.
|
||||
|
|
@ -59,8 +44,7 @@ vampire-mutation-hemomancer-info =
|
|||
|
||||
- Визг
|
||||
- Кража крови
|
||||
|
||||
vampire-mutation-umbrae-info =
|
||||
vampire-mutation-umbrae-info =
|
||||
Тень
|
||||
|
||||
Фокусируется на темноте, стелсе, мобильности.
|
||||
|
|
@ -69,8 +53,7 @@ vampire-mutation-umbrae-info =
|
|||
|
||||
- Блик
|
||||
- Плащ тьмы
|
||||
|
||||
vampire-mutation-gargantua-info =
|
||||
vampire-mutation-gargantua-info =
|
||||
Гаргантюа
|
||||
|
||||
Фокусируется на ближнем уроне и стойкости.
|
||||
|
|
@ -79,8 +62,7 @@ vampire-mutation-gargantua-info =
|
|||
|
||||
- Нечестивая сила
|
||||
- Сверхъестественная сила
|
||||
|
||||
vampire-mutation-bestia-info =
|
||||
vampire-mutation-bestia-info =
|
||||
Бестия
|
||||
|
||||
Фокусируется на превращении и собирании трофеев
|
||||
|
|
@ -89,8 +71,8 @@ vampire-mutation-bestia-info =
|
|||
|
||||
- Форма летучей мыши
|
||||
- Мышиная форма
|
||||
|
||||
|
||||
## Objectives
|
||||
|
||||
objective-condition-drain-title = Выпить { $count } крови.
|
||||
objective-condition-drain-description = Я должен выпить { $count } крови. Это необходимо для моего выживания и дальнейшей эволюции.
|
||||
objective-condition-drain-description = Я должен выпить { $count } крови. Это необходимо для моего выживания и дальнейшей эволюции.
|
||||
|
|
|
|||
2
Resources/Locale/ru-RU/_strings/_sunrise/categories.ftl
Normal file
2
Resources/Locale/ru-RU/_strings/_sunrise/categories.ftl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
categories-surgeries = операции
|
||||
categories-surgery-steps = этапы операции
|
||||
|
|
@ -57,4 +57,4 @@ step-uranium-glass-shard-name = осколок уранового стекла
|
|||
step-exosuit-air-tank-name = воздушный баллон экзокостюма
|
||||
step-exosuit-thruster-name = ускоритель экзокостюма
|
||||
step-ripley-peripherals-control-module-name = модуль управления периферией Рипли
|
||||
step-ripley-central-control-module-name = центральный модуль управления Рипли
|
||||
step-ripley-central-control-module-name = центральный модуль управления Рипли
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
expcollar-examine-armed = Боевой режим [color=red]включен[/color]
|
||||
expcollar-examine-armed = Боевой режим [color=red]включен[/color]
|
||||
expcoller-examine-disarmed = Боевой режим [color=green]выключен[/color]
|
||||
expcollar-examine-virgin = [color=yellow]Ошейник ни разу не использовался до этого[/color]
|
||||
expcollar-examine-unvirgin = [color=red]Ошейник уже был использован до этого и потерял свою эффективность[/color]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
uplink-exp-collars-kit-name = Набор с взрывными ошейниками
|
||||
uplink-exp-collars-kit-name = Набор с взрывными ошейниками
|
||||
uplink-exp-collars-kit-desc = Набор включает два взрывных ошейника и свадебный букет. Если соединить ошейники, то при смерти носителя красного ошейника они оба взорвутся.
|
||||
uplink-bouquet-name = Букет
|
||||
uplink-bouquet-desc = Красивый набор цветов, соответствующий последним стандартам Синдиката.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
expcollar-connect = Ошейники связываются!
|
||||
expcollar-connect = Ошейники связываются!
|
||||
expcollar-connected = Ошейник уже привязан.
|
||||
expcollar-bolts-down = Болты ошейника отключаются!
|
||||
expcollar-kill = Зафиксирована смерть хоста!
|
||||
|
|
|
|||
|
|
@ -43,4 +43,4 @@ ent-MagazineFamasExtended = расширенный магазин Famas
|
|||
ent-MagazineV31Extended = расширенный магазин V31
|
||||
ent-MagazineBauer127Extended = расширенный магазин Bauer127
|
||||
ent-MagazineBR64Extended = расширенный магазин BR64
|
||||
ent-MagazineDragunovExtended = расширенный магазин Dragunov
|
||||
ent-MagazineDragunovExtended = расширенный магазин Dragunov
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
lathe-category-surgery = Хирургия
|
||||
lathe-category-cyberlimbs = Конечности
|
||||
lathe-category-reports = Отчёты
|
||||
lathe-category-statements = Заключения
|
||||
lathe-category-inquiries-and-appeals = Запросы и обращения
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ background-type-Parallax = Паралакс
|
|||
background-type-Animation = Анимация
|
||||
background-type-Art = Арт
|
||||
background-type-Random = Случайный
|
||||
|
||||
# Animations
|
||||
lobby-animation-Random = Случайный
|
||||
lobby-animation-AyakaAduare = Ayaka by Aduare
|
||||
|
|
@ -50,7 +49,6 @@ lobby-animation-SpaceStarts = Space Starts
|
|||
lobby-animation-SunnyCity = Sunny City
|
||||
lobby-animation-SunnyPtl = Sunny PTL
|
||||
lobby-animation-SunnyPtl2 = Sunny PTL 2
|
||||
|
||||
# Arts
|
||||
lobby-art-Random = Случайный
|
||||
lobby-art-BarLife = Bar Life
|
||||
|
|
@ -106,7 +104,6 @@ lobby-art-Behonker = Behonker
|
|||
lobby-art-TerminalStation = Terminal Station
|
||||
lobby-art-JustAWeekAway = Just A Week Away
|
||||
lobby-art-JaniShootout = Jani Shootout
|
||||
|
||||
# Parallaxes
|
||||
lobby-parallax-Random = Случайный
|
||||
lobby-parallax-FastSpace = Fast Space
|
||||
|
|
|
|||
|
|
@ -1,38 +1,26 @@
|
|||
alerts-mood-insane-name = Воодушевлён
|
||||
alerts-mood-insane-name = Воодушевлён
|
||||
alerts-mood-insane-desc = Я полон энергии и энтузиазма, готов горы свернуть!
|
||||
|
||||
alerts-mood-horrible-name = Подавлен
|
||||
alerts-mood-horrible-desc = Сейчас тяжело, но это временно. Нужно собраться с силами.
|
||||
|
||||
alerts-mood-terrible-name = Расстроен
|
||||
alerts-mood-terrible-desc = День выдался не из лёгких. Стоит немного отдохнуть.
|
||||
|
||||
alerts-mood-bad-name = Не в духе
|
||||
alerts-mood-bad-desc = Что-то сегодня всё не так. Может, стоит сменить обстановку?
|
||||
|
||||
alerts-mood-meh-name = Так себе
|
||||
alerts-mood-meh-desc = Ничего особенного. Обычный будний день.
|
||||
|
||||
alerts-mood-neutral-name = Спокоен
|
||||
alerts-mood-neutral-desc = Всё идёт своим чередом, без особых взлётов и падений.
|
||||
|
||||
alerts-mood-good-name = Доволен
|
||||
alerts-mood-good-desc = Дела идут неплохо, и это радует!
|
||||
|
||||
alerts-mood-great-name = Воодушевлён
|
||||
alerts-mood-great-desc = Сегодня определённо удачный день! Хочется сделать что-то хорошее.
|
||||
|
||||
alerts-mood-exceptional-name = Вдохновлён
|
||||
alerts-mood-exceptional-desc = Чувствую прилив сил и энергии. Горы готов свернуть!
|
||||
|
||||
alerts-mood-perfect-name = На высоте
|
||||
alerts-mood-perfect-desc = Потрясающее настроение! Кажется, сегодня всё по плечу!
|
||||
|
||||
alerts-mood-dead-name = Мертв
|
||||
alerts-mood-dead-desc = ...
|
||||
|
||||
mood-show-effects-start = [font size=12]Текущее настроение:[/font]
|
||||
|
||||
mood-effect-HungerOverfed = Я так наелся, что вот-вот лопну!
|
||||
mood-effect-HungerOkay = Чувствую себя сытым.
|
||||
mood-effect-HungerPeckish = Не отказался бы от перекуса.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
research-discipline-biochemical = Биохимия
|
||||
research-technology-basic-surgery = Базовая хирургия
|
||||
research-technology-basic-cyberlimbs = Базовые кибер-конечности
|
||||
research-technology-advanced-surgery = Продвинутая хирургия
|
||||
research-technology-handcraft-nvd = Кустарные ПНВ
|
||||
research-technology-basic-nvd = Продвинутое ПНВ
|
||||
research-technology-combat-equipment = Боевое снаряжение
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
surgery-window-name = Хирургия
|
||||
surgery-window-partsbutton-name = Части тела
|
||||
surgery-window-surgeriesbutton-name = Операции
|
||||
surgery-window-stepsbutton-name = Этапы
|
||||
surgery-window-reguires = [bold]Требует: { $surgeryname }[/bold]
|
||||
surgery-window-reguires-table = [color=red](Требует операционный стол)[/color]
|
||||
surgery-window-reguires-undress = [color=red](Снимите с него броню!)[/color]
|
||||
surgery-window-reguires-tool = [color=red](Отсутствует инструмент)[/color]
|
||||
surgery-window-reguires-laydown = [color=red][font size=16]Он должен лежать![/font][/color]
|
||||
|
|
@ -16,4 +16,4 @@ admin-verb-text-make-pirate = Сделать пиратом
|
|||
admin-verb-text-make-head-rev = Сделать Главой революции
|
||||
admin-verb-text-make-thief = Сделать вором
|
||||
admin-verb-text-make-changeling = Сделать генокрадом
|
||||
admin-verb-text-make-vampire = Сделать вампиром
|
||||
admin-verb-text-make-vampire = Сделать вампиром
|
||||
|
|
|
|||
|
|
@ -22,4 +22,4 @@ hypospray-cant-inject = Нельзя сделать инъекцию в { $targe
|
|||
|
||||
## failure
|
||||
|
||||
hypospay-component-failure-hardsuit = Вы не сможете провести иглу через толстое покрытие!
|
||||
hypospay-component-failure-hardsuit = Вы не сможете провести иглу через толстое покрытие!
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ injector-component-injecting-target = { CAPITALIZE($user) } начинает в
|
|||
|
||||
## failure
|
||||
|
||||
injector-component-failure-hardsuit = Вы не сможете провести иглу через толстое покрытие!
|
||||
injector-component-failure-hardsuit = Вы не сможете провести иглу через толстое покрытие!
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ vampire-role-greeting =
|
|||
Ваши задачи указаны в меню персонажа.
|
||||
Пейте кровь и эволюционируйте, чтобы выполнить их!
|
||||
vampire-role-greeting-short = Вы вампир, который пробрался на станцию под видом работника!
|
||||
roles-antag-vamire-name = Вампир
|
||||
roles-antag-vamire-name = Вампир
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ metabolizer-type-plant = Растение
|
|||
metabolizer-type-dwarf = Дварф
|
||||
metabolizer-type-moth = Ниан
|
||||
metabolizer-type-arachnid = Арахнид
|
||||
metabolizer-type-vampire = Вампир
|
||||
metabolizer-type-vampire = Вампир
|
||||
|
|
|
|||
|
|
@ -96,4 +96,4 @@ reagent-physical-desc-slimy = склизкое
|
|||
reagent-physical-desc-neural = нейронное
|
||||
reagent-physical-desc-vile = мерзкое
|
||||
reagent-physical-desc-celliminol = сильно отдающее кровью
|
||||
reagent-physical-desc-h-32 = сильно пахнущее радиоактивными ожогами
|
||||
reagent-physical-desc-h-32 = сильно пахнущее радиоактивными ожогами
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidStomach
|
||||
parent: OrganAnimalStomach
|
||||
parent: [OrganAnimalStomach, BaseOrganStomach]
|
||||
name: stomach
|
||||
description: "Gross. This is hard to stomach."
|
||||
components:
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidLungs
|
||||
parent: BaseArachnidOrgan
|
||||
parent: [BaseArachnidOrgan, BaseOrganLungs]
|
||||
name: lungs
|
||||
description: "Filters oxygen from an atmosphere... just more greedily."
|
||||
components:
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidHeart
|
||||
parent: BaseArachnidOrgan
|
||||
parent: [BaseArachnidOrgan, BaseOrganHeart]
|
||||
name: heart
|
||||
description: "A disgustingly persistent little biological pump made for spiders."
|
||||
components:
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidLiver
|
||||
parent: BaseHumanOrgan
|
||||
parent: [BaseHumanOrgan, BaseOrganLiver]
|
||||
name: liver
|
||||
description: "Pairing suggestion: chianti and fava beans."
|
||||
categories: [ HideSpawnMenu ]
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidKidneys
|
||||
parent: BaseHumanOrgan
|
||||
parent: [BaseHumanOrgan, BaseOrganKidneys]
|
||||
name: kidneys
|
||||
description: "Filters toxins from the bloodstream."
|
||||
categories: [ HideSpawnMenu ]
|
||||
|
|
@ -149,7 +149,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidEyes
|
||||
parent: BaseArachnidOrgan
|
||||
parent: [BaseArachnidOrgan, BaseOrganEyes]
|
||||
name: eyes
|
||||
description: "Two was already too many."
|
||||
components:
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
|
||||
- type: entity
|
||||
id: OrganArachnidTongue
|
||||
parent: BaseArachnidOrgan
|
||||
parent: [BaseArachnidOrgan, BaseOrganTongue]
|
||||
name: tongue
|
||||
description: "A fleshy muscle mostly used for lying."
|
||||
components:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue