Merge branch 'master-upstream' into expl_int_analyzer

# Conflicts:
#	Content.Server/GameObjects/Components/Body/Part/BodyPartComponent.cs
#	Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs
#	Content.Server/GameObjects/Components/Chemistry/PillComponent.cs
#	Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs
#	Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs
#	Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs
#	Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs
#	Content.Server/GameObjects/Components/Medical/HealingComponent.cs
#	Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs
#	Content.Shared/Chemistry/Solution.cs
This commit is contained in:
Paul
2021-02-04 17:50:28 +01:00
499 changed files with 6357 additions and 8689 deletions

View File

@@ -0,0 +1,74 @@
#nullable enable
using System;
using Content.Shared.GameObjects.Components.Chemistry;
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.Maths;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Chemistry
{
[UsedImplicitly]
public class FoamVisualizer : AppearanceVisualizer
{
private const string AnimationKey = "foamdissolve_animation";
private Animation _foamDissolve = new();
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var delay = 0.6f;
var state = "foam-dissolve";
if (node.TryGetNode("animationTime", out var delayNode))
{
delay = delayNode.AsFloat();
}
if (node.TryGetNode("animationState", out var stateNode))
{
state = stateNode.AsString();
}
_foamDissolve = new Animation {Length = TimeSpan.FromSeconds(delay)};
var flick = new AnimationTrackSpriteFlick();
_foamDissolve.AnimationTracks.Add(flick);
flick.LayerKey = FoamVisualLayers.Base;
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame(state, 0f));
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.TryGetData<bool>(FoamVisuals.State, out var state))
{
if (state)
{
if (component.Owner.TryGetComponent(out AnimationPlayerComponent? animPlayer))
{
if (!animPlayer.HasRunningAnimation(AnimationKey))
animPlayer.Play(_foamDissolve, AnimationKey);
}
}
}
if (component.TryGetData<Color>(FoamVisuals.Color, out var color))
{
if (component.Owner.TryGetComponent(out ISpriteComponent? sprite))
{
sprite.Color = color;
}
}
}
}
public enum FoamVisualLayers : byte
{
Base
}
}

View File

@@ -0,0 +1,68 @@
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
#nullable enable
namespace Content.Client.GameObjects.Components.Chemistry
{
[RegisterComponent]
public sealed class HyposprayComponent : SharedHyposprayComponent, IItemStatus
{
[ViewVariables] private ReagentUnit CurrentVolume { get; set; }
[ViewVariables] private ReagentUnit TotalVolume { get; set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not HyposprayComponentState cState)
return;
CurrentVolume = cState.CurVolume;
TotalVolume = cState.MaxVolume;
_uiUpdateNeeded = true;
}
Control IItemStatus.MakeControl()
{
return new StatusControl(this);
}
private sealed class StatusControl : Control
{
private readonly HyposprayComponent _parent;
private readonly RichTextLabel _label;
public StatusControl(HyposprayComponent parent)
{
_parent = parent;
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
AddChild(_label);
parent._uiUpdateNeeded = true;
}
protected override void Update(FrameEventArgs args)
{
base.Update(args);
if (!_parent._uiUpdateNeeded)
{
return;
}
_parent._uiUpdateNeeded = false;
_label.SetMarkup(Loc.GetString(
"Volume: [color=white]{0}/{1}[/color]",
_parent.CurrentVolume, _parent.TotalVolume));
}
}
}
}

View File

@@ -48,7 +48,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser
_window.DispenseButton1.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount1);
_window.DispenseButton5.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount5);
_window.DispenseButton10.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount10);
_window.DispenseButton15.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount15);
_window.DispenseButton20.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount20);
_window.DispenseButton25.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount25);
_window.DispenseButton30.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount30);
_window.DispenseButton50.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount50);
_window.DispenseButton100.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount100);
}

View File

@@ -34,9 +34,18 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser
/// <summary>Sets the dispense amount to 10 when pressed.</summary>
public Button DispenseButton10 { get; }
/// <summary>Sets the dispense amount to 15 when pressed.</summary>
public Button DispenseButton15 { get; }
/// <summary>Sets the dispense amount to 20 when pressed.</summary>
public Button DispenseButton20 { get; }
/// <summary>Sets the dispense amount to 25 when pressed.</summary>
public Button DispenseButton25 { get; }
/// <summary>Sets the dispense amount to 30 when pressed.</summary>
public Button DispenseButton30 { get; }
/// <summary>Sets the dispense amount to 50 when pressed.</summary>
public Button DispenseButton50 { get; }
@@ -79,7 +88,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser
(DispenseButton1 = new Button {Text = "1", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenRight }}),
(DispenseButton5 = new Button {Text = "5", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton10 = new Button {Text = "10", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton15 = new Button {Text = "15", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton20 = new Button {Text = "20", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton25 = new Button {Text = "25", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton30 = new Button {Text = "30", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton50 = new Button {Text = "50", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}),
(DispenseButton100 = new Button {Text = "100", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenLeft }}),
}
@@ -215,9 +227,18 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser
case 10:
DispenseButton10.Pressed = true;
break;
case 15:
DispenseButton15.Pressed = true;
break;
case 20:
DispenseButton20.Pressed = true;
break;
case 25:
DispenseButton25.Pressed = true;
break;
case 30:
DispenseButton30.Pressed = true;
break;
case 50:
DispenseButton50.Pressed = true;
break;

View File

@@ -0,0 +1,26 @@
#nullable enable
using Content.Shared.GameObjects.Components.Chemistry;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Chemistry
{
[UsedImplicitly]
public class SmokeVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.TryGetData<Color>(SmokeVisuals.Color, out var color))
{
if (component.Owner.TryGetComponent(out ISpriteComponent? sprite))
{
sprite.Color = color;
}
}
}
}
}

View File

@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.GameObjects.EntitySystems;
using Content.Shared;
using Content.Shared.GameObjects.Components.Instruments;
using Content.Shared.Physics;
using Robust.Client.Audio.Midi;
@@ -11,13 +10,11 @@ using Robust.Shared.Audio.Midi;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Instruments
@@ -162,6 +159,41 @@ namespace Content.Client.GameObjects.Components.Instruments
[ViewVariables]
public bool IsRendererAlive => _renderer != null;
[ViewVariables]
public int PlayerTotalTick => _renderer?.PlayerTotalTick ?? 0;
[ViewVariables]
public int PlayerTick
{
get => _renderer?.PlayerTick ?? 0;
set
{
if (!IsRendererAlive || _renderer!.Status != MidiRendererStatus.File) return;
_midiEventBuffer.Clear();
_renderer.PlayerTick = value;
var tick = _renderer.SequencerTick;
// We add a "all notes off" message.
for (byte i = 0; i < 16; i++)
{
_midiEventBuffer.Add(new MidiEvent()
{
Tick = tick, Type = 176,
Control = 123, Velocity = 0, Channel = i,
});
}
// Now we add a Reset All Controllers message.
_midiEventBuffer.Add(new MidiEvent()
{
Tick = tick, Type = 176,
Control = 121, Value = 0,
});
}
}
public override void Initialize()
{
base.Initialize();
@@ -390,20 +422,6 @@ namespace Content.Client.GameObjects.Components.Instruments
/// <param name="midiEvent">The received midi event</param>
private void RendererOnMidiEvent(MidiEvent midiEvent)
{
// avoid of out-of-band, unimportant or unsupported events
switch (midiEvent.Type)
{
case 0x80: // NOTE_OFF
case 0x90: // NOTE_ON
case 0xa0: // KEY_PRESSURE
case 0xb0: // CONTROL_CHANGE
case 0xd0: // CHANNEL_PRESSURE
case 0xe0: // PITCH_BEND
break;
default:
return;
}
_midiEventBuffer.Add(midiEvent);
}

View File

@@ -1,14 +1,23 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Animations;
using Content.Client.UserInterface;
using Content.Shared.GameObjects.Components.Items;
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.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Items
@@ -244,6 +253,23 @@ namespace Content.Client.GameObjects.Components.Items
}
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
{
case AnimatePickupEntityMessage msg:
{
if (Owner.EntityManager.TryGetEntity(msg.EntityId, out var entity))
{
ReusableAnimations.AnimateEntityPickup(entity, msg.EntityPosition, Owner.Transform.WorldPosition);
}
break;
}
}
}
public void SendChangeHand(string index)
{
SendNetworkMessage(new ClientChangedHandMsg(index));

View File

@@ -1,169 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Interfaces;
using Robust.Client.GameObjects;
using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Mobs
{
/// <summary>
/// A character UI component which shows the current damage state of the mob (living/dead)
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(SharedOverlayEffectsComponent))]
public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
/// <summary>
/// A list of overlay containers representing the current overlays applied
/// </summary>
private List<OverlayContainer> _currentEffects = new();
[ViewVariables(VVAccess.ReadOnly)]
public List<OverlayContainer> ActiveOverlays
{
get => _currentEffects;
set => SetEffects(value);
}
public override void Initialize()
{
base.Initialize();
UpdateOverlays();
}
public override void HandleMessage(ComponentMessage message, IComponent component)
{
switch (message)
{
case PlayerAttachedMsg _:
UpdateOverlays();
break;
case PlayerDetachedMsg _:
ActiveOverlays.ForEach(o => _overlayManager.RemoveOverlay(o.ID));
break;
}
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
if (message is OverlayEffectComponentMessage overlayMessage)
{
SetEffects(overlayMessage.Overlays);
}
}
private void UpdateOverlays()
{
_currentEffects = _overlayManager.AllOverlays
.Where(overlay => Enum.IsDefined(typeof(SharedOverlayID), overlay.ID))
.Select(overlay => new OverlayContainer(overlay.ID))
.ToList();
foreach (var overlayContainer in ActiveOverlays)
{
if (!_overlayManager.HasOverlay(overlayContainer.ID))
{
if (TryCreateOverlay(overlayContainer, out var overlay))
{
_overlayManager.AddOverlay(overlay);
}
}
}
SendNetworkMessage(new ResendOverlaysMessage(), _netManager.ServerChannel);
}
private void SetEffects(List<OverlayContainer> newOverlays)
{
foreach (var container in ActiveOverlays.ToArray())
{
if (!newOverlays.Contains(container))
{
RemoveOverlay(container);
}
}
foreach (var container in newOverlays)
{
if (!ActiveOverlays.Contains(container))
{
AddOverlay(container);
}
else
{
UpdateOverlayConfiguration(container, _overlayManager.GetOverlay(container.ID));
}
}
_currentEffects = newOverlays;
}
private void RemoveOverlay(OverlayContainer container)
{
ActiveOverlays.Remove(container);
_overlayManager.RemoveOverlay(container.ID);
}
private void AddOverlay(OverlayContainer container)
{
if (_overlayManager.HasOverlay(container.ID))
{
return;
}
ActiveOverlays.Add(container);
if (TryCreateOverlay(container, out var overlay))
{
_overlayManager.AddOverlay(overlay);
}
else
{
Logger.ErrorS("overlay", $"Could not add overlay {container.ID}");
}
}
private void UpdateOverlayConfiguration(OverlayContainer container, Overlay overlay)
{
if (overlay is IConfigurableOverlay configurable)
{
foreach (var param in container.Parameters)
{
configurable.Configure(param);
}
}
}
private bool TryCreateOverlay(OverlayContainer container, out Overlay overlay)
{
var overlayTypes = _reflectionManager.GetAllChildren<Overlay>();
var overlayType = overlayTypes.FirstOrDefault(t => t.Name == container.ID);
if (overlayType != null)
{
overlay = IoCManager.Resolve<IDynamicTypeFactory>().CreateInstance<Overlay>(overlayType);
UpdateOverlayConfiguration(container, overlay);
return true;
}
overlay = default;
return false;
}
}
}

View File

@@ -46,7 +46,7 @@ namespace Content.Client.GameObjects.Components.Observer
private void SetGhostVisibility(bool visibility)
{
foreach (var ghost in _componentManager.GetAllComponents(typeof(GhostComponent)))
foreach (var ghost in _componentManager.GetAllComponents(typeof(GhostComponent), true))
{
if (ghost.Owner.TryGetComponent(out SpriteComponent? component))
{

View File

@@ -1,17 +1,23 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.Animations;
using Content.Client.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -77,6 +83,9 @@ namespace Content.Client.GameObjects.Components.Storage
case CloseStorageUIMessage _:
CloseUI();
break;
case AnimateInsertingEntitiesMessage msg:
HandleAnimatingInsertingEntities(msg);
break;
}
}
@@ -92,6 +101,24 @@ namespace Content.Client.GameObjects.Components.Storage
Window.BuildEntityList();
}
/// <summary>
/// Animate the newly stored entities in <paramref name="msg"/> flying towards this storage's position
/// </summary>
/// <param name="msg"></param>
private void HandleAnimatingInsertingEntities(AnimateInsertingEntitiesMessage msg)
{
for (var i = 0; msg.StoredEntities.Count > i; i++)
{
var entityId = msg.StoredEntities[i];
var initialPosition = msg.EntityPositions[i];
if (Owner.EntityManager.TryGetEntity(entityId, out var entity))
{
ReusableAnimations.AnimateEntityPickup(entity, initialPosition, Owner.Transform.WorldPosition);
}
}
}
/// <summary>
/// Opens the storage UI if closed. Closes it if opened.
/// </summary>
@@ -284,7 +311,7 @@ namespace Content.Client.GameObjects.Components.Storage
/// <summary>
/// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label
/// </summary>
private class EntityButton : PanelContainer
private class EntityButton : Control
{
public EntityUid EntityUid { get; set; }
public Button ActualButton { get; }

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading;
using Content.Client.Graphics.Overlays;
using Content.Shared.GameObjects.Components.Weapons;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Graphics.Overlays;
@@ -19,10 +20,8 @@ namespace Content.Client.GameObjects.Components.Weapons
[RegisterComponent]
public sealed class FlashableComponent : SharedFlashableComponent
{
private CancellationTokenSource _cancelToken;
private TimeSpan _startTime;
private double _duration;
private FlashOverlay _overlay;
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
@@ -57,94 +56,15 @@ namespace Content.Client.GameObjects.Components.Weapons
if (currentTime > newEndTime)
{
DisableOverlay();
return;
}
_startTime = newState.Time;
_duration = newState.Duration;
EnableOverlay(newEndTime - currentTime);
}
private void EnableOverlay(double duration)
{
// If the timer gets reset
if (_overlay != null)
{
_overlay.Duration = _duration;
_overlay.StartTime = _startTime;
_cancelToken.Cancel();
}
else
{
var overlayManager = IoCManager.Resolve<IOverlayManager>();
_overlay = new FlashOverlay(_duration);
overlayManager.AddOverlay(_overlay);
}
_cancelToken = new CancellationTokenSource();
Owner.SpawnTimer((int) duration * 1000, DisableOverlay, _cancelToken.Token);
}
private void DisableOverlay()
{
if (_overlay == null)
{
return;
}
var overlayManager = IoCManager.Resolve<IOverlayManager>();
overlayManager.RemoveOverlay(_overlay.ID);
_overlay = null;
_cancelToken.Cancel();
_cancelToken = null;
}
}
public sealed class FlashOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly IGameTiming _timer;
private readonly IClyde _displayManager;
public TimeSpan StartTime { get; set; }
public double Duration { get; set; }
public FlashOverlay(double duration) : base(nameof(FlashOverlay))
{
_timer = IoCManager.Resolve<IGameTiming>();
_displayManager = IoCManager.Resolve<IClyde>();
StartTime = _timer.CurTime;
Duration = duration;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
{
var elapsedTime = (_timer.CurTime - StartTime).TotalSeconds;
if (elapsedTime > Duration)
{
return;
}
var screenHandle = (DrawingHandleScreen) handle;
screenHandle.DrawRect(
new UIBox2(0.0f, 0.0f, _displayManager.ScreenSize.X, _displayManager.ScreenSize.Y),
Color.White.WithAlpha(GetAlpha(elapsedTime / Duration))
);
}
private float GetAlpha(double ratio)
{
// Ideally you just want a smooth slope to finish it so it's not jarring at the end
// By all means put in a better curve
const float slope = -9.0f;
const float exponent = 0.1f;
const float yOffset = 9.0f;
const float xOffset = 0.0f;
// Overkill but easy to adjust if you want to mess around with the design
var result = (float) MathHelper.Clamp(slope * (float) Math.Pow(ratio - xOffset, exponent) + yOffset, 0.0, 1.0);
DebugTools.Assert(!float.IsNaN(result));
return result;
var overlay = overlayManager.GetOverlay<FlashOverlay>(nameof(FlashOverlay));
overlay.ReceiveFlash(_duration);
}
}
}