Merge branch 'master' into mathmerge

This commit is contained in:
Pieter-Jan Briers
2020-08-20 20:33:43 +02:00
808 changed files with 18173 additions and 5666 deletions

View File

@@ -1,35 +0,0 @@
using Content.Client.Atmos;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Client.GameObjects.Components.Atmos
{
[RegisterComponent]
public class CanSeeGasesComponent : Component
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
public override string Name => "CanSeeGases";
public override void HandleMessage(ComponentMessage message, IComponent component)
{
base.HandleMessage(message, component);
switch (message)
{
case PlayerAttachedMsg _:
if(!_overlayManager.HasOverlay(nameof(GasTileOverlay)))
_overlayManager.AddOverlay(new GasTileOverlay());
break;
case PlayerDetachedMsg _:
if(!_overlayManager.HasOverlay(nameof(GasTileOverlay)))
_overlayManager.RemoveOverlay(nameof(GasTileOverlay));
break;
}
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
using Content.Shared.GameObjects.Components;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class ExtinguisherVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Deleted)
{
return;
}
if (component.TryGetData<double>(ExtinguisherVisuals.Rotation, out var degrees))
{
SetRotation(component, Angle.FromDegrees(degrees));
}
}
private void SetRotation(AppearanceComponent component, Angle rotation)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!sprite.Owner.TryGetComponent(out AnimationPlayerComponent animation))
{
sprite.Rotation = rotation;
return;
}
if (animation.HasRunningAnimation("rotate"))
{
animation.Stop("rotate");
}
animation.Play(new Animation
{
Length = TimeSpan.FromSeconds(0.125),
AnimationTracks =
{
new AnimationTrackComponentProperty
{
ComponentType = typeof(ISpriteComponent),
Property = nameof(ISpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(sprite.Rotation, 0),
new AnimationTrackProperty.KeyFrame(rotation, 0.125f)
}
}
}
}, "rotate");
}
}
}

View File

@@ -0,0 +1,71 @@
#nullable enable
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Players;
namespace Content.Client.GameObjects.Components.Body
{
[RegisterComponent]
[ComponentReference(typeof(IDamageableComponent))]
[ComponentReference(typeof(IBodyManagerComponent))]
public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning restore 649
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
}
public bool ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
if (!Owner.TryGetComponent(out ISpriteComponent? sprite))
{
return;
}
switch (message)
{
case BodyPartAddedMessage partAdded:
sprite.LayerSetVisible(partAdded.RSIMap, true);
sprite.LayerSetRSI(partAdded.RSIMap, partAdded.RSIPath);
sprite.LayerSetState(partAdded.RSIMap, partAdded.RSIState);
break;
case BodyPartRemovedMessage partRemoved:
sprite.LayerSetVisible(partRemoved.RSIMap, false);
if (!partRemoved.Dropped.HasValue ||
!_entityManager.TryGetEntity(partRemoved.Dropped.Value, out var entity) ||
!entity.TryGetComponent(out ISpriteComponent? droppedSprite))
{
break;
}
var color = sprite[partRemoved.RSIMap].Color;
droppedSprite.LayerSetColor(0, color);
break;
case MechanismSpriteAddedMessage mechanismAdded:
sprite.LayerSetVisible(mechanismAdded.RSIMap, true);
break;
case MechanismSpriteRemovedMessage mechanismRemoved:
sprite.LayerSetVisible(mechanismRemoved.RSIMap, false);
break;
}
}
}
}

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using Content.Shared.Body.Scanner;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Body.Scanner
{
[UsedImplicitly]
public class BodyScannerBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private BodyScannerDisplay _display;
[ViewVariables]
private BodyScannerTemplateData _template;
[ViewVariables]
private Dictionary<string, BodyScannerBodyPartData> _parts;
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
protected override void Open()
{
base.Open();
_display = new BodyScannerDisplay(this);
_display.OnClose += Close;
_display.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (!(state is BodyScannerInterfaceState scannerState))
{
return;
}
_template = scannerState.Template;
_parts = scannerState.Parts;
_display.UpdateDisplay(_template, _parts);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_display.Dispose();
_template = null;
_parts.Clear();
}
}
}
}

View File

@@ -0,0 +1,164 @@
using System.Collections.Generic;
using System.Globalization;
using Content.Shared.Body.Scanner;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.ItemList;
namespace Content.Client.GameObjects.Components.Body.Scanner
{
public sealed class BodyScannerDisplay : SS14Window
{
private BodyScannerTemplateData _template;
private Dictionary<string, BodyScannerBodyPartData> _parts;
private List<string> _slots;
private BodyScannerBodyPartData _currentBodyPart;
public BodyScannerDisplay(BodyScannerBoundUserInterface owner)
{
IoCManager.InjectDependencies(this);
Owner = owner;
Title = Loc.GetString("Body Scanner");
var hSplit = new HBoxContainer
{
Children =
{
// Left half
new ScrollContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children =
{
(BodyPartList = new ItemList())
}
},
// Right half
new VBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children =
{
// Top half of the right half
new VBoxContainer
{
SizeFlagsVertical = SizeFlags.FillExpand,
Children =
{
(BodyPartLabel = new Label()),
new HBoxContainer
{
Children =
{
new Label
{
Text = "Health: "
},
(BodyPartHealth = new Label())
}
},
new ScrollContainer
{
SizeFlagsVertical = SizeFlags.FillExpand,
Children =
{
(MechanismList = new ItemList())
}
}
}
},
// Bottom half of the right half
(MechanismInfoLabel = new RichTextLabel
{
SizeFlagsVertical = SizeFlags.FillExpand
})
}
}
}
};
Contents.AddChild(hSplit);
BodyPartList.OnItemSelected += BodyPartOnItemSelected;
MechanismList.OnItemSelected += MechanismOnItemSelected;
}
public BodyScannerBoundUserInterface Owner { get; }
protected override Vector2? CustomSize => (800, 600);
private ItemList BodyPartList { get; }
private Label BodyPartLabel { get; }
private Label BodyPartHealth { get; }
private ItemList MechanismList { get; }
private RichTextLabel MechanismInfoLabel { get; }
public void UpdateDisplay(BodyScannerTemplateData template, Dictionary<string, BodyScannerBodyPartData> parts)
{
_template = template;
_parts = parts;
_slots = new List<string>();
BodyPartList.Clear();
foreach (var slotName in _parts.Keys)
{
// We have to do this since ItemLists only return the index of what item is
// selected and dictionaries don't allow you to explicitly grab things by index.
// So we put the contents of the dictionary into a list so
// that we can grab the list by index. I don't know either.
_slots.Add(slotName);
BodyPartList.AddItem(Loc.GetString(slotName));
}
}
public void BodyPartOnItemSelected(ItemListSelectedEventArgs args)
{
if (_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) {
UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]);
}
}
private void UpdateBodyPartBox(BodyScannerBodyPartData part, string slotName)
{
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Name)}";
BodyPartHealth.Text = $"{part.CurrentDurability}/{part.MaxDurability}";
MechanismList.Clear();
foreach (var mechanism in part.Mechanisms) {
MechanismList.AddItem(mechanism.Name);
}
}
public void MechanismOnItemSelected(ItemListSelectedEventArgs args)
{
UpdateMechanismBox(_currentBodyPart.Mechanisms[args.ItemIndex]);
}
private void UpdateMechanismBox(BodyScannerMechanismData mechanism)
{
// TODO: Improve UI
if (mechanism == null)
{
MechanismInfoLabel.SetMessage("");
return;
}
var message =
Loc.GetString(
$"{mechanism.Name}\nHealth: {mechanism.CurrentDurability}/{mechanism.MaxDurability}\n{mechanism.Description}");
MechanismInfoLabel.SetMessage(message);
}
}
}

View File

@@ -0,0 +1,86 @@
#nullable enable
using Content.Shared.Body.Surgery;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
namespace Content.Client.GameObjects.Components.Body.Surgery
{
// TODO : Make window close if target or surgery tool gets too far away from user.
/// <summary>
/// Generic client-side UI list popup that allows users to choose from an option
/// of limbs or organs to operate on.
/// </summary>
[UsedImplicitly]
public class GenericSurgeryBoundUserInterface : BoundUserInterface
{
private GenericSurgeryWindow? _window;
public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
protected override void Open()
{
_window = new GenericSurgeryWindow();
_window.OpenCentered();
_window.OnClose += Close;
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
switch (message)
{
case RequestBodyPartSurgeryUIMessage msg:
HandleBodyPartRequest(msg);
break;
case RequestMechanismSurgeryUIMessage msg:
HandleMechanismRequest(msg);
break;
case RequestBodyPartSlotSurgeryUIMessage msg:
HandleBodyPartSlotRequest(msg);
break;
}
}
private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg)
{
_window?.BuildDisplay(msg.Targets, BodyPartSelectedCallback);
}
private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg)
{
_window?.BuildDisplay(msg.Targets, MechanismSelectedCallback);
}
private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg)
{
_window?.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback);
}
private void BodyPartSelectedCallback(int selectedOptionData)
{
SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData));
}
private void MechanismSelectedCallback(int selectedOptionData)
{
SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData));
}
private void BodyPartSlotSelectedCallback(int selectedOptionData)
{
SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Body.Surgery
{
public class GenericSurgeryWindow : SS14Window
{
public delegate void OptionSelectedCallback(int selectedOptionData);
private readonly VBoxContainer _optionsBox;
private OptionSelectedCallback _optionSelectedCallback;
protected override Vector2? CustomSize => (300, 400);
public GenericSurgeryWindow()
{
Title = Loc.GetString("Select surgery target...");
RectClipContent = true;
var vSplitContainer = new VBoxContainer
{
Children =
{
new ScrollContainer
{
SizeFlagsVertical = SizeFlags.FillExpand,
SizeFlagsHorizontal = SizeFlags.FillExpand,
HScrollEnabled = true,
VScrollEnabled = true,
Children =
{
(_optionsBox = new VBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand
})
}
}
}
};
Contents.AddChild(vSplitContainer);
}
public void BuildDisplay(Dictionary<string, int> data, OptionSelectedCallback callback)
{
_optionsBox.DisposeAllChildren();
_optionSelectedCallback = callback;
foreach (var (displayText, callbackData) in data)
{
var button = new SurgeryButton(callbackData);
button.SetOnToggleBehavior(OnButtonPressed);
button.SetDisplayText(Loc.GetString(displayText));
_optionsBox.AddChild(button);
}
}
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
{
if (args.Button.Parent is SurgeryButton surgery)
{
_optionSelectedCallback(surgery.CallbackData);
}
}
}
class SurgeryButton : PanelContainer
{
public Button Button { get; }
private SpriteView SpriteView { get; }
private Control EntityControl { get; }
private Label DisplayText { get; }
public int CallbackData { get; }
public SurgeryButton(int callbackData)
{
CallbackData = callbackData;
Button = new Button
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
ToggleMode = true,
MouseFilter = MouseFilterMode.Stop
};
AddChild(Button);
AddChild(new HBoxContainer
{
Children =
{
(SpriteView = new SpriteView
{
CustomMinimumSize = new Vector2(32.0f, 32.0f)
}),
(DisplayText = new Label
{
SizeFlagsVertical = SizeFlags.ShrinkCenter,
Text = "N/A",
}),
(new Control
{
SizeFlagsHorizontal = SizeFlags.FillExpand
})
}
});
}
public void SetDisplayText(string text)
{
DisplayText.Text = text;
}
public void SetOnToggleBehavior(Action<BaseButton.ButtonToggledEventArgs> behavior)
{
Button.OnToggled += behavior;
}
public void SetSprite()
{
//button.SpriteView.Sprite = sprite;
}
}
}

View File

@@ -37,7 +37,7 @@ namespace Content.Client.GameObjects.Components
/// <returns>True if the click worked, false otherwise.</returns>
public bool CheckClick(Vector2 worldPos, out int drawDepth, out uint renderOrder)
{
if (!Owner.TryGetComponent(out ISpriteComponent sprite) || !sprite.Visible)
if (!Owner.TryGetComponent(out ISpriteComponent? sprite) || !sprite.Visible)
{
drawDepth = default;
renderOrder = default;

View File

@@ -1,29 +0,0 @@
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components
{
/// <summary>
/// Fuck I really hate doing this
/// TODO: make sure the client only gets damageable component on the clientside entity for its player mob
/// </summary>
[RegisterComponent]
public class DamageableComponent : SharedDamageableComponent
{
/// <inheritdoc />
public override string Name => "Damageable";
public Dictionary<DamageType, int> CurrentDamage = new Dictionary<DamageType, int>();
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if(curState is DamageComponentState damagestate)
{
CurrentDamage = damagestate.CurrentDamage;
}
}
}
}

View File

@@ -0,0 +1,67 @@
#nullable enable
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Localization;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
/// <summary>
/// Initializes a <see cref="DisposalRouterWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public class DisposalRouterBoundUserInterface : BoundUserInterface
{
private DisposalRouterWindow? _window;
public DisposalRouterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = new DisposalRouterWindow();
_window.OpenCentered();
_window.OnClose += Close;
_window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
_window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
}
private void ButtonPressed(UiAction action, string tag)
{
SendMessage(new UiActionMessage(action, tag));
_window?.Close();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (!(state is DisposalRouterUserInterfaceState cast))
{
return;
}
_window?.UpdateState(cast);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -0,0 +1,51 @@
using Content.Shared.GameObjects.Components.Disposal;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
/// <summary>
/// Client-side UI used to control a <see cref="SharedDisposalRouterComponent"/>
/// </summary>
public class DisposalRouterWindow : SS14Window
{
public readonly LineEdit TagInput;
public readonly Button Confirm;
protected override Vector2? CustomSize => (400, 80);
public DisposalRouterWindow()
{
Title = Loc.GetString("Disposal Router");
Contents.AddChild(new VBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Tags:")},
new Control {CustomMinimumSize = (0, 10)},
new HBoxContainer
{
Children =
{
(TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0),
ToolTip = Loc.GetString("A comma separated list of tags"), IsValid = tags => TagRegex.IsMatch(tags)}),
new Control {CustomMinimumSize = (10, 0)},
(Confirm = new Button {Text = Loc.GetString("Confirm")})
}
}
}
});
}
public void UpdateState(DisposalRouterUserInterfaceState state)
{
TagInput.Text = state.Tags;
}
}
}

View File

@@ -0,0 +1,67 @@
#nullable enable
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Localization;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
/// <summary>
/// Initializes a <see cref="DisposalTaggerWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public class DisposalTaggerBoundUserInterface : BoundUserInterface
{
private DisposalTaggerWindow? _window;
public DisposalTaggerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = new DisposalTaggerWindow();
_window.OpenCentered();
_window.OnClose += Close;
_window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
_window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
}
private void ButtonPressed(UiAction action, string tag)
{
SendMessage(new UiActionMessage(action, tag));
_window?.Close();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (!(state is DisposalTaggerUserInterfaceState cast))
{
return;
}
_window?.UpdateState(cast);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -0,0 +1,51 @@
using Content.Shared.GameObjects.Components.Disposal;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
/// <summary>
/// Client-side UI used to control a <see cref="SharedDisposalTaggerComponent"/>
/// </summary>
public class DisposalTaggerWindow : SS14Window
{
public readonly LineEdit TagInput;
public readonly Button Confirm;
protected override Vector2? CustomSize => (400, 80);
public DisposalTaggerWindow()
{
Title = Loc.GetString("Disposal Tagger");
Contents.AddChild(new VBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Tag:")},
new Control {CustomMinimumSize = (0, 10)},
new HBoxContainer
{
Children =
{
(TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0),
IsValid = tag => TagRegex.IsMatch(tag)}),
new Control {CustomMinimumSize = (10, 0)},
(Confirm = new Button {Text = Loc.GetString("Confirm")})
}
}
}
});
}
public void UpdateState(DisposalTaggerUserInterfaceState state)
{
TagInput.Text = state.Tag;
}
}
}

View File

@@ -148,7 +148,7 @@ namespace Content.Client.GameObjects.Components.Items
return;
}
if (!entity.TryGetComponent(out ItemComponent item)) return;
if (!entity.TryGetComponent(out ItemComponent? item)) return;
var maybeInHands = item.GetInHandStateInfo(hand.Location);

View File

@@ -1,9 +1,11 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
namespace Content.Client.GameObjects.Components.MedicalScanner
{
[UsedImplicitly]
public class MedicalScannerBoundUserInterface : BoundUserInterface
{
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
@@ -20,6 +22,7 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
Title = Owner.Owner.Name,
};
_window.OnClose += Close;
_window.ScanButton.OnPressed += _ => SendMessage(new UiButtonPressedMessage(UiButton.ScanDNA));
_window.OpenCentered();
}

View File

@@ -1,6 +1,10 @@
using System.Text;
using Content.Shared.Damage;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
@@ -8,28 +12,63 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{
public class MedicalScannerWindow : SS14Window
{
public readonly Button ScanButton;
private readonly Label _diagnostics;
protected override Vector2? CustomSize => (485, 90);
public MedicalScannerWindow()
{
Contents.AddChild(new VBoxContainer
{
Children =
{
(ScanButton = new Button
{
Text = "Scan and Save DNA"
}),
(_diagnostics = new Label
{
Text = ""
})
}
});
}
public void Populate(MedicalScannerBoundUserInterfaceState state)
{
Contents.RemoveAllChildren();
var text = new StringBuilder();
if (state.MaxHealth == 0)
{
text.Append("No patient data.");
} else
{
text.Append($"Patient's health: {state.CurrentHealth}/{state.MaxHealth}\n");
if (state.DamageDictionary != null)
{
foreach (var (dmgType, amount) in state.DamageDictionary)
{
text.Append($"\n{dmgType}: {amount}");
}
}
if (!state.Entity.HasValue ||
!state.HasDamage() ||
!IoCManager.Resolve<IEntityManager>().TryGetEntity(state.Entity.Value, out var entity))
{
_diagnostics.Text = Loc.GetString("No patient data.");
ScanButton.Disabled = true;
}
else
{
text.Append($"{entity.Name}{Loc.GetString("'s health:")}\n");
foreach (var (@class, classAmount) in state.DamageClasses)
{
text.Append($"\n{Loc.GetString("{0}: {1}", @class, classAmount)}");
foreach (var type in @class.ToTypes())
{
if (!state.DamageTypes.TryGetValue(type, out var typeAmount))
{
continue;
}
text.Append($"\n- {Loc.GetString("{0}: {1}", type, typeAmount)}");
}
text.Append("\n");
}
_diagnostics.Text = text.ToString();
ScanButton.Disabled = state.IsScanned;
}
Contents.AddChild(new Label(){Text = text.ToString()});
}
}
}

View File

@@ -23,7 +23,7 @@ namespace Content.Client.GameObjects.Components.Mobs
private const float RestoreRateRamp = 0.1f;
// The maximum magnitude of the kick applied to the camera at any point.
private const float KickMagnitudeMax = 5f;
private const float KickMagnitudeMax = 2f;
private Vector2 _currentKick;
private float _lastKickTime;

View File

@@ -44,8 +44,8 @@ namespace Content.Client.GameObjects.Components.Mobs
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "human_chest_m" : "human_chest_f");
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "human_head_m" : "human_head_f");
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "torso_m" : "torso_f");
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "head_m" : "head_f");
sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female);

View File

@@ -1,22 +0,0 @@
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Mobs
{
[RegisterComponent]
[ComponentReference(typeof(SharedSpeciesComponent))]
public class SpeciesComponent : SharedSpeciesComponent, IClientDraggable
{
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Content.Client.GameObjects.Components.Mobs
WalkModifierOverride = state.WalkModifierOverride;
RunModifierOverride = state.RunModifierOverride;
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Movement;
namespace Content.Client.GameObjects.Components.Movement
{
[RegisterComponent]
[ComponentReference(typeof(IClimbable))]
public class ClimbableComponent : SharedClimbableComponent
{
}
}

View File

@@ -0,0 +1,34 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Movement;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.Physics;
namespace Content.Client.GameObjects.Components.Movement
{
[RegisterComponent]
public class ClimbingComponent : SharedClimbingComponent, IClientDraggable
{
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
if (!(curState is ClimbModeComponentState climbModeState) || Body == null)
{
return;
}
IsClimbing = climbModeState.Climbing;
}
public override bool IsClimbing { get; set; }
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<IClimbable>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Content.Client.GameObjects.Components.Nutrition
_currentHungerThreshold = hunger.CurrentThreshold;
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}

View File

@@ -20,7 +20,7 @@ namespace Content.Client.GameObjects.Components.Nutrition
_currentThirstThreshold = thirst.CurrentThreshold;
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.GameObjects.Components.PDA;
using Content.Shared.GameObjects.Components.PDA;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
@@ -10,7 +10,7 @@ namespace Content.Client.GameObjects.Components.PDA
private enum PDAVisualLayers
{
Base,
Unlit
Flashlight
}
@@ -22,13 +22,13 @@ namespace Content.Client.GameObjects.Components.PDA
return;
}
var sprite = component.Owner.GetComponent<ISpriteComponent>();
sprite.LayerSetVisible(PDAVisualLayers.Unlit, false);
if(!component.TryGetData<bool>(PDAVisuals.ScreenLit, out var isScreenLit))
sprite.LayerSetVisible(PDAVisualLayers.Flashlight, false);
if(!component.TryGetData<bool>(PDAVisuals.FlashlightLit, out var isScreenLit))
{
return;
}
sprite.LayerSetState(PDAVisualLayers.Unlit, "unlit_pda_screen");
sprite.LayerSetVisible(PDAVisualLayers.Unlit, isScreenLit);
sprite.LayerSetState(PDAVisualLayers.Flashlight, "light_overlay");
sprite.LayerSetVisible(PDAVisualLayers.Flashlight, isScreenLit);
}

View File

@@ -1,5 +1,6 @@
using System;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Rotation;
using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
@@ -7,22 +8,23 @@ using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Mobs
namespace Content.Client.GameObjects.Components.Rotation
{
public class SpeciesVisualizer : AppearanceVisualizer
[UsedImplicitly]
public class RotationVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var state))
if (component.TryGetData<RotationState>(RotationVisuals.RotationState, out var state))
{
switch (state)
{
case SharedSpeciesComponent.MobState.Standing:
case RotationState.Vertical:
SetRotation(component, 0);
break;
case SharedSpeciesComponent.MobState.Down:
case RotationState.Horizontal:
SetRotation(component, Angle.FromDegrees(90));
break;
}
@@ -40,7 +42,9 @@ namespace Content.Client.GameObjects.Components.Mobs
}
if (animation.HasRunningAnimation("rotate"))
{
animation.Stop("rotate");
}
animation.Play(new Animation
{

View File

@@ -143,7 +143,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
{
base.FrameUpdate(args);
if (AttachedEntity?.IsValid() != true || !AttachedEntity.TryGetComponent(out DoAfterComponent doAfterComponent))
if (AttachedEntity?.IsValid() != true || !AttachedEntity.TryGetComponent(out DoAfterComponent? doAfterComponent))
{
return;
}

View File

@@ -67,7 +67,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
Gui ??= new DoAfterGui();
Gui.AttachedEntity = entity;
if (entity.TryGetComponent(out DoAfterComponent doAfterComponent))
if (entity.TryGetComponent(out DoAfterComponent? doAfterComponent))
{
foreach (var (_, doAfter) in doAfterComponent.DoAfters)
{
@@ -87,7 +87,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
return;
}
if (!_player.TryGetComponent(out DoAfterComponent doAfterComponent))
if (!_player.TryGetComponent(out DoAfterComponent? doAfterComponent))
{
return;
}

View File

@@ -1,13 +1,19 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using Content.Client.Atmos;
using Content.Client.GameObjects.Components.Atmos;
using Content.Shared.Atmos;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.Atmos;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -16,8 +22,9 @@ using Robust.Shared.Utility;
namespace Content.Client.GameObjects.EntitySystems
{
[UsedImplicitly]
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
private readonly Dictionary<float, Color> _fireCache = new Dictionary<float, Color>();
@@ -36,19 +43,20 @@ namespace Content.Client.GameObjects.EntitySystems
private readonly float[][] _fireFrameDelays = new float[FireStates][];
private readonly int[] _fireFrameCounter = new int[FireStates];
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>> _overlay = new Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>>();
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _tileData =
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent(new EntityEventHandler<GasTileOverlayMessage>(OnTileOverlayMessage));
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
_mapManager.OnGridRemoved += OnGridRemoved;
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
var gas = Atmospherics.GetGas(i);
switch (gas.GasOverlay)
var overlay = Atmospherics.GetOverlay(i);
switch (overlay)
{
case SpriteSpecifier.Rsi animated:
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
@@ -82,13 +90,77 @@ namespace Content.Client.GameObjects.EntitySystems
_fireFrameDelays[i] = state.GetDelays();
_fireFrameCounter[i] = 0;
}
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
overlayManager.AddOverlay(new GasTileOverlay());
}
private void HandleGasOverlayMessage(GasOverlayMessage message)
{
foreach (var (indices, data) in message.OverlayData)
{
var chunk = GetOrCreateChunk(message.GridId, indices);
chunk.Update(data, indices);
}
}
// Slightly different to the server-side system version
private GasOverlayChunk GetOrCreateChunk(GridId gridId, MapIndices indices)
{
if (!_tileData.TryGetValue(gridId, out var chunks))
{
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
_tileData[gridId] = chunks;
}
var chunkIndices = GetGasChunkIndices(indices);
if (!chunks.TryGetValue(chunkIndices, out var chunk))
{
chunk = new GasOverlayChunk(gridId, chunkIndices);
chunks[chunkIndices] = chunk;
}
return chunk;
}
public override void Shutdown()
{
base.Shutdown();
_mapManager.OnGridRemoved -= OnGridRemoved;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
overlayManager.RemoveOverlay(nameof(GasTileOverlay));
}
private void OnGridRemoved(GridId gridId)
{
if (_tileData.ContainsKey(gridId))
{
_tileData.Remove(gridId);
}
}
public bool HasData(GridId gridId)
{
return _tileData.ContainsKey(gridId);
}
public (Texture, Color color)[] GetOverlays(GridId gridIndex, MapIndices indices)
{
if (!_overlay.TryGetValue(gridIndex, out var tiles) || !tiles.TryGetValue(indices, out var overlays))
if (!_tileData.TryGetValue(gridIndex, out var chunks))
return Array.Empty<(Texture, Color)>();
var chunkIndex = GetGasChunkIndices(indices);
if (!chunks.TryGetValue(chunkIndex, out var chunk))
return Array.Empty<(Texture, Color)>();
var overlays = chunk.GetData(indices);
if (overlays.Gas == null)
return Array.Empty<(Texture, Color)>();
var fire = overlays.FireState != 0;
var length = overlays.Gas.Length + (fire ? 1 : 0);
@@ -112,23 +184,6 @@ namespace Content.Client.GameObjects.EntitySystems
return list;
}
private void OnTileOverlayMessage(GasTileOverlayMessage ev)
{
if(ev.ClearAllOtherOverlays)
_overlay.Clear();
foreach (var data in ev.OverlayData)
{
if (!_overlay.TryGetValue(data.GridIndex, out var gridOverlays))
{
gridOverlays = new Dictionary<MapIndices, GasOverlayData>();
_overlay.Add(data.GridIndex, gridOverlays);
}
gridOverlays[data.GridIndices] = data.Data;
}
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);

View File

@@ -25,7 +25,7 @@ namespace Content.Client.GameObjects.EntitySystems
{
var playerEnt = _playerManager.LocalPlayer?.ControlledEntity;
if (playerEnt == null || !playerEnt.TryGetComponent(out IMoverComponent mover))
if (playerEnt == null || !playerEnt.TryGetComponent(out IMoverComponent? mover))
{
return;
}