Merge branch 'master' into fix_highlight
This commit is contained in:
54
.github/workflows/check-trailing-whitespace.yml
vendored
54
.github/workflows/check-trailing-whitespace.yml
vendored
@@ -1,54 +0,0 @@
|
|||||||
name: Trailing Whitespace Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Trailing Whitespace Check
|
|
||||||
if: github.event.pull_request.draft == false
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4.2.2
|
|
||||||
- name: Get changed text files
|
|
||||||
id: changed-files
|
|
||||||
uses: tj-actions/changed-files@v46.0.5
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
**.cs
|
|
||||||
**.yml
|
|
||||||
**.swsl
|
|
||||||
**.json
|
|
||||||
**.py
|
|
||||||
- name: Check for trailing whitespace and EOF newline
|
|
||||||
env:
|
|
||||||
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
|
||||||
run: |
|
|
||||||
has_trailing_whitespace=0
|
|
||||||
has_missing_eof_newline=0
|
|
||||||
|
|
||||||
for file in ${ALL_CHANGED_FILES}; do
|
|
||||||
echo "Checking $file"
|
|
||||||
|
|
||||||
# Check for trailing whitespace
|
|
||||||
if grep -qP '[ \t]+$' "$file"; then
|
|
||||||
echo "::error file=$file::Trailing whitespace found"
|
|
||||||
has_trailing_whitespace=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for missing EOF newline
|
|
||||||
if [ -f "$file" ] && [ -s "$file" ]; then
|
|
||||||
last_char=$(tail -c 1 "$file")
|
|
||||||
if [ "$last_char" != "" ] && [ "$last_char" != $'\n' ]; then
|
|
||||||
echo "::error file=$file::Missing newline at end of file"
|
|
||||||
has_missing_eof_newline=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$has_trailing_whitespace" -eq 1 ] || [ "$has_missing_eof_newline" -eq 1 ]; then
|
|
||||||
echo "Issues found: trailing whitespace or missing EOF newline."
|
|
||||||
echo "We recommend using an IDE to prevent this from happening."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -203,6 +203,9 @@ namespace Content.Client.Cargo.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void PopulateOrders(IEnumerable<CargoOrderData> orders)
|
public void PopulateOrders(IEnumerable<CargoOrderData> orders)
|
||||||
{
|
{
|
||||||
|
if (!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
|
||||||
|
return;
|
||||||
|
|
||||||
Requests.DisposeAllChildren();
|
Requests.DisposeAllChildren();
|
||||||
|
|
||||||
foreach (var order in orders)
|
foreach (var order in orders)
|
||||||
@@ -237,6 +240,7 @@ namespace Content.Client.Cargo.UI
|
|||||||
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
|
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
|
||||||
|
|
||||||
// TODO: Disable based on access.
|
// TODO: Disable based on access.
|
||||||
|
row.SetApproveVisible(orderConsole.Mode != CargoOrderConsoleMode.SendToPrimary);
|
||||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||||
Requests.AddChild(row);
|
Requests.AddChild(row);
|
||||||
}
|
}
|
||||||
@@ -290,8 +294,8 @@ namespace Content.Client.Cargo.UI
|
|||||||
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
|
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
|
||||||
_timing.CurTime < orderConsole.NextAccountActionTime;
|
_timing.CurTime < orderConsole.NextAccountActionTime;
|
||||||
|
|
||||||
OrdersSpacer.Visible = !orderConsole.SlipPrinter;
|
OrdersSpacer.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||||
Orders.Visible = !orderConsole.SlipPrinter;
|
Orders.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,15 @@ namespace Content.Client.Cargo.UI
|
|||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetApproveVisible(bool visible)
|
||||||
|
{
|
||||||
|
Approve.Visible = visible;
|
||||||
|
|
||||||
|
if (visible)
|
||||||
|
Cancel.AddStyleClass("OpenLeft");
|
||||||
|
else
|
||||||
|
Cancel.RemoveStyleClass("OpenLeft");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Content.Client.Chat.UI
|
|||||||
{
|
{
|
||||||
public abstract class SpeechBubble : Control
|
public abstract class SpeechBubble : Control
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
|
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
|
||||||
@@ -30,12 +31,12 @@ namespace Content.Client.Chat.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total time a speech bubble stays on screen.
|
/// The total time a speech bubble stays on screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float TotalTime = 4;
|
private static readonly TimeSpan TotalTime = TimeSpan.FromSeconds(4);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of time at the end of the bubble's life at which it starts fading.
|
/// The amount of time at the end of the bubble's life at which it starts fading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float FadeTime = 0.25f;
|
private static readonly TimeSpan FadeTime = TimeSpan.FromSeconds(0.25f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The distance in world space to offset the speech bubble from the center of the entity.
|
/// The distance in world space to offset the speech bubble from the center of the entity.
|
||||||
@@ -50,7 +51,10 @@ namespace Content.Client.Chat.UI
|
|||||||
|
|
||||||
private readonly EntityUid _senderEntity;
|
private readonly EntityUid _senderEntity;
|
||||||
|
|
||||||
private float _timeLeft = TotalTime;
|
/// <summary>
|
||||||
|
/// The time at which this bubble will die.
|
||||||
|
/// </summary>
|
||||||
|
private TimeSpan _deathTime;
|
||||||
|
|
||||||
public float VerticalOffset { get; set; }
|
public float VerticalOffset { get; set; }
|
||||||
private float _verticalOffsetAchieved;
|
private float _verticalOffsetAchieved;
|
||||||
@@ -99,6 +103,7 @@ namespace Content.Client.Chat.UI
|
|||||||
bubble.Measure(Vector2Helpers.Infinity);
|
bubble.Measure(Vector2Helpers.Infinity);
|
||||||
ContentSize = bubble.DesiredSize;
|
ContentSize = bubble.DesiredSize;
|
||||||
_verticalOffsetAchieved = -ContentSize.Y;
|
_verticalOffsetAchieved = -ContentSize.Y;
|
||||||
|
_deathTime = _timing.RealTime + TotalTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
|
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
|
||||||
@@ -107,8 +112,8 @@ namespace Content.Client.Chat.UI
|
|||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
_timeLeft -= args.DeltaSeconds;
|
var timeLeft = (float)(_deathTime - _timing.RealTime).TotalSeconds;
|
||||||
if (_entityManager.Deleted(_senderEntity) || _timeLeft <= 0)
|
if (_entityManager.Deleted(_senderEntity) || timeLeft <= 0)
|
||||||
{
|
{
|
||||||
// Timer spawn to prevent concurrent modification exception.
|
// Timer spawn to prevent concurrent modification exception.
|
||||||
Timer.Spawn(0, Die);
|
Timer.Spawn(0, Die);
|
||||||
@@ -131,10 +136,10 @@ namespace Content.Client.Chat.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_timeLeft <= FadeTime)
|
if (timeLeft <= FadeTime.TotalSeconds)
|
||||||
{
|
{
|
||||||
// Update alpha if we're fading.
|
// Update alpha if we're fading.
|
||||||
Modulate = Color.White.WithAlpha(_timeLeft / FadeTime);
|
Modulate = Color.White.WithAlpha(timeLeft / (float)FadeTime.TotalSeconds);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -144,7 +149,7 @@ namespace Content.Client.Chat.UI
|
|||||||
|
|
||||||
var baseOffset = 0f;
|
var baseOffset = 0f;
|
||||||
|
|
||||||
if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
|
if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
|
||||||
baseOffset = speech.SpeechBubbleOffset;
|
baseOffset = speech.SpeechBubbleOffset;
|
||||||
|
|
||||||
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
|
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
|
||||||
@@ -175,9 +180,9 @@ namespace Content.Client.Chat.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void FadeNow()
|
public void FadeNow()
|
||||||
{
|
{
|
||||||
if (_timeLeft > FadeTime)
|
if (_deathTime > _timing.RealTime)
|
||||||
{
|
{
|
||||||
_timeLeft = FadeTime;
|
_deathTime = _timing.RealTime + FadeTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Content.Client.Construction.UI
|
|||||||
private ConstructionSystem? _constructionSystem;
|
private ConstructionSystem? _constructionSystem;
|
||||||
private ConstructionPrototype? _selected;
|
private ConstructionPrototype? _selected;
|
||||||
private List<ConstructionPrototype> _favoritedRecipes = [];
|
private List<ConstructionPrototype> _favoritedRecipes = [];
|
||||||
private Dictionary<string, ContainerButton> _recipeButtons = new();
|
private readonly Dictionary<string, ContainerButton> _recipeButtons = new();
|
||||||
private string _selectedCategory = string.Empty;
|
private string _selectedCategory = string.Empty;
|
||||||
|
|
||||||
private const string FavoriteCatName = "construction-category-favorites";
|
private const string FavoriteCatName = "construction-category-favorites";
|
||||||
@@ -217,8 +217,8 @@ namespace Content.Client.Construction.UI
|
|||||||
var itemButton = new ContainerButton()
|
var itemButton = new ContainerButton()
|
||||||
{
|
{
|
||||||
VerticalAlignment = Control.VAlignment.Center,
|
VerticalAlignment = Control.VAlignment.Center,
|
||||||
Name = recipe.TargetPrototype.Name,
|
Name = recipe.Prototype.Name,
|
||||||
ToolTip = recipe.TargetPrototype.Name,
|
ToolTip = recipe.Prototype.Name,
|
||||||
ToggleMode = true,
|
ToggleMode = true,
|
||||||
Children = { protoView },
|
Children = { protoView },
|
||||||
};
|
};
|
||||||
@@ -235,7 +235,7 @@ namespace Content.Client.Construction.UI
|
|||||||
|
|
||||||
if (buttonToggledEventArgs.Pressed &&
|
if (buttonToggledEventArgs.Pressed &&
|
||||||
_selected != null &&
|
_selected != null &&
|
||||||
_recipeButtons.TryGetValue(_selected.Name!, out var oldButton))
|
_recipeButtons.TryGetValue(_selected.ID, out var oldButton))
|
||||||
{
|
{
|
||||||
oldButton.Pressed = false;
|
oldButton.Pressed = false;
|
||||||
SelectGridButton(oldButton, false);
|
SelectGridButton(oldButton, false);
|
||||||
@@ -245,7 +245,7 @@ namespace Content.Client.Construction.UI
|
|||||||
};
|
};
|
||||||
|
|
||||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||||
_recipeButtons[recipe.Prototype.Name!] = itemButton;
|
_recipeButtons[recipe.Prototype.ID] = itemButton;
|
||||||
var isCurrentButtonSelected = _selected == recipe.Prototype;
|
var isCurrentButtonSelected = _selected == recipe.Prototype;
|
||||||
itemButton.Pressed = isCurrentButtonSelected;
|
itemButton.Pressed = isCurrentButtonSelected;
|
||||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||||
@@ -307,7 +307,7 @@ namespace Content.Client.Construction.UI
|
|||||||
if (button.Parent is not PanelContainer buttonPanel)
|
if (button.Parent is not PanelContainer buttonPanel)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
button.Modulate = select ? Color.Green : Color.Transparent;
|
button.Children.Single().Modulate = select ? Color.Green : Color.White;
|
||||||
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
|
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
|
||||||
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
|
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
|
||||||
}
|
}
|
||||||
|
|||||||
54
Content.Client/Instruments/InstrumentSystem.MidiParsing.cs
Normal file
54
Content.Client/Instruments/InstrumentSystem.MidiParsing.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Instruments;
|
||||||
|
using Robust.Shared.Audio.Midi;
|
||||||
|
|
||||||
|
namespace Content.Client.Instruments;
|
||||||
|
|
||||||
|
public sealed partial class InstrumentSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to parse the input data as a midi and set the channel names respectively.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Thank you to http://www.somascape.org/midi/tech/mfile.html for providing an awesome resource for midi files.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method has exception tolerance and does not throw, even if the midi file is invalid.
|
||||||
|
/// </remarks>
|
||||||
|
private bool TrySetChannels(EntityUid uid, byte[] data)
|
||||||
|
{
|
||||||
|
if (!MidiParser.MidiParser.TryGetMidiTracks(data, out var tracks, out var error))
|
||||||
|
{
|
||||||
|
Log.Error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedTracks = new List<MidiTrack?>();
|
||||||
|
for (var index = 0; index < tracks.Length; index++)
|
||||||
|
{
|
||||||
|
var midiTrack = tracks[index];
|
||||||
|
if (midiTrack is { TrackName: null, ProgramName: null, InstrumentName: null})
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (midiTrack)
|
||||||
|
{
|
||||||
|
case { TrackName: not null, ProgramName: not null }:
|
||||||
|
case { TrackName: not null, InstrumentName: not null }:
|
||||||
|
case { TrackName: not null }:
|
||||||
|
case { ProgramName: not null }:
|
||||||
|
resolvedTracks.Add(midiTrack);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
resolvedTracks.Add(null); // Used so the channel still displays as MIDI Channel X and doesn't just take the next valid one in the UI
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug($"Channel name: {resolvedTracks.Last()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseNetworkEvent(new InstrumentSetChannelsEvent(GetNetEntity(uid), resolvedTracks.Take(RobustMidiEvent.MaxChannels).ToArray()));
|
||||||
|
Log.Debug($"Resolved {resolvedTracks.Count} channels.");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Instruments;
|
using Content.Shared.Instruments;
|
||||||
@@ -12,7 +13,7 @@ using Robust.Shared.Timing;
|
|||||||
|
|
||||||
namespace Content.Client.Instruments;
|
namespace Content.Client.Instruments;
|
||||||
|
|
||||||
public sealed class InstrumentSystem : SharedInstrumentSystem
|
public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||||
@@ -23,6 +24,8 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
|||||||
public int MaxMidiEventsPerBatch { get; private set; }
|
public int MaxMidiEventsPerBatch { get; private set; }
|
||||||
public int MaxMidiEventsPerSecond { get; private set; }
|
public int MaxMidiEventsPerSecond { get; private set; }
|
||||||
|
|
||||||
|
public event Action? OnChannelsUpdated;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -38,6 +41,26 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
|||||||
|
|
||||||
SubscribeLocalEvent<InstrumentComponent, ComponentShutdown>(OnShutdown);
|
SubscribeLocalEvent<InstrumentComponent, ComponentShutdown>(OnShutdown);
|
||||||
SubscribeLocalEvent<InstrumentComponent, ComponentHandleState>(OnHandleState);
|
SubscribeLocalEvent<InstrumentComponent, ComponentHandleState>(OnHandleState);
|
||||||
|
SubscribeLocalEvent<ActiveInstrumentComponent, AfterAutoHandleStateEvent>(OnActiveInstrumentAfterHandleState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isUpdateQueued = false;
|
||||||
|
|
||||||
|
private void OnActiveInstrumentAfterHandleState(Entity<ActiveInstrumentComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||||
|
{
|
||||||
|
// Called in the update loop so that the components update client side for resolving them in TryComps.
|
||||||
|
_isUpdateQueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FrameUpdate(float frameTime)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(frameTime);
|
||||||
|
|
||||||
|
if (!_isUpdateQueued)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isUpdateQueued = false;
|
||||||
|
OnChannelsUpdated?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnHandleState(EntityUid uid, SharedInstrumentComponent component, ref ComponentHandleState args)
|
private void OnHandleState(EntityUid uid, SharedInstrumentComponent component, ref ComponentHandleState args)
|
||||||
@@ -252,7 +275,13 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use overload that takes in byte[] instead.")]
|
||||||
public bool OpenMidi(EntityUid uid, ReadOnlySpan<byte> data, InstrumentComponent? instrument = null)
|
public bool OpenMidi(EntityUid uid, ReadOnlySpan<byte> data, InstrumentComponent? instrument = null)
|
||||||
|
{
|
||||||
|
return OpenMidi(uid, data.ToArray(), instrument);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OpenMidi(EntityUid uid, byte[] data, InstrumentComponent? instrument = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref instrument))
|
if (!Resolve(uid, ref instrument))
|
||||||
return false;
|
return false;
|
||||||
@@ -263,6 +292,8 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
SetMaster(uid, null);
|
SetMaster(uid, null);
|
||||||
|
TrySetChannels(uid, data);
|
||||||
|
|
||||||
instrument.MidiEventBuffer.Clear();
|
instrument.MidiEventBuffer.Clear();
|
||||||
instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add;
|
instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
147
Content.Client/Instruments/MidiParser/MidiInstrument.cs
Normal file
147
Content.Client/Instruments/MidiParser/MidiInstrument.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Instruments.MidiParser;
|
||||||
|
|
||||||
|
// This file was autogenerated. Based on https://www.ccarh.org/courses/253/handout/gminstruments/
|
||||||
|
public enum MidiInstrument : byte
|
||||||
|
{
|
||||||
|
AcousticGrandPiano = 0,
|
||||||
|
BrightAcousticPiano = 1,
|
||||||
|
ElectricGrandPiano = 2,
|
||||||
|
HonkyTonkPiano = 3,
|
||||||
|
RhodesPiano = 4,
|
||||||
|
ChorusedPiano = 5,
|
||||||
|
Harpsichord = 6,
|
||||||
|
Clavinet = 7,
|
||||||
|
Celesta = 8,
|
||||||
|
Glockenspiel = 9,
|
||||||
|
MusicBox = 10,
|
||||||
|
Vibraphone = 11,
|
||||||
|
Marimba = 12,
|
||||||
|
Xylophone = 13,
|
||||||
|
TubularBells = 14,
|
||||||
|
Dulcimer = 15,
|
||||||
|
HammondOrgan = 16,
|
||||||
|
PercussiveOrgan = 17,
|
||||||
|
RockOrgan = 18,
|
||||||
|
ChurchOrgan = 19,
|
||||||
|
ReedOrgan = 20,
|
||||||
|
Accordion = 21,
|
||||||
|
Harmonica = 22,
|
||||||
|
TangoAccordion = 23,
|
||||||
|
AcousticNylonGuitar = 24,
|
||||||
|
AcousticSteelGuitar = 25,
|
||||||
|
ElectricJazzGuitar = 26,
|
||||||
|
ElectricCleanGuitar = 27,
|
||||||
|
ElectricMutedGuitar = 28,
|
||||||
|
OverdrivenGuitar = 29,
|
||||||
|
DistortionGuitar = 30,
|
||||||
|
GuitarHarmonics = 31,
|
||||||
|
AcousticBass = 32,
|
||||||
|
FingeredElectricBass = 33,
|
||||||
|
PluckedElectricBass = 34,
|
||||||
|
FretlessBass = 35,
|
||||||
|
SlapBass1 = 36,
|
||||||
|
SlapBass2 = 37,
|
||||||
|
SynthBass1 = 38,
|
||||||
|
SynthBass2 = 39,
|
||||||
|
Violin = 40,
|
||||||
|
Viola = 41,
|
||||||
|
Cello = 42,
|
||||||
|
Contrabass = 43,
|
||||||
|
TremoloStrings = 44,
|
||||||
|
PizzicatoStrings = 45,
|
||||||
|
OrchestralHarp = 46,
|
||||||
|
Timpani = 47,
|
||||||
|
StringEnsemble1 = 48,
|
||||||
|
StringEnsemble2 = 49,
|
||||||
|
SynthStrings1 = 50,
|
||||||
|
SynthStrings2 = 51,
|
||||||
|
ChoirAah = 52,
|
||||||
|
VoiceOoh = 53,
|
||||||
|
SynthChoir = 54,
|
||||||
|
OrchestraHit = 55,
|
||||||
|
Trumpet = 56,
|
||||||
|
Trombone = 57,
|
||||||
|
Tuba = 58,
|
||||||
|
MutedTrumpet = 59,
|
||||||
|
FrenchHorn = 60,
|
||||||
|
BrassSection = 61,
|
||||||
|
SynthBrass1 = 62,
|
||||||
|
SynthBrass2 = 63,
|
||||||
|
SopranoSax = 64,
|
||||||
|
AltoSax = 65,
|
||||||
|
TenorSax = 66,
|
||||||
|
BaritoneSax = 67,
|
||||||
|
Oboe = 68,
|
||||||
|
EnglishHorn = 69,
|
||||||
|
Bassoon = 70,
|
||||||
|
Clarinet = 71,
|
||||||
|
Piccolo = 72,
|
||||||
|
Flute = 73,
|
||||||
|
Recorder = 74,
|
||||||
|
PanFlute = 75,
|
||||||
|
BottleBlow = 76,
|
||||||
|
Shakuhachi = 77,
|
||||||
|
Whistle = 78,
|
||||||
|
Ocarina = 79,
|
||||||
|
SquareWaveLead = 80,
|
||||||
|
SawtoothWaveLead = 81,
|
||||||
|
CalliopeLead = 82,
|
||||||
|
ChiffLead = 83,
|
||||||
|
CharangLead = 84,
|
||||||
|
VoiceLead = 85,
|
||||||
|
FithsLead = 86,
|
||||||
|
BassLead = 87,
|
||||||
|
NewAgePad = 88,
|
||||||
|
WarmPad = 89,
|
||||||
|
PolysynthPad = 90,
|
||||||
|
ChoirPad = 91,
|
||||||
|
BowedPad = 92,
|
||||||
|
MetallicPad = 93,
|
||||||
|
HaloPad = 94,
|
||||||
|
SweepPad = 95,
|
||||||
|
RainEffect = 96,
|
||||||
|
SoundtrackEffect = 97,
|
||||||
|
CrystalEffect = 98,
|
||||||
|
AtmosphereEffect = 99,
|
||||||
|
BrightnessEffect = 100,
|
||||||
|
GoblinsEffect = 101,
|
||||||
|
EchoesEffect = 102,
|
||||||
|
SciFiEffect = 103,
|
||||||
|
Sitar = 104,
|
||||||
|
Banjo = 105,
|
||||||
|
Shamisen = 106,
|
||||||
|
Koto = 107,
|
||||||
|
Kalimba = 108,
|
||||||
|
Bagpipe = 109,
|
||||||
|
Fiddle = 110,
|
||||||
|
Shanai = 111,
|
||||||
|
TinkleBell = 112,
|
||||||
|
Agogo = 113,
|
||||||
|
SteelDrums = 114,
|
||||||
|
Woodblock = 115,
|
||||||
|
TaikoDrum = 116,
|
||||||
|
MelodicTom = 117,
|
||||||
|
SynthDrum = 118,
|
||||||
|
ReverseCymbal = 119,
|
||||||
|
GuitarFretNoise = 120,
|
||||||
|
BreathNoise = 121,
|
||||||
|
Seashore = 122,
|
||||||
|
BirdTweet = 123,
|
||||||
|
TelephoneRing = 124,
|
||||||
|
Helicopter = 125,
|
||||||
|
Applause = 126,
|
||||||
|
Gunshot = 127,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MidiInstrumentExt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Turns the given enum value into it's string representation to be used in localization.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetStringRep(this MidiInstrument instrument)
|
||||||
|
{
|
||||||
|
return CaseConversion.PascalToKebab(instrument.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
184
Content.Client/Instruments/MidiParser/MidiParser.cs
Normal file
184
Content.Client/Instruments/MidiParser/MidiParser.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text;
|
||||||
|
using Content.Shared.Instruments;
|
||||||
|
|
||||||
|
namespace Content.Client.Instruments.MidiParser;
|
||||||
|
|
||||||
|
public static class MidiParser
|
||||||
|
{
|
||||||
|
// Thanks again to http://www.somascape.org/midi/tech/mfile.html
|
||||||
|
public static bool TryGetMidiTracks(
|
||||||
|
byte[] data,
|
||||||
|
[NotNullWhen(true)] out MidiTrack[]? tracks,
|
||||||
|
[NotNullWhen(false)] out string? error)
|
||||||
|
{
|
||||||
|
tracks = null;
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
var stream = new MidiStreamWrapper(data);
|
||||||
|
|
||||||
|
if (stream.ReadString(4) != "MThd")
|
||||||
|
{
|
||||||
|
error = "Invalid file header";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerLength = stream.ReadUInt32();
|
||||||
|
// MIDI specs define that the header is 6 bytes, we only look at the 6 bytes, if its more, we skip ahead.
|
||||||
|
|
||||||
|
stream.Skip(2); // format
|
||||||
|
var trackCount = stream.ReadUInt16();
|
||||||
|
stream.Skip(2); // time div
|
||||||
|
|
||||||
|
// We now skip ahead if we still have any header length left
|
||||||
|
stream.Skip((int)(headerLength - 6));
|
||||||
|
|
||||||
|
var parsedTracks = new List<MidiTrack>();
|
||||||
|
|
||||||
|
for (var i = 0; i < trackCount; i++)
|
||||||
|
{
|
||||||
|
if (stream.ReadString(4) != "MTrk")
|
||||||
|
{
|
||||||
|
tracks = null;
|
||||||
|
error = "Track contains invalid header";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var track = new MidiTrack();
|
||||||
|
|
||||||
|
var trackLength = stream.ReadUInt32();
|
||||||
|
var trackEnd = stream.StreamPosition + trackLength;
|
||||||
|
var hasMidiEvent = false;
|
||||||
|
byte? lastStatusByte = null;
|
||||||
|
|
||||||
|
while (stream.StreamPosition < trackEnd)
|
||||||
|
{
|
||||||
|
stream.ReadVariableLengthQuantity();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the first (status) byte is less than 128 (hex 80), this implies that running status is in effect,
|
||||||
|
* and that this byte is actually the first data byte (the status carrying over from the previous MIDI event).
|
||||||
|
* This can only be the case if the immediately previous event was also a MIDI event,
|
||||||
|
* i.e. SysEx and Meta events interrupt (clear) running status.
|
||||||
|
* See http://www.somascape.org/midi/tech/mfile.html#events
|
||||||
|
*/
|
||||||
|
|
||||||
|
var firstByte = stream.ReadByte();
|
||||||
|
if (firstByte >= 0x80)
|
||||||
|
{
|
||||||
|
lastStatusByte = firstByte;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Running status: push byte back for reading as data
|
||||||
|
stream.Skip(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first event in each MTrk chunk must specify status.
|
||||||
|
if (lastStatusByte == null)
|
||||||
|
{
|
||||||
|
tracks = null;
|
||||||
|
error = "Track data not valid, expected status byte, got nothing.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventType = (byte)(lastStatusByte & 0xF0);
|
||||||
|
|
||||||
|
switch (lastStatusByte)
|
||||||
|
{
|
||||||
|
// Meta events
|
||||||
|
case 0xFF:
|
||||||
|
{
|
||||||
|
var metaType = stream.ReadByte();
|
||||||
|
var metaLength = stream.ReadVariableLengthQuantity();
|
||||||
|
var metaData = stream.ReadBytes((int)metaLength);
|
||||||
|
if (metaType == 0x00) // SequenceNumber event
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Meta event types 01 through 0F are reserved for text and all follow the basic FF 01 len text format
|
||||||
|
if (metaType is < 0x01 or > 0x0F)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x03 is TrackName,
|
||||||
|
// 0x04 is InstrumentName
|
||||||
|
|
||||||
|
var text = Encoding.ASCII.GetString(metaData, 0, (int)metaLength);
|
||||||
|
switch (metaType)
|
||||||
|
{
|
||||||
|
case 0x03 when track.TrackName == null:
|
||||||
|
track.TrackName = text;
|
||||||
|
break;
|
||||||
|
case 0x04 when track.InstrumentName == null:
|
||||||
|
track.InstrumentName = text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// still here? then we dont care about the event
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysEx events
|
||||||
|
case 0xF0:
|
||||||
|
case 0xF7:
|
||||||
|
{
|
||||||
|
var sysexLength = stream.ReadVariableLengthQuantity();
|
||||||
|
stream.Skip((int)sysexLength);
|
||||||
|
// Sysex events and meta-events cancel any running status which was in effect.
|
||||||
|
// Running status does not apply to and may not be used for these messages.
|
||||||
|
lastStatusByte = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
switch (eventType)
|
||||||
|
{
|
||||||
|
// Program Change
|
||||||
|
case 0xC0:
|
||||||
|
{
|
||||||
|
var programNumber = stream.ReadByte();
|
||||||
|
if (track.ProgramName == null)
|
||||||
|
{
|
||||||
|
if (programNumber < Enum.GetValues<MidiInstrument>().Length)
|
||||||
|
track.ProgramName = Loc.GetString($"instruments-component-menu-midi-channel-{((MidiInstrument)programNumber).GetStringRep()}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x80: // Note Off
|
||||||
|
case 0x90: // Note On
|
||||||
|
case 0xA0: // Polyphonic Key Pressure
|
||||||
|
case 0xB0: // Control Change
|
||||||
|
case 0xE0: // Pitch Bend
|
||||||
|
{
|
||||||
|
hasMidiEvent = true;
|
||||||
|
stream.Skip(2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0xD0: // Channel Pressure
|
||||||
|
{
|
||||||
|
hasMidiEvent = true;
|
||||||
|
stream.Skip(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
error = $"Unknown MIDI event type {lastStatusByte:X2}";
|
||||||
|
tracks = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasMidiEvent)
|
||||||
|
parsedTracks.Add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracks = parsedTracks.ToArray();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Content.Client/Instruments/MidiParser/MidiStreamWrapper.cs
Normal file
103
Content.Client/Instruments/MidiParser/MidiStreamWrapper.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Content.Client.Instruments.MidiParser;
|
||||||
|
|
||||||
|
public sealed class MidiStreamWrapper
|
||||||
|
{
|
||||||
|
private readonly MemoryStream _stream;
|
||||||
|
private byte[] _buffer;
|
||||||
|
|
||||||
|
public long StreamPosition => _stream.Position;
|
||||||
|
|
||||||
|
public MidiStreamWrapper(byte[] data)
|
||||||
|
{
|
||||||
|
_stream = new MemoryStream(data, writable: false);
|
||||||
|
_buffer = new byte[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips X number of bytes in the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of bytes to skip. If 0, no operations on the stream are performed.</param>
|
||||||
|
public void Skip(int count)
|
||||||
|
{
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_stream.Seek(count, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
var b = _stream.ReadByte();
|
||||||
|
if (b == -1)
|
||||||
|
throw new Exception("Unexpected end of stream");
|
||||||
|
|
||||||
|
return (byte)b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads N bytes using the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] ReadBytes(int count)
|
||||||
|
{
|
||||||
|
if (_buffer.Length < count)
|
||||||
|
{
|
||||||
|
Array.Resize(ref _buffer, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
var read = _stream.Read(_buffer, 0, count);
|
||||||
|
if (read != count)
|
||||||
|
throw new Exception("Unexpected end of stream");
|
||||||
|
|
||||||
|
return _buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a 4 byte big-endian uint.
|
||||||
|
/// </summary>
|
||||||
|
public uint ReadUInt32()
|
||||||
|
{
|
||||||
|
var bytes = ReadBytes(4);
|
||||||
|
return (uint)((bytes[0] << 24) |
|
||||||
|
(bytes[1] << 16) |
|
||||||
|
(bytes[2] << 8) |
|
||||||
|
(bytes[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a 2 byte big-endian ushort.
|
||||||
|
/// </summary>
|
||||||
|
public ushort ReadUInt16()
|
||||||
|
{
|
||||||
|
var bytes = ReadBytes(2);
|
||||||
|
return (ushort)((bytes[0] << 8) | bytes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadString(int count)
|
||||||
|
{
|
||||||
|
var bytes = ReadBytes(count);
|
||||||
|
return Encoding.UTF8.GetString(bytes, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint ReadVariableLengthQuantity()
|
||||||
|
{
|
||||||
|
uint value = 0;
|
||||||
|
|
||||||
|
// variable-length-quantities encode ints using 7 bits per byte
|
||||||
|
// the highest bit (7) is used for a continuation flag. We read until the high bit is 0
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var b = ReadByte();
|
||||||
|
value = (value << 7) | (uint)(b & 0x7f); // Shift current value and add 7 bits
|
||||||
|
// value << 7, make room for the next 7 bits
|
||||||
|
// b & 0x7F mask out the high bit to just get the 7 bit payload
|
||||||
|
if ((b & 0x80) == 0)
|
||||||
|
break; // This was the last bit.
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,5 +7,7 @@
|
|||||||
<Button Name="AllButton" Text="{Loc 'instruments-component-channels-all-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
<Button Name="AllButton" Text="{Loc 'instruments-component-channels-all-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
||||||
<Button Name="ClearButton" Text="{Loc 'instruments-component-channels-clear-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
<Button Name="ClearButton" Text="{Loc 'instruments-component-channels-clear-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
<CheckButton Name="DisplayTrackNames"
|
||||||
|
Text="{Loc 'instruments-component-channels-track-names-toggle'}" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</DefaultWindow>
|
</DefaultWindow>
|
||||||
|
|||||||
@@ -1,26 +1,56 @@
|
|||||||
|
using Content.Shared.Instruments;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Audio.Midi;
|
using Robust.Shared.Audio.Midi;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Instruments.UI;
|
namespace Content.Client.Instruments.UI;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class ChannelsMenu : DefaultWindow
|
public sealed partial class ChannelsMenu : DefaultWindow
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = null!;
|
||||||
|
|
||||||
private readonly InstrumentBoundUserInterface _owner;
|
private readonly InstrumentBoundUserInterface _owner;
|
||||||
|
|
||||||
public ChannelsMenu(InstrumentBoundUserInterface owner) : base()
|
public ChannelsMenu(InstrumentBoundUserInterface owner) : base()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
|
|
||||||
ChannelList.OnItemSelected += OnItemSelected;
|
ChannelList.OnItemSelected += OnItemSelected;
|
||||||
ChannelList.OnItemDeselected += OnItemDeselected;
|
ChannelList.OnItemDeselected += OnItemDeselected;
|
||||||
AllButton.OnPressed += OnAllPressed;
|
AllButton.OnPressed += OnAllPressed;
|
||||||
ClearButton.OnPressed += OnClearPressed;
|
ClearButton.OnPressed += OnClearPressed;
|
||||||
|
DisplayTrackNames.OnPressed += OnDisplayTrackNamesPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void EnteredTree()
|
||||||
|
{
|
||||||
|
base.EnteredTree();
|
||||||
|
|
||||||
|
_owner.Instruments.OnChannelsUpdated += UpdateChannelList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisplayTrackNamesPressed(BaseButton.ButtonEventArgs obj)
|
||||||
|
{
|
||||||
|
DisplayTrackNames.SetClickPressed(!DisplayTrackNames.Pressed);
|
||||||
|
Populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateChannelList()
|
||||||
|
{
|
||||||
|
Populate(); // This is kind of in-efficent because we don't filter for which instrument updated its channels, but idc
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ExitedTree()
|
||||||
|
{
|
||||||
|
base.ExitedTree();
|
||||||
|
|
||||||
|
_owner.Instruments.OnChannelsUpdated -= UpdateChannelList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||||
@@ -51,15 +81,71 @@ public sealed partial class ChannelsMenu : DefaultWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Populate(InstrumentComponent? instrument)
|
/// <summary>
|
||||||
|
/// Walks up the tree of instrument masters to find the truest master of them all.
|
||||||
|
/// </summary>
|
||||||
|
private ActiveInstrumentComponent ResolveActiveInstrument(InstrumentComponent? comp)
|
||||||
|
{
|
||||||
|
comp ??= _entityManager.GetComponent<InstrumentComponent>(_owner.Owner);
|
||||||
|
|
||||||
|
var instrument = new Entity<InstrumentComponent>(_owner.Owner, comp);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (instrument.Comp.Master == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
instrument = new Entity<InstrumentComponent>((EntityUid)instrument.Comp.Master,
|
||||||
|
_entityManager.GetComponent<InstrumentComponent>((EntityUid)instrument.Comp.Master));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _entityManager.GetComponent<ActiveInstrumentComponent>(instrument.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate()
|
||||||
{
|
{
|
||||||
ChannelList.Clear();
|
ChannelList.Clear();
|
||||||
|
var instrument = _entityManager.GetComponent<InstrumentComponent>(_owner.Owner);
|
||||||
|
var activeInstrument = ResolveActiveInstrument(instrument);
|
||||||
|
|
||||||
for (int i = 0; i < RobustMidiEvent.MaxChannels; i++)
|
for (int i = 0; i < RobustMidiEvent.MaxChannels; i++)
|
||||||
{
|
{
|
||||||
var item = ChannelList.AddItem(_owner.Loc.GetString("instrument-component-channel-name",
|
var label = _owner.Loc.GetString("instrument-component-channel-name",
|
||||||
("number", i)), null, true, i);
|
("number", i));
|
||||||
|
if (activeInstrument != null
|
||||||
|
&& activeInstrument.Tracks.TryGetValue(i, out var resolvedMidiChannel)
|
||||||
|
&& resolvedMidiChannel != null)
|
||||||
|
{
|
||||||
|
if (DisplayTrackNames.Pressed)
|
||||||
|
{
|
||||||
|
label = resolvedMidiChannel switch
|
||||||
|
{
|
||||||
|
{ TrackName: not null, InstrumentName: not null } =>
|
||||||
|
Loc.GetString("instruments-component-channels-multi",
|
||||||
|
("channel", i),
|
||||||
|
("name", resolvedMidiChannel.TrackName),
|
||||||
|
("other", resolvedMidiChannel.InstrumentName)),
|
||||||
|
{ TrackName: not null } =>
|
||||||
|
Loc.GetString("instruments-component-channels-single",
|
||||||
|
("channel", i),
|
||||||
|
("name", resolvedMidiChannel.TrackName)),
|
||||||
|
_ => label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label = resolvedMidiChannel switch
|
||||||
|
{
|
||||||
|
{ ProgramName: not null } =>
|
||||||
|
Loc.GetString("instruments-component-channels-single",
|
||||||
|
("channel", i),
|
||||||
|
("name", resolvedMidiChannel.ProgramName)),
|
||||||
|
_ => label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = ChannelList.AddItem(label, null, true, i);
|
||||||
|
|
||||||
item.Selected = !instrument?.FilteredChannels[i] ?? false;
|
item.Selected = !instrument?.FilteredChannels[i] ?? false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.Instruments;
|
||||||
using Content.Shared.Instruments.UI;
|
using Content.Shared.Instruments.UI;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Robust.Client.Audio.Midi;
|
using Robust.Client.Audio.Midi;
|
||||||
@@ -101,9 +102,7 @@ namespace Content.Client.Instruments.UI
|
|||||||
public void OpenChannelsMenu()
|
public void OpenChannelsMenu()
|
||||||
{
|
{
|
||||||
_channelsMenu ??= new ChannelsMenu(this);
|
_channelsMenu ??= new ChannelsMenu(this);
|
||||||
EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument);
|
_channelsMenu.Populate();
|
||||||
|
|
||||||
_channelsMenu.Populate(instrument);
|
|
||||||
_channelsMenu.OpenCenteredRight();
|
_channelsMenu.OpenCenteredRight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Robust.Client.UserInterface.XAML;
|
|||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||||
|
|
||||||
@@ -145,10 +146,6 @@ namespace Content.Client.Instruments.UI
|
|||||||
if (!PlayCheck())
|
if (!PlayCheck())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await using var memStream = new MemoryStream((int) file.Length);
|
|
||||||
|
|
||||||
await file.CopyToAsync(memStream);
|
|
||||||
|
|
||||||
if (!_entManager.TryGetComponent<InstrumentComponent>(Entity, out var instrument))
|
if (!_entManager.TryGetComponent<InstrumentComponent>(Entity, out var instrument))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -156,7 +153,7 @@ namespace Content.Client.Instruments.UI
|
|||||||
|
|
||||||
if (!_entManager.System<InstrumentSystem>()
|
if (!_entManager.System<InstrumentSystem>()
|
||||||
.OpenMidi(Entity,
|
.OpenMidi(Entity,
|
||||||
memStream.GetBuffer().AsSpan(0, (int) memStream.Length),
|
file.CopyToArray(),
|
||||||
instrument))
|
instrument))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ namespace Content.Client.Inventory
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private readonly EntityUid _virtualHiddenEntity;
|
private readonly EntityUid _virtualHiddenEntity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current amount of added hand buttons.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private int _handCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current shape of the inventory, needed to calculate the window size.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private Vector2i _inventoryDimensions;
|
||||||
|
|
||||||
public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
_examine = EntMan.System<ExamineSystem>();
|
_examine = EntMan.System<ExamineSystem>();
|
||||||
@@ -93,6 +105,8 @@ namespace Content.Client.Inventory
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_strippingMenu.ClearButtons();
|
_strippingMenu.ClearButtons();
|
||||||
|
_handCount = 0;
|
||||||
|
_inventoryDimensions = Vector2i.Zero;
|
||||||
|
|
||||||
if (EntMan.TryGetComponent<InventoryComponent>(Owner, out var inv))
|
if (EntMan.TryGetComponent<InventoryComponent>(Owner, out var inv))
|
||||||
{
|
{
|
||||||
@@ -152,9 +166,15 @@ namespace Content.Client.Inventory
|
|||||||
// TODO allow windows to resize based on content's desired size
|
// TODO allow windows to resize based on content's desired size
|
||||||
|
|
||||||
// for now: shit-code
|
// for now: shit-code
|
||||||
// this breaks for drones (too many hands, lots of empty vertical space), and looks shit for monkeys and the like.
|
// calculate the window size manually
|
||||||
// but the window is realizable, so eh.
|
// +20 horizontally and vertically from the ContentsContainer margin
|
||||||
_strippingMenu.SetSize = new Vector2(220, snare?.IsEnsnared == true ? 550 : 530);
|
// +16 vertically from the BoxContainer margin
|
||||||
|
// +27 vertically from the window header
|
||||||
|
var horizontalMenuSize = Math.Max(200, Math.Max(_handCount, _inventoryDimensions.X + 1) * (SlotControl.DefaultButtonSize + ButtonSeparation) + 20);
|
||||||
|
var verticalMenuSize = Math.Max(200, (_inventoryDimensions.Y + (_handCount > 0 ? 2 : 1)) * (SlotControl.DefaultButtonSize + ButtonSeparation) + 53);
|
||||||
|
if (snare?.IsEnsnared == true)
|
||||||
|
verticalMenuSize += 20;
|
||||||
|
_strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddHandButton(Hand hand)
|
private void AddHandButton(Hand hand)
|
||||||
@@ -172,6 +192,8 @@ namespace Content.Client.Inventory
|
|||||||
|
|
||||||
UpdateEntityIcon(button, hand.HeldEntity);
|
UpdateEntityIcon(button, hand.HeldEntity);
|
||||||
_strippingMenu!.HandsContainer.AddChild(button);
|
_strippingMenu!.HandsContainer.AddChild(button);
|
||||||
|
LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation));
|
||||||
|
_handCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SlotPressed(GUIBoundKeyEventArgs ev, SlotControl slot)
|
private void SlotPressed(GUIBoundKeyEventArgs ev, SlotControl slot)
|
||||||
@@ -220,6 +242,10 @@ namespace Content.Client.Inventory
|
|||||||
UpdateEntityIcon(button, entity);
|
UpdateEntityIcon(button, entity);
|
||||||
|
|
||||||
LayoutContainer.SetPosition(button, slotDef.StrippingWindowPos * (SlotControl.DefaultButtonSize + ButtonSeparation));
|
LayoutContainer.SetPosition(button, slotDef.StrippingWindowPos * (SlotControl.DefaultButtonSize + ButtonSeparation));
|
||||||
|
if (slotDef.StrippingWindowPos.X > _inventoryDimensions.X)
|
||||||
|
_inventoryDimensions = new Vector2i(slotDef.StrippingWindowPos.X, _inventoryDimensions.Y);
|
||||||
|
if (slotDef.StrippingWindowPos.Y > _inventoryDimensions.Y)
|
||||||
|
_inventoryDimensions = new Vector2i(_inventoryDimensions.X, slotDef.StrippingWindowPos.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateEntityIcon(SlotControl button, EntityUid? entity)
|
private void UpdateEntityIcon(SlotControl button, EntityUid? entity)
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ public sealed class MappingState : GameplayStateBase
|
|||||||
switch (prototype)
|
switch (prototype)
|
||||||
{
|
{
|
||||||
case EntityPrototype entity:
|
case EntityPrototype entity:
|
||||||
textures.AddRange(SpriteComponent.GetPrototypeTextures(entity, _resources).Select(t => t.Default));
|
textures.AddRange(_sprite.GetPrototypeTextures(entity).Select(t => t.Default));
|
||||||
break;
|
break;
|
||||||
case DecalPrototype decal:
|
case DecalPrototype decal:
|
||||||
textures.Add(_sprite.Frame0(decal.Sprite));
|
textures.Add(_sprite.Frame0(decal.Sprite));
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ using Robust.Shared.Console;
|
|||||||
|
|
||||||
namespace Content.Client.Shuttles.Commands;
|
namespace Content.Client.Shuttles.Commands;
|
||||||
|
|
||||||
public sealed class ShowEmergencyShuttleCommand : IConsoleCommand
|
public sealed class ShowEmergencyShuttleCommand : LocalizedEntityCommands
|
||||||
{
|
{
|
||||||
public string Command => "showemergencyshuttle";
|
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||||
public string Description => "Shows the expected position of the emergency shuttle";
|
|
||||||
public string Help => $"{Command}";
|
public override string Command => "showemergencyshuttle";
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
var tstalker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
|
_shuttle.EnableShuttlePosition ^= true;
|
||||||
tstalker.EnableShuttlePosition ^= true;
|
shell.WriteLine(Loc.GetString($"cmd-showemergencyshuttle-status", ("status", _shuttle.EnableShuttlePosition)));
|
||||||
shell.WriteLine($"Set emergency shuttle debug to {tstalker.EnableShuttlePosition}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Content.Client.Strip
|
|||||||
public sealed class StrippingMenu : DefaultWindow
|
public sealed class StrippingMenu : DefaultWindow
|
||||||
{
|
{
|
||||||
public LayoutContainer InventoryContainer = new();
|
public LayoutContainer InventoryContainer = new();
|
||||||
public BoxContainer HandsContainer = new() { Orientation = LayoutOrientation.Horizontal };
|
public LayoutContainer HandsContainer = new();
|
||||||
public BoxContainer SnareContainer = new();
|
public BoxContainer SnareContainer = new();
|
||||||
public bool Dirty = true;
|
public bool Dirty = true;
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,17 @@ namespace Content.Client.UserInterface.Systems.Chat;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSystem>
|
public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSystem>
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||||
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
||||||
|
|
||||||
|
private static readonly Regex StartDoubleQuote = new("\"$");
|
||||||
|
private static readonly Regex EndDoubleQuote = new("^\"|(?<=^@)\"");
|
||||||
|
private static readonly Regex StartAtSign = new("^@");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of words to be highlighted in the chatbox.
|
/// The list of words to be highlighted in the chatbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<string> _highlights = new();
|
private readonly List<string> _highlights = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The string holding the hex color used to highlight words.
|
/// The string holding the hex color used to highlight words.
|
||||||
@@ -42,7 +47,7 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
|||||||
_config.OnValueChanged(CCVars.ChatHighlightsColor, (value) => { _highlightsColor = value; }, true);
|
_config.OnValueChanged(CCVars.ChatHighlightsColor, (value) => { _highlightsColor = value; }, true);
|
||||||
|
|
||||||
// Load highlights if any were saved.
|
// Load highlights if any were saved.
|
||||||
string highlights = _config.GetCVar(CCVars.ChatHighlights);
|
var highlights = _config.GetCVar(CCVars.ChatHighlights);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(highlights))
|
if (!string.IsNullOrEmpty(highlights))
|
||||||
{
|
{
|
||||||
@@ -84,12 +89,12 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
|||||||
|
|
||||||
// We first subdivide the highlights based on newlines to prevent replacing
|
// We first subdivide the highlights based on newlines to prevent replacing
|
||||||
// a valid "\n" tag and adding it to the final regex.
|
// a valid "\n" tag and adding it to the final regex.
|
||||||
string[] splittedHighlights = newHighlights.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var splittedHighlights = newHighlights.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
for (int i = 0; i < splittedHighlights.Length; i++)
|
for (var i = 0; i < splittedHighlights.Length; i++)
|
||||||
{
|
{
|
||||||
// Replace every "\" character with a "\\" to prevent "\n", "\0", etc...
|
// Replace every "\" character with a "\\" to prevent "\n", "\0", etc...
|
||||||
string keyword = splittedHighlights[i].Replace(@"\", @"\\");
|
var keyword = splittedHighlights[i].Replace(@"\", @"\\");
|
||||||
|
|
||||||
// Escape the keyword to prevent special characters like "(" and ")" to be considered valid regex.
|
// Escape the keyword to prevent special characters like "(" and ")" to be considered valid regex.
|
||||||
keyword = Regex.Escape(keyword);
|
keyword = Regex.Escape(keyword);
|
||||||
@@ -102,18 +107,18 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
|||||||
// that make sure the words to match are separated by spaces or punctuation.
|
// that make sure the words to match are separated by spaces or punctuation.
|
||||||
// NOTE: The reason why we don't use \b tags is that \b doesn't match reverse slash characters "\" so
|
// NOTE: The reason why we don't use \b tags is that \b doesn't match reverse slash characters "\" so
|
||||||
// a pre-sanitized (see 1.) string like "\[test]" wouldn't get picked up by the \b.
|
// a pre-sanitized (see 1.) string like "\[test]" wouldn't get picked up by the \b.
|
||||||
if (keyword.Count(c => (c == '"')) > 0)
|
if (keyword.Any(c => c == '"'))
|
||||||
{
|
{
|
||||||
// Matches the last double quote character.
|
// Matches the last double quote character.
|
||||||
keyword = Regex.Replace(keyword, "\"$", "(?!\\w)");
|
keyword = StartDoubleQuote.Replace(keyword, "(?!\\w)");
|
||||||
// When matching for the first double quote character we also consider the possibility
|
// When matching for the first double quote character we also consider the possibility
|
||||||
// of the double quote being preceded by a @ character.
|
// of the double quote being preceded by a @ character.
|
||||||
keyword = Regex.Replace(keyword, "^\"|(?<=^@)\"", "(?<!\\w)");
|
keyword = EndDoubleQuote.Replace(keyword, "(?<!\\w)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the character's name is highlighted only when mentioned directly (eg. it's said by someone),
|
// Make sure the character's name is highlighted only when mentioned directly (eg. it's said by someone),
|
||||||
// for example in 'Name Surname says, "..."' 'Name Surname' won't be highlighted.
|
// for example in 'Name Surname says, "..."' 'Name Surname' won't be highlighted.
|
||||||
keyword = Regex.Replace(keyword, "^@", @"(?<=(?<=,.*"".*)|(?<!\[Name].*))");
|
keyword = StartAtSign.Replace(keyword, @"(?<=(?<=,.*"".*)|(?<!\[Name].*))");
|
||||||
|
|
||||||
_highlights.Add(keyword);
|
_highlights.Add(keyword);
|
||||||
}
|
}
|
||||||
@@ -133,7 +138,7 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
|||||||
var (_, job, _, _, entityName) = data;
|
var (_, job, _, _, entityName) = data;
|
||||||
|
|
||||||
// Mark this entity's name as our character name for the "UpdateHighlights" function.
|
// Mark this entity's name as our character name for the "UpdateHighlights" function.
|
||||||
string newHighlights = "@" + entityName;
|
var newHighlights = "@" + entityName;
|
||||||
|
|
||||||
// Subdivide the character's name based on spaces or hyphens so that every word gets highlighted.
|
// Subdivide the character's name based on spaces or hyphens so that every word gets highlighted.
|
||||||
if (newHighlights.Count(c => (c == ' ' || c == '-')) == 1)
|
if (newHighlights.Count(c => (c == ' ' || c == '-')) == 1)
|
||||||
@@ -145,9 +150,9 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
|||||||
newHighlights = newHighlights.Split('-')[0] + "\n@" + newHighlights.Split('-')[^1];
|
newHighlights = newHighlights.Split('-')[0] + "\n@" + newHighlights.Split('-')[^1];
|
||||||
|
|
||||||
// Convert the job title to kebab-case and use it as a key for the loc file.
|
// Convert the job title to kebab-case and use it as a key for the loc file.
|
||||||
string jobKey = job.Replace(' ', '-').ToLower();
|
var jobKey = job.Replace(' ', '-').ToLower();
|
||||||
|
|
||||||
if (Loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
|
if (_loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
|
||||||
newHighlights += '\n' + jobMatches.Replace(", ", "\n");
|
newHighlights += '\n' + jobMatches.Replace(", ", "\n");
|
||||||
|
|
||||||
UpdateHighlights(newHighlights);
|
UpdateHighlights(newHighlights);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
using Content.Server.Ghost.Roles.Raffles;
|
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Content.Shared.Ghost.Roles;
|
using Content.Shared.Ghost.Roles;
|
||||||
|
using Content.Shared.Ghost.Roles.Raffles;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Console;
|
using Robust.Client.Console;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Numerics;
|
||||||
using System.Numerics;
|
|
||||||
using Content.Server.Ghost.Roles.Raffles;
|
|
||||||
using Content.Shared.Ghost.Roles.Raffles;
|
using Content.Shared.Ghost.Roles.Raffles;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
|||||||
@@ -3,39 +3,33 @@ using Robust.Client.Graphics;
|
|||||||
using Robust.Client.Input;
|
using Robust.Client.Input;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.Map;
|
|
||||||
|
|
||||||
namespace Content.Client.Weapons.Melee;
|
namespace Content.Client.Weapons.Melee;
|
||||||
|
|
||||||
|
public sealed class MeleeSpreadCommand : LocalizedEntityCommands
|
||||||
public sealed class MeleeSpreadCommand : IConsoleCommand
|
|
||||||
{
|
{
|
||||||
public string Command => "showmeleespread";
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
public string Description => "Shows the current weapon's range and arc for debugging";
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||||
public string Help => $"{Command}";
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly MeleeWeaponSystem _meleeSystem = default!;
|
||||||
|
[Dependency] private readonly SharedCombatModeSystem _combatSystem = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
|
|
||||||
|
public override string Command => "showmeleespread";
|
||||||
|
|
||||||
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
var collection = IoCManager.Instance;
|
if (_overlay.RemoveOverlay<MeleeArcOverlay>())
|
||||||
|
|
||||||
if (collection == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var overlayManager = collection.Resolve<IOverlayManager>();
|
_overlay.AddOverlay(new MeleeArcOverlay(
|
||||||
|
EntityManager,
|
||||||
if (overlayManager.RemoveOverlay<MeleeArcOverlay>())
|
_eyeManager,
|
||||||
{
|
_inputManager,
|
||||||
return;
|
_playerManager,
|
||||||
}
|
_meleeSystem,
|
||||||
|
_combatSystem,
|
||||||
var sysManager = collection.Resolve<IEntitySystemManager>();
|
_transformSystem));
|
||||||
|
|
||||||
overlayManager.AddOverlay(new MeleeArcOverlay(
|
|
||||||
collection.Resolve<IEntityManager>(),
|
|
||||||
collection.Resolve<IEyeManager>(),
|
|
||||||
collection.Resolve<IInputManager>(),
|
|
||||||
collection.Resolve<IPlayerManager>(),
|
|
||||||
sysManager.GetEntitySystem<MeleeWeaponSystem>(),
|
|
||||||
sysManager.GetEntitySystem<SharedCombatModeSystem>(),
|
|
||||||
sysManager.GetEntitySystem<SharedTransformSystem>()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
using Content.Client.Implants;
|
|
||||||
using Content.IntegrationTests.Tests.Interaction;
|
using Content.IntegrationTests.Tests.Interaction;
|
||||||
using Content.Shared.Clothing;
|
using Content.Shared.Clothing;
|
||||||
using Content.Shared.Implants;
|
using Content.Shared.Implants;
|
||||||
@@ -11,17 +9,17 @@ using Robust.Shared.Prototypes;
|
|||||||
namespace Content.IntegrationTests.Tests.Chameleon;
|
namespace Content.IntegrationTests.Tests.Chameleon;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures all round <see cref="IsProbablyRoundStartJob">"round start jobs"</see> have an associated chameleon loadout.
|
/// Ensures all <see cref="IsProbablyRoundStartJob">"round start jobs"</see> have an associated chameleon loadout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ChameleonJobLoadoutTest : InteractionTest
|
public sealed class ChameleonJobLoadoutTest : InteractionTest
|
||||||
{
|
{
|
||||||
private readonly List<ProtoId<JobPrototype>> JobBlacklist =
|
private static readonly List<ProtoId<JobPrototype>> JobBlacklist =
|
||||||
[
|
[
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task CheckAllJobs()
|
public Task CheckAllJobs()
|
||||||
{
|
{
|
||||||
var alljobs = ProtoMan.EnumeratePrototypes<JobPrototype>();
|
var alljobs = ProtoMan.EnumeratePrototypes<JobPrototype>();
|
||||||
|
|
||||||
@@ -47,24 +45,16 @@ public sealed class ChameleonJobLoadoutTest : InteractionTest
|
|||||||
validJobs[chameleon.Job.Value] += 1;
|
validJobs[chameleon.Job.Value] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorMessage = new StringBuilder();
|
Assert.Multiple(() =>
|
||||||
errorMessage.AppendLine("The following job(s) have no chameleon prototype(s):");
|
|
||||||
var invalid = false;
|
|
||||||
|
|
||||||
// All round start jobs have a chameleon loadout
|
|
||||||
foreach (var job in validJobs)
|
|
||||||
{
|
{
|
||||||
if (job.Value != 0)
|
foreach (var job in validJobs)
|
||||||
continue;
|
{
|
||||||
|
Assert.That(job.Value, Is.Not.Zero,
|
||||||
|
$"{job.Key} has no chameleonOutfit prototype.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
errorMessage.AppendLine(job.Key + " has no chameleonOutfit prototype.");
|
return Task.CompletedTask;
|
||||||
invalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!invalid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Assert.Fail(errorMessage.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
var client = pair.Client;
|
var client = pair.Client;
|
||||||
var prototypeManager = client.ResolveDependency<IPrototypeManager>();
|
var prototypeManager = client.ResolveDependency<IPrototypeManager>();
|
||||||
var resourceCache = client.ResolveDependency<IResourceCache>();
|
var resourceCache = client.ResolveDependency<IResourceCache>();
|
||||||
|
var spriteSys = client.System<SpriteSystem>();
|
||||||
|
|
||||||
await client.WaitAssertion(() =>
|
await client.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
@@ -26,7 +27,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
Assert.DoesNotThrow(() =>
|
Assert.DoesNotThrow(() =>
|
||||||
{
|
{
|
||||||
var _ = SpriteComponent.GetPrototypeTextures(proto, resourceCache).ToList();
|
var _ = spriteSys.GetPrototypeTextures(proto).ToList();
|
||||||
}, "Prototype {0} threw an exception when getting its textures.",
|
}, "Prototype {0} threw an exception when getting its textures.",
|
||||||
proto.ID);
|
proto.ID);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using Content.Shared.FixedPoint;
|
|||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.NPC.Prototypes;
|
||||||
using Content.Shared.NPC.Systems;
|
using Content.Shared.NPC.Systems;
|
||||||
using Content.Shared.NukeOps;
|
using Content.Shared.NukeOps;
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
@@ -23,12 +24,16 @@ using Content.Shared.Station.Components;
|
|||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.GameRules;
|
namespace Content.IntegrationTests.Tests.GameRules;
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public sealed class NukeOpsTest
|
public sealed class NukeOpsTest
|
||||||
{
|
{
|
||||||
|
private static readonly ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||||
|
private static readonly ProtoId<NpcFactionPrototype> NanotrasenFaction = "NanoTrasen";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded.
|
/// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -119,8 +124,8 @@ public sealed class NukeOpsTest
|
|||||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(player));
|
Assert.That(entMan.HasComponent<NukeOperativeComponent>(player));
|
||||||
Assert.That(roleSys.MindIsAntagonist(mind));
|
Assert.That(roleSys.MindIsAntagonist(mind));
|
||||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
|
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
|
||||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
Assert.That(factionSys.IsMember(player, SyndicateFaction), Is.True);
|
||||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
Assert.That(factionSys.IsMember(player, NanotrasenFaction), Is.False);
|
||||||
var roles = roleSys.MindGetAllRoleInfo(mind);
|
var roles = roleSys.MindGetAllRoleInfo(mind);
|
||||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
|
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
|
||||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||||
@@ -130,8 +135,8 @@ public sealed class NukeOpsTest
|
|||||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(dummyEnts[1]));
|
Assert.That(entMan.HasComponent<NukeOperativeComponent>(dummyEnts[1]));
|
||||||
Assert.That(roleSys.MindIsAntagonist(dummyMind));
|
Assert.That(roleSys.MindIsAntagonist(dummyMind));
|
||||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
||||||
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
|
Assert.That(factionSys.IsMember(dummyEnts[1], SyndicateFaction), Is.True);
|
||||||
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
|
Assert.That(factionSys.IsMember(dummyEnts[1], NanotrasenFaction), Is.False);
|
||||||
roles = roleSys.MindGetAllRoleInfo(dummyMind);
|
roles = roleSys.MindGetAllRoleInfo(dummyMind);
|
||||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
|
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
|
||||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||||
@@ -146,8 +151,8 @@ public sealed class NukeOpsTest
|
|||||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
||||||
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
|
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
|
||||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
|
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
|
||||||
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
|
Assert.That(factionSys.IsMember(ent, SyndicateFaction), Is.False);
|
||||||
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
|
Assert.That(factionSys.IsMember(ent, NanotrasenFaction), Is.True);
|
||||||
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
|
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
|
||||||
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
|
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Content.Server.Roles;
|
|||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.GameTicking.Components;
|
using Content.Shared.GameTicking.Components;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
|
using Content.Shared.NPC.Prototypes;
|
||||||
using Content.Shared.NPC.Systems;
|
using Content.Shared.NPC.Systems;
|
||||||
using Content.Shared.Objectives.Components;
|
using Content.Shared.Objectives.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -20,6 +21,8 @@ public sealed class TraitorRuleTest
|
|||||||
{
|
{
|
||||||
private const string TraitorGameRuleProtoId = "Traitor";
|
private const string TraitorGameRuleProtoId = "Traitor";
|
||||||
private const string TraitorAntagRoleName = "Traitor";
|
private const string TraitorAntagRoleName = "Traitor";
|
||||||
|
private static readonly ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||||
|
private static readonly ProtoId<NpcFactionPrototype> NanotrasenFaction = "NanoTrasen";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestTraitorObjectives()
|
public async Task TestTraitorObjectives()
|
||||||
@@ -108,8 +111,8 @@ public sealed class TraitorRuleTest
|
|||||||
// Make sure the player is a traitor.
|
// Make sure the player is a traitor.
|
||||||
var mind = mindSys.GetMind(player)!.Value;
|
var mind = mindSys.GetMind(player)!.Value;
|
||||||
Assert.That(roleSys.MindIsAntagonist(mind));
|
Assert.That(roleSys.MindIsAntagonist(mind));
|
||||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
Assert.That(factionSys.IsMember(player, SyndicateFaction), Is.True);
|
||||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
Assert.That(factionSys.IsMember(player, NanotrasenFaction), Is.False);
|
||||||
Assert.That(traitorRule.TotalTraitors, Is.EqualTo(1));
|
Assert.That(traitorRule.TotalTraitors, Is.EqualTo(1));
|
||||||
Assert.That(traitorRule.TraitorMinds[0], Is.EqualTo(mind));
|
Assert.That(traitorRule.TraitorMinds[0], Is.EqualTo(mind));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Localization;
|
||||||
|
|
||||||
|
public sealed class EntityPrototypeLocalizationTest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An explanation of why LocIds should not be used for entity prototype names/descriptions.
|
||||||
|
/// Appended to the error message when the test is failed.
|
||||||
|
/// </summary>
|
||||||
|
private const string NoLocIdExplanation = "Entity prototypes should not use LocIds for names/descriptions, as localization IDs are automated for entity prototypes. See https://docs.spacestation14.com/en/ss14-by-example/fluent-and-localization.html#localizing-prototypes for more information.";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks that no entity prototypes have a LocId as their name or description.
|
||||||
|
/// See <see href="https://docs.spacestation14.com/en/ss14-by-example/fluent-and-localization.html#localizing-prototypes"/> for why this is important.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task TestNoManualEntityLocStrings()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient();
|
||||||
|
var server = pair.Server;
|
||||||
|
var protoMan = server.ProtoMan;
|
||||||
|
var locMan = server.ResolveDependency<ILocalizationManager>();
|
||||||
|
|
||||||
|
var protos = protoMan.EnumeratePrototypes<EntityPrototype>();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
foreach (var proto in protos)
|
||||||
|
{
|
||||||
|
// Check name
|
||||||
|
if (!string.IsNullOrEmpty(proto.SetName))
|
||||||
|
{
|
||||||
|
Assert.That(locMan.HasString(proto.SetName), Is.False,
|
||||||
|
$"Entity prototype {proto.ID} has a LocId ({proto.SetName}) as a name. {NoLocIdExplanation}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check description
|
||||||
|
if (!string.IsNullOrEmpty(proto.SetDesc))
|
||||||
|
{
|
||||||
|
Assert.That(locMan.HasString(proto.SetDesc), Is.False,
|
||||||
|
$"Entity prototype {proto.ID} has a LocId ({proto.SetDesc}) as a description. {NoLocIdExplanation}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,8 +48,6 @@ namespace Content.IntegrationTests.Tests
|
|||||||
{
|
{
|
||||||
"/Maps/centcomm.yml",
|
"/Maps/centcomm.yml",
|
||||||
"/Maps/bagel.yml", // Contains mime's rubber stamp --> Either fix this, remove the category, or remove this comment if intentional.
|
"/Maps/bagel.yml", // Contains mime's rubber stamp --> Either fix this, remove the category, or remove this comment if intentional.
|
||||||
"/Maps/gate.yml", // Contains positronic brain and LSE-1200c "Perforator"
|
|
||||||
"/Maps/meta.yml", // Contains warden's rubber stamp
|
|
||||||
"/Maps/reach.yml", // Contains handheld crew monitor
|
"/Maps/reach.yml", // Contains handheld crew monitor
|
||||||
"/Maps/Shuttles/ShuttleEvent/cruiser.yml", // Contains LSE-1200c "Perforator"
|
"/Maps/Shuttles/ShuttleEvent/cruiser.yml", // Contains LSE-1200c "Perforator"
|
||||||
"/Maps/Shuttles/ShuttleEvent/honki.yml", // Contains golden honker, clown's rubber stamp
|
"/Maps/Shuttles/ShuttleEvent/honki.yml", // Contains golden honker, clown's rubber stamp
|
||||||
@@ -62,27 +60,20 @@ namespace Content.IntegrationTests.Tests
|
|||||||
"Dev",
|
"Dev",
|
||||||
"TestTeg",
|
"TestTeg",
|
||||||
"Fland",
|
"Fland",
|
||||||
"Meta",
|
|
||||||
"Packed",
|
"Packed",
|
||||||
"Omega",
|
|
||||||
"Bagel",
|
"Bagel",
|
||||||
"CentComm",
|
"CentComm",
|
||||||
"Box",
|
"Box",
|
||||||
"Core",
|
|
||||||
"Marathon",
|
"Marathon",
|
||||||
"MeteorArena",
|
"MeteorArena",
|
||||||
"Saltern",
|
"Saltern",
|
||||||
"Reach",
|
"Reach",
|
||||||
"Train",
|
|
||||||
"Oasis",
|
"Oasis",
|
||||||
"Gate",
|
|
||||||
"Amber",
|
"Amber",
|
||||||
"Loop",
|
|
||||||
"Plasma",
|
"Plasma",
|
||||||
"Elkridge",
|
"Elkridge",
|
||||||
"Convex",
|
|
||||||
"Relic",
|
"Relic",
|
||||||
"dm01-entryway"
|
"dm01-entryway",
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ namespace Content.Server.Administration.Commands;
|
|||||||
public sealed class PersistenceSave : LocalizedEntityCommands
|
public sealed class PersistenceSave : LocalizedEntityCommands
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
|
||||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||||
|
[Dependency] private readonly MapLoaderSystem _mapLoader = default!;
|
||||||
|
|
||||||
public override string Command => "persistencesave";
|
public override string Command => "persistencesave";
|
||||||
public override string Description => "Saves server data to a persistence file to be loaded later.";
|
|
||||||
public override string Help => "persistencesave [mapId] [filePath - default: game.map (CCVar) ]";
|
|
||||||
|
|
||||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
@@ -47,8 +45,7 @@ public sealed class PersistenceSave : LocalizedEntityCommands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapLoader = _system.GetEntitySystem<MapLoaderSystem>();
|
_mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath));
|
||||||
mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath));
|
|
||||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
{
|
{
|
||||||
Text = pinballName,
|
Text = pinballName,
|
||||||
Category = VerbCategory.Smite,
|
Category = VerbCategory.Smite,
|
||||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "basketball"),
|
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/Balls/basketball.rsi"), "icon"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
var xform = Transform(args.Target);
|
var xform = Transform(args.Target);
|
||||||
@@ -685,7 +685,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
{
|
{
|
||||||
Text = reptilianName,
|
Text = reptilianName,
|
||||||
Category = VerbCategory.Smite,
|
Category = VerbCategory.Smite,
|
||||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"),
|
Icon = new SpriteSpecifier.Rsi(new ("Textures/Objects/Fun/Plushies/lizard.rsi"), "icon"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
|
_polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
|
||||||
|
|||||||
@@ -38,18 +38,21 @@ public sealed class SolutionCommand : ToolshedCommand
|
|||||||
public SolutionRef AdjReagent(
|
public SolutionRef AdjReagent(
|
||||||
[PipedArgument] SolutionRef input,
|
[PipedArgument] SolutionRef input,
|
||||||
ProtoId<ReagentPrototype> proto,
|
ProtoId<ReagentPrototype> proto,
|
||||||
FixedPoint2 amount
|
float amount
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
|
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
|
||||||
|
|
||||||
if (amount > 0)
|
// Convert float to FixedPoint2
|
||||||
|
var amountFixed = FixedPoint2.New(amount);
|
||||||
|
|
||||||
|
if (amountFixed > 0)
|
||||||
{
|
{
|
||||||
_solutionContainer.TryAddReagent(input.Solution, proto, amount, out _);
|
_solutionContainer.TryAddReagent(input.Solution, proto, amountFixed, out _);
|
||||||
}
|
}
|
||||||
else if (amount < 0)
|
else if (amountFixed < 0)
|
||||||
{
|
{
|
||||||
_solutionContainer.RemoveReagent(input.Solution, proto, -amount);
|
_solutionContainer.RemoveReagent(input.Solution, proto, -amountFixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
@@ -59,7 +62,7 @@ public sealed class SolutionCommand : ToolshedCommand
|
|||||||
public IEnumerable<SolutionRef> AdjReagent(
|
public IEnumerable<SolutionRef> AdjReagent(
|
||||||
[PipedArgument] IEnumerable<SolutionRef> input,
|
[PipedArgument] IEnumerable<SolutionRef> input,
|
||||||
ProtoId<ReagentPrototype> name,
|
ProtoId<ReagentPrototype> name,
|
||||||
FixedPoint2 amount
|
float amount
|
||||||
)
|
)
|
||||||
=> input.Select(x => AdjReagent(x, name, amount));
|
=> input.Select(x => AdjReagent(x, name, amount));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public sealed partial class AtmosPipeLayersSystem : SharedAtmosPipeLayersSystem
|
|||||||
if (ent.Comp.PipeLayersLocked)
|
if (ent.Comp.PipeLayersLocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
base.SetPipeLayer(ent, layer);
|
base.SetPipeLayer(ent, layer, user, used);
|
||||||
|
|
||||||
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
|
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using Content.Server.Actions;
|
|
||||||
using Content.Server.Bed.Components;
|
using Content.Server.Bed.Components;
|
||||||
using Content.Server.Body.Systems;
|
|
||||||
using Content.Server.Power.EntitySystems;
|
using Content.Server.Power.EntitySystems;
|
||||||
using Content.Shared.Bed;
|
using Content.Shared.Bed;
|
||||||
using Content.Shared.Bed.Components;
|
using Content.Shared.Bed.Components;
|
||||||
using Content.Shared.Bed.Sleep;
|
using Content.Shared.Bed.Sleep;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Buckle.Components;
|
using Content.Shared.Buckle.Components;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Emag.Systems;
|
using Content.Shared.Emag.Systems;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Shared.EntityEffects.Effects;
|
|
||||||
using Content.Server.Fluids.EntitySystems;
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
@@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent;
|
|||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.Drunk;
|
using Content.Shared.Drunk;
|
||||||
|
using Content.Shared.EntityEffects.Effects;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Forensics;
|
using Content.Shared.Forensics;
|
||||||
using Content.Shared.Forensics.Components;
|
using Content.Shared.Forensics.Components;
|
||||||
@@ -247,18 +248,30 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
|
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
|
||||||
{
|
{
|
||||||
// Shows profusely bleeding at half the max bleed rate.
|
// Shows massively bleeding at 0.75x the max bleed rate.
|
||||||
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount / 2)
|
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.75f)
|
||||||
{
|
{
|
||||||
args.Message.PushNewline();
|
args.Message.PushNewline();
|
||||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", ent.Owner)));
|
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-massive-bleeding", ("target", ent.Owner)));
|
||||||
}
|
}
|
||||||
// Shows bleeding message when bleeding, but less than profusely.
|
// Shows bleeding message when bleeding above half the max rate, but less than massively.
|
||||||
else if (ent.Comp.BleedAmount > 0)
|
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.5f)
|
||||||
|
{
|
||||||
|
args.Message.PushNewline();
|
||||||
|
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-strong-bleeding", ("target", ent.Owner)));
|
||||||
|
}
|
||||||
|
// Shows bleeding message when bleeding above 0.25x the max rate, but less than half the max.
|
||||||
|
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.25f)
|
||||||
{
|
{
|
||||||
args.Message.PushNewline();
|
args.Message.PushNewline();
|
||||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
|
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
|
||||||
}
|
}
|
||||||
|
// Shows bleeding message when bleeding below 0.25x the max cap
|
||||||
|
else if (ent.Comp.BleedAmount > 0)
|
||||||
|
{
|
||||||
|
args.Message.PushNewline();
|
||||||
|
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-slight-bleeding", ("target", ent.Owner)));
|
||||||
|
}
|
||||||
|
|
||||||
// If the mob's blood level is below the damage threshhold, the pale message is added.
|
// If the mob's blood level is below the damage threshhold, the pale message is added.
|
||||||
if (GetBloodLevelPercentage(ent, ent) < ent.Comp.BloodlossThreshold)
|
if (GetBloodLevelPercentage(ent, ent) < ent.Comp.BloodlossThreshold)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
using System.Numerics;
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Ghost;
|
using Content.Server.Ghost;
|
||||||
using Content.Server.Humanoid;
|
using Content.Server.Humanoid;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
using Content.Shared.Body.Systems;
|
using Content.Shared.Body.Systems;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
using Content.Shared.Humanoid;
|
using Content.Shared.Humanoid;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
@@ -11,8 +14,6 @@ using Content.Shared.Movement.Events;
|
|||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Numerics;
|
|
||||||
using Content.Shared.Damage.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems;
|
namespace Content.Server.Body.Systems;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Body.Organ;
|
using Content.Shared.Body.Organ;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
@@ -231,29 +232,4 @@ namespace Content.Server.Body.Systems
|
|||||||
_solutionContainerSystem.UpdateChemicals(soln.Value);
|
_solutionContainerSystem.UpdateChemicals(soln.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO REFACTOR THIS
|
|
||||||
// This will cause rates to slowly drift over time due to floating point errors.
|
|
||||||
// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
|
|
||||||
[ByRefEvent]
|
|
||||||
public readonly record struct ApplyMetabolicMultiplierEvent(
|
|
||||||
EntityUid Uid,
|
|
||||||
float Multiplier,
|
|
||||||
bool Apply)
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The entity whose metabolism is being modified.
|
|
||||||
/// </summary>
|
|
||||||
public readonly EntityUid Uid = Uid;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What the metabolism's update rate will be multiplied by.
|
|
||||||
/// </summary>
|
|
||||||
public readonly float Multiplier = Multiplier;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If true, apply the multiplier. If false, revert it.
|
|
||||||
/// </summary>
|
|
||||||
public readonly bool Apply = Apply;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@ using Content.Server.Atmos.EntitySystems;
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.EntityEffects;
|
using Content.Server.EntityEffects;
|
||||||
using Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
using Content.Shared.EntityEffects.Effects;
|
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Body.Prototypes;
|
using Content.Shared.Body.Prototypes;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.EffectConditions;
|
||||||
|
using Content.Shared.EntityEffects.Effects;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ namespace Content.Server.Cargo.Systems
|
|||||||
{
|
{
|
||||||
OnInteractUsingCash(uid, component, ref args);
|
OnInteractUsingCash(uid, component, ref args);
|
||||||
}
|
}
|
||||||
else if (TryComp<CargoSlipComponent>(args.Used, out var slip) && !component.SlipPrinter)
|
else if (TryComp<CargoSlipComponent>(args.Used, out var slip) && component.Mode == CargoOrderConsoleMode.DirectOrder)
|
||||||
{
|
{
|
||||||
OnInteractUsingSlip((uid, component), ref args, slip);
|
OnInteractUsingSlip((uid, component), ref args, slip);
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ namespace Content.Server.Cargo.Systems
|
|||||||
if (args.Actor is not { Valid: true } player)
|
if (args.Actor is not { Valid: true } player)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.SlipPrinter)
|
if (component.Mode != CargoOrderConsoleMode.DirectOrder)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_accessReaderSystem.IsAllowed(player, uid))
|
if (!_accessReaderSystem.IsAllowed(player, uid))
|
||||||
@@ -181,7 +181,7 @@ namespace Content.Server.Cargo.Systems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var amount = GetOutstandingOrderCount(orderDatabase, order.Account);
|
var amount = GetOutstandingOrderCount((station.Value, orderDatabase), order.Account);
|
||||||
var capacity = orderDatabase.Capacity;
|
var capacity = orderDatabase.Capacity;
|
||||||
|
|
||||||
// Too many orders, avoid them getting spammed in the UI.
|
// Too many orders, avoid them getting spammed in the UI.
|
||||||
@@ -312,7 +312,7 @@ namespace Content.Server.Cargo.Systems
|
|||||||
{
|
{
|
||||||
var station = _station.GetOwningStation(uid);
|
var station = _station.GetOwningStation(uid);
|
||||||
|
|
||||||
if (component.SlipPrinter)
|
if (component.Mode != CargoOrderConsoleMode.DirectOrder)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!TryGetOrderDatabase(station, out var orderDatabase))
|
if (!TryGetOrderDatabase(station, out var orderDatabase))
|
||||||
@@ -367,6 +367,9 @@ namespace Content.Server.Cargo.Systems
|
|||||||
if (!TryGetOrderDatabase(stationUid, out var orderDatabase))
|
if (!TryGetOrderDatabase(stationUid, out var orderDatabase))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<StationBankAccountComponent>(stationUid, out var bank))
|
||||||
|
return;
|
||||||
|
|
||||||
if (!_protoMan.TryIndex<CargoProductPrototype>(args.CargoProductId, out var product))
|
if (!_protoMan.TryIndex<CargoProductPrototype>(args.CargoProductId, out var product))
|
||||||
{
|
{
|
||||||
Log.Error($"Tried to add invalid cargo product {args.CargoProductId} as order!");
|
Log.Error($"Tried to add invalid cargo product {args.CargoProductId} as order!");
|
||||||
@@ -376,15 +379,17 @@ namespace Content.Server.Cargo.Systems
|
|||||||
if (!GetAvailableProducts((uid, component)).Contains(args.CargoProductId))
|
if (!GetAvailableProducts((uid, component)).Contains(args.CargoProductId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.SlipPrinter)
|
if (component.Mode == CargoOrderConsoleMode.PrintSlip)
|
||||||
{
|
{
|
||||||
OnAddOrderMessageSlipPrinter(uid, component, args, product);
|
OnAddOrderMessageSlipPrinter(uid, component, args, product);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var targetAccount = component.Mode == CargoOrderConsoleMode.SendToPrimary ? bank.PrimaryAccount : component.Account;
|
||||||
|
|
||||||
var data = GetOrderData(args, product, GenerateOrderId(orderDatabase), component.Account);
|
var data = GetOrderData(args, product, GenerateOrderId(orderDatabase), component.Account);
|
||||||
|
|
||||||
if (!TryAddOrder(stationUid.Value, component.Account, data, orderDatabase))
|
if (!TryAddOrder(stationUid.Value, targetAccount, data, orderDatabase))
|
||||||
{
|
{
|
||||||
PlayDenySound(uid, component);
|
PlayDenySound(uid, component);
|
||||||
return;
|
return;
|
||||||
@@ -419,15 +424,33 @@ namespace Content.Server.Cargo.Systems
|
|||||||
CargoConsoleUiKey.Orders,
|
CargoConsoleUiKey.Orders,
|
||||||
new CargoConsoleInterfaceState(
|
new CargoConsoleInterfaceState(
|
||||||
MetaData(station.Value).EntityName,
|
MetaData(station.Value).EntityName,
|
||||||
GetOutstandingOrderCount(orderDatabase, console.Account),
|
GetOutstandingOrderCount((station!.Value, orderDatabase), console.Account),
|
||||||
orderDatabase.Capacity,
|
orderDatabase.Capacity,
|
||||||
GetNetEntity(station.Value),
|
GetNetEntity(station.Value),
|
||||||
orderDatabase.Orders[console.Account],
|
RelevantOrders((station!.Value, orderDatabase), (consoleUid, console)),
|
||||||
GetAvailableProducts((consoleUid, console))
|
GetAvailableProducts((consoleUid, console))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets orders relevant to this account, i.e. orders on the account directly or orders on behalf of the account in the primary account.
|
||||||
|
/// </summary>
|
||||||
|
private List<CargoOrderData> RelevantOrders(Entity<StationCargoOrderDatabaseComponent> station, Entity<CargoOrderConsoleComponent> console)
|
||||||
|
{
|
||||||
|
if (!TryComp<StationBankAccountComponent>(station, out var bank))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var ourOrders = station.Comp.Orders[console.Comp.Account];
|
||||||
|
|
||||||
|
if (console.Comp.Account == bank.PrimaryAccount)
|
||||||
|
return ourOrders;
|
||||||
|
|
||||||
|
var otherOrders = station.Comp.Orders[bank.PrimaryAccount].Where(order => order.Account == console.Comp.Account);
|
||||||
|
|
||||||
|
return ourOrders.Concat(otherOrders).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private void ConsolePopup(EntityUid actor, string text)
|
private void ConsolePopup(EntityUid actor, string text)
|
||||||
{
|
{
|
||||||
_popup.PopupCursor(text, actor);
|
_popup.PopupCursor(text, actor);
|
||||||
@@ -447,17 +470,32 @@ namespace Content.Server.Cargo.Systems
|
|||||||
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason, account);
|
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component, ProtoId<CargoAccountPrototype> account)
|
public int GetOutstandingOrderCount(Entity<StationCargoOrderDatabaseComponent> station, ProtoId<CargoAccountPrototype> account)
|
||||||
{
|
{
|
||||||
var amount = 0;
|
var amount = 0;
|
||||||
|
|
||||||
foreach (var order in component.Orders[account])
|
if (!TryComp<StationBankAccountComponent>(station, out var bank))
|
||||||
|
return amount;
|
||||||
|
|
||||||
|
foreach (var order in station.Comp.Orders[account])
|
||||||
{
|
{
|
||||||
if (!order.Approved)
|
if (!order.Approved)
|
||||||
continue;
|
continue;
|
||||||
amount += order.OrderQuantity - order.NumDispatched;
|
amount += order.OrderQuantity - order.NumDispatched;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (account == bank.PrimaryAccount)
|
||||||
|
return amount;
|
||||||
|
|
||||||
|
foreach (var order in station.Comp.Orders[bank.PrimaryAccount])
|
||||||
|
{
|
||||||
|
if (order.Account != account)
|
||||||
|
continue;
|
||||||
|
if (!order.Approved)
|
||||||
|
continue;
|
||||||
|
amount += order.OrderQuantity - order.NumDispatched;
|
||||||
|
}
|
||||||
|
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,17 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Decals;
|
using Content.Server.Decals;
|
||||||
using Content.Server.Nutrition.EntitySystems;
|
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Crayon;
|
using Content.Shared.Crayon;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Decals;
|
using Content.Shared.Decals;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Crayon;
|
namespace Content.Server.Crayon;
|
||||||
|
|||||||
@@ -5,29 +5,31 @@ using Content.Server.DeviceNetwork.Systems;
|
|||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Tools;
|
using Content.Server.Tools;
|
||||||
using Content.Shared.UserInterface;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.DeviceNetwork;
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Content.Shared.DeviceNetwork.Components;
|
||||||
using Content.Shared.DeviceNetwork.Events;
|
using Content.Shared.DeviceNetwork.Events;
|
||||||
using Content.Shared.Emag.Systems;
|
using Content.Shared.Emag.Systems;
|
||||||
using Content.Shared.Fax;
|
using Content.Shared.Fax;
|
||||||
using Content.Shared.Fax.Systems;
|
|
||||||
using Content.Shared.Fax.Components;
|
using Content.Shared.Fax.Components;
|
||||||
|
using Content.Shared.Fax.Systems;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Labels.Components;
|
using Content.Shared.Labels.Components;
|
||||||
using Content.Shared.Labels.EntitySystems;
|
using Content.Shared.Labels.EntitySystems;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
|
using Content.Shared.NameModifier.Components;
|
||||||
using Content.Shared.Paper;
|
using Content.Shared.Paper;
|
||||||
|
using Content.Shared.Power;
|
||||||
|
using Content.Shared.Tools;
|
||||||
|
using Content.Shared.UserInterface;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Content.Shared.NameModifier.Components;
|
using Robust.Shared.Prototypes;
|
||||||
using Content.Shared.Power;
|
|
||||||
using Content.Shared.DeviceNetwork.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Fax;
|
namespace Content.Server.Fax;
|
||||||
|
|
||||||
@@ -50,6 +52,8 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
||||||
[Dependency] private readonly EmagSystem _emag = default!;
|
[Dependency] private readonly EmagSystem _emag = default!;
|
||||||
|
|
||||||
|
private static readonly ProtoId<ToolQualityPrototype> ScrewingQuality = "Screwing";
|
||||||
|
|
||||||
private const string PaperSlotId = "Paper";
|
private const string PaperSlotId = "Paper";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -209,7 +213,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
if (args.Handled ||
|
if (args.Handled ||
|
||||||
!TryComp<ActorComponent>(args.User, out var actor) ||
|
!TryComp<ActorComponent>(args.User, out var actor) ||
|
||||||
!_toolSystem.HasQuality(args.Used, "Screwing")) // Screwing because Pulsing already used by device linking
|
!_toolSystem.HasQuality(args.Used, ScrewingQuality)) // Screwing because Pulsing already used by device linking
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_quickDialog.OpenDialog(actor.PlayerSession,
|
_quickDialog.OpenDialog(actor.PlayerSession,
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
using System.Linq;
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Interaction;
|
using Content.Server.Interaction;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Stunnable;
|
using Content.Server.Stunnable;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Instruments;
|
using Content.Shared.Instruments;
|
||||||
using Content.Shared.Instruments.UI;
|
using Content.Shared.Instruments.UI;
|
||||||
@@ -17,6 +21,7 @@ using Robust.Shared.Console;
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Instruments;
|
namespace Content.Server.Instruments;
|
||||||
|
|
||||||
@@ -31,6 +36,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
|||||||
[Dependency] private readonly PopupSystem _popup = default!;
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
[Dependency] private readonly TransformSystem _transform = default!;
|
[Dependency] private readonly TransformSystem _transform = default!;
|
||||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||||
|
[Dependency] private readonly IAdminLogManager _admingLogSystem = default!;
|
||||||
|
|
||||||
private const float MaxInstrumentBandRange = 10f;
|
private const float MaxInstrumentBandRange = 10f;
|
||||||
|
|
||||||
@@ -50,6 +56,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
|||||||
SubscribeNetworkEvent<InstrumentStopMidiEvent>(OnMidiStop);
|
SubscribeNetworkEvent<InstrumentStopMidiEvent>(OnMidiStop);
|
||||||
SubscribeNetworkEvent<InstrumentSetMasterEvent>(OnMidiSetMaster);
|
SubscribeNetworkEvent<InstrumentSetMasterEvent>(OnMidiSetMaster);
|
||||||
SubscribeNetworkEvent<InstrumentSetFilteredChannelEvent>(OnMidiSetFilteredChannel);
|
SubscribeNetworkEvent<InstrumentSetFilteredChannelEvent>(OnMidiSetFilteredChannel);
|
||||||
|
SubscribeNetworkEvent<InstrumentSetChannelsEvent>(OnMidiSetChannels);
|
||||||
|
|
||||||
Subs.BuiEvents<InstrumentComponent>(InstrumentUiKey.Key, subs =>
|
Subs.BuiEvents<InstrumentComponent>(InstrumentUiKey.Key, subs =>
|
||||||
{
|
{
|
||||||
@@ -132,6 +139,44 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
|||||||
Clean(uid, instrument);
|
Clean(uid, instrument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnMidiSetChannels(InstrumentSetChannelsEvent msg, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
var uid = GetEntity(msg.Uid);
|
||||||
|
|
||||||
|
if (!TryComp(uid, out InstrumentComponent? instrument) || !TryComp(uid, out ActiveInstrumentComponent? activeInstrument))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (msg.Tracks.Length > RobustMidiEvent.MaxChannels)
|
||||||
|
{
|
||||||
|
Log.Warning($"{args.SenderSession.UserId.ToString()} - Tried to send tracks over the limit! Received: {msg.Tracks.Length}; Limit: {RobustMidiEvent.MaxChannels}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tracksString = string.Join("\n",
|
||||||
|
msg.Tracks
|
||||||
|
.Where(t => t != null)
|
||||||
|
.Select(t => t!.ToString()));
|
||||||
|
|
||||||
|
_admingLogSystem.Add(
|
||||||
|
LogType.Instrument,
|
||||||
|
LogImpact.Low,
|
||||||
|
$"{ToPrettyString(args.SenderSession.AttachedEntity)} set the midi channels for {ToPrettyString(uid)} to {tracksString}");
|
||||||
|
|
||||||
|
// Truncate any track names too long.
|
||||||
|
foreach (var t in msg.Tracks)
|
||||||
|
{
|
||||||
|
t?.TruncateFields(_cfg.GetCVar(CCVars.MidiMaxChannelNameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
activeInstrument.Tracks = msg.Tracks;
|
||||||
|
|
||||||
|
Dirty(uid, activeInstrument);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMidiSetMaster(InstrumentSetMasterEvent msg, EntitySessionEventArgs args)
|
private void OnMidiSetMaster(InstrumentSetMasterEvent msg, EntitySessionEventArgs args)
|
||||||
{
|
{
|
||||||
var uid = GetEntity(msg.Uid);
|
var uid = GetEntity(msg.Uid);
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Kitchen.Components;
|
using Content.Server.Kitchen.Components;
|
||||||
using Content.Server.Nutrition.EntitySystems;
|
|
||||||
using Content.Shared.Body.Components;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Nutrition.Components;
|
|
||||||
using Content.Shared.Popups;
|
|
||||||
using Content.Shared.Storage;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Content.Shared.Destructible;
|
using Content.Shared.Destructible;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Hands.Components;
|
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
|
using Content.Shared.Nutrition.Components;
|
||||||
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Storage;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
using Robust.Server.Containers;
|
using Robust.Server.Containers;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Mech.Components;
|
using Content.Server.Mech.Components;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Power.EntitySystems;
|
using Content.Server.Power.EntitySystems;
|
||||||
@@ -13,16 +14,17 @@ using Content.Shared.Mech.Components;
|
|||||||
using Content.Shared.Mech.EntitySystems;
|
using Content.Shared.Mech.EntitySystems;
|
||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Tools;
|
||||||
using Content.Shared.Tools.Components;
|
using Content.Shared.Tools.Components;
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Content.Shared.Wires;
|
|
||||||
using Content.Server.Body.Systems;
|
|
||||||
using Content.Shared.Tools.Systems;
|
using Content.Shared.Tools.Systems;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
|
using Content.Shared.Wires;
|
||||||
using Robust.Server.Containers;
|
using Robust.Server.Containers;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Content.Shared.Whitelist;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Mech.Systems;
|
namespace Content.Server.Mech.Systems;
|
||||||
|
|
||||||
@@ -40,6 +42,8 @@ public sealed partial class MechSystem : SharedMechSystem
|
|||||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||||
|
|
||||||
|
private static readonly ProtoId<ToolQualityPrototype> PryingQuality = "Prying";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -91,7 +95,7 @@ public sealed partial class MechSystem : SharedMechSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_toolSystem.HasQuality(args.Used, "Prying") && component.BatterySlot.ContainedEntity != null)
|
if (_toolSystem.HasQuality(args.Used, PryingQuality) && component.BatterySlot.ContainedEntity != null)
|
||||||
{
|
{
|
||||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.BatteryRemovalDelay,
|
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.BatteryRemovalDelay,
|
||||||
new RemoveBatteryEvent(), uid, target: uid, used: args.Target)
|
new RemoveBatteryEvent(), uid, target: uid, used: args.Target)
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ using Content.Server.NodeContainer.EntitySystems;
|
|||||||
using Content.Server.NodeContainer.NodeGroups;
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
using Content.Server.NodeContainer.Nodes;
|
using Content.Server.NodeContainer.Nodes;
|
||||||
using Content.Server.Temperature.Components;
|
using Content.Server.Temperature.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.UserInterface;
|
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Climbing.Systems;
|
using Content.Shared.Climbing.Systems;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
@@ -26,11 +25,14 @@ using Content.Shared.Interaction;
|
|||||||
using Content.Shared.Medical.Cryogenics;
|
using Content.Shared.Medical.Cryogenics;
|
||||||
using Content.Shared.MedicalScanner;
|
using Content.Shared.MedicalScanner;
|
||||||
using Content.Shared.Power;
|
using Content.Shared.Power;
|
||||||
|
using Content.Shared.Tools;
|
||||||
|
using Content.Shared.Tools.Systems;
|
||||||
|
using Content.Shared.UserInterface;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
|
|
||||||
|
|
||||||
namespace Content.Server.Medical;
|
namespace Content.Server.Medical;
|
||||||
|
|
||||||
@@ -51,6 +53,8 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
|
|||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||||
|
|
||||||
|
private static readonly ProtoId<ToolQualityPrototype> PryingQuality = "Prying";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -211,7 +215,7 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
|
|||||||
if (args.Handled || !entity.Comp.Locked || entity.Comp.BodyContainer.ContainedEntity == null)
|
if (args.Handled || !entity.Comp.Locked || entity.Comp.BodyContainer.ContainedEntity == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
args.Handled = _toolSystem.UseTool(args.Used, args.User, entity.Owner, entity.Comp.PryDelay, "Prying", new CryoPodPryFinished());
|
args.Handled = _toolSystem.UseTool(args.Used, args.User, entity.Owner, entity.Comp.PryDelay, PryingQuality, new CryoPodPryFinished());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnExamined(Entity<CryoPodComponent> entity, ref ExaminedEvent args)
|
private void OnExamined(Entity<CryoPodComponent> entity, ref ExaminedEvent args)
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ using Content.Server.Fluids.EntitySystems;
|
|||||||
using Content.Server.Forensics;
|
using Content.Server.Forensics;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Stunnable;
|
using Content.Server.Stunnable;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Shared.Access.Components;
|
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
@@ -60,4 +59,12 @@ public sealed class RenameCommand : LocalizedEntityCommands
|
|||||||
entityUid = EntityUid.Invalid;
|
entityUid = EntityUid.Invalid;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
return CompletionResult.FromOptions(CompletionHelper.SessionNames());
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.Components;
|
namespace Content.Server.Nutrition.Components;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.Components;
|
namespace Content.Server.Nutrition.Components;
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,25 @@
|
|||||||
using Content.Server.Body.Components;
|
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Shared.EntityEffects.Effects;
|
|
||||||
using Content.Server.Fluids.EntitySystems;
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Server.Forensics;
|
using Content.Server.Forensics;
|
||||||
using Content.Server.Inventory;
|
using Content.Server.Inventory;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.EntityEffects.Effects;
|
||||||
using Content.Shared.EntityEffects;
|
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Hands.EntitySystems;
|
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Mobs.Systems;
|
|
||||||
using Content.Shared.Nutrition;
|
using Content.Shared.Nutrition;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
@@ -36,19 +31,15 @@ namespace Content.Server.Nutrition.EntitySystems;
|
|||||||
public sealed class DrinkSystem : SharedDrinkSystem
|
public sealed class DrinkSystem : SharedDrinkSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly BodySystem _body = default!;
|
[Dependency] private readonly BodySystem _body = default!;
|
||||||
[Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
|
|
||||||
[Dependency] private readonly FoodSystem _food = default!;
|
[Dependency] private readonly FoodSystem _food = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
|
||||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||||
[Dependency] private readonly PopupSystem _popup = default!;
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||||
[Dependency] private readonly ReactiveSystem _reaction = default!;
|
[Dependency] private readonly ReactiveSystem _reaction = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
|
||||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
|
||||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||||
[Dependency] private readonly StomachSystem _stomach = default!;
|
[Dependency] private readonly StomachSystem _stomach = default!;
|
||||||
@@ -65,7 +56,6 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
|||||||
// run after openable so its always open -> drink
|
// run after openable so its always open -> drink
|
||||||
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse, before: [typeof(ServerInventorySystem)], after: [typeof(OpenableSystem)]);
|
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse, before: [typeof(ServerInventorySystem)], after: [typeof(OpenableSystem)]);
|
||||||
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
|
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
|
||||||
SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
|
|
||||||
SubscribeLocalEvent<DrinkComponent, ConsumeDoAfterEvent>(OnDoAfter);
|
SubscribeLocalEvent<DrinkComponent, ConsumeDoAfterEvent>(OnDoAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,76 +147,6 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
|||||||
_appearance.SetData(uid, FoodVisuals.Visual, drainAvailable.Float(), appearance);
|
_appearance.SetData(uid, FoodVisuals.Visual, drainAvailable.Float(), appearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to feed the drink item to the target entity
|
|
||||||
/// </summary>
|
|
||||||
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
|
|
||||||
{
|
|
||||||
if (!HasComp<BodyComponent>(target))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_openable.IsClosed(item, user))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!_solutionContainer.TryGetSolution(item, drink.Solution, out _, out var drinkSolution) || drinkSolution.Volume <= 0)
|
|
||||||
{
|
|
||||||
if (drink.IgnoreEmpty)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_popup.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", item)), item, user);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_food.IsMouthBlocked(target, user))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!_interaction.InRangeUnobstructed(user, item, popup: true))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var forceDrink = user != target;
|
|
||||||
|
|
||||||
if (forceDrink)
|
|
||||||
{
|
|
||||||
var userName = Identity.Entity(user, EntityManager);
|
|
||||||
|
|
||||||
_popup.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, target);
|
|
||||||
|
|
||||||
// logging
|
|
||||||
_adminLogger.Add(LogType.ForceFeed, LogImpact.High, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// log voluntary drinking
|
|
||||||
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var flavors = _flavorProfile.GetLocalizedFlavorsMessage(user, drinkSolution);
|
|
||||||
|
|
||||||
var doAfterEventArgs = new DoAfterArgs(EntityManager,
|
|
||||||
user,
|
|
||||||
forceDrink ? drink.ForceFeedDelay : drink.Delay,
|
|
||||||
new ConsumeDoAfterEvent(drink.Solution, flavors),
|
|
||||||
eventTarget: item,
|
|
||||||
target: target,
|
|
||||||
used: item)
|
|
||||||
{
|
|
||||||
BreakOnHandChange = false,
|
|
||||||
BreakOnMove = forceDrink,
|
|
||||||
BreakOnDamage = true,
|
|
||||||
MovementThreshold = 0.01f,
|
|
||||||
DistanceThreshold = 1.0f,
|
|
||||||
// do-after will stop if item is dropped when trying to feed someone else
|
|
||||||
// or if the item started out in the user's own hands
|
|
||||||
NeedHand = forceDrink || _hands.IsHolding(user, item),
|
|
||||||
};
|
|
||||||
|
|
||||||
_doAfter.TryStartDoAfter(doAfterEventArgs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised directed at a victim when someone has force fed them a drink.
|
/// Raised directed at a victim when someone has force fed them a drink.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -241,7 +161,7 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
|||||||
if (args.Used is null || !_solutionContainer.TryGetSolution(args.Used.Value, args.Solution, out var soln, out var solution))
|
if (args.Used is null || !_solutionContainer.TryGetSolution(args.Used.Value, args.Solution, out var soln, out var solution))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_openable.IsClosed(args.Used.Value, args.Target.Value))
|
if (_openable.IsClosed(args.Used.Value, args.Target.Value, predicted: true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO this should really be checked every tick.
|
// TODO this should really be checked every tick.
|
||||||
@@ -330,36 +250,4 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
|||||||
if (!forceDrink && solution.Volume > 0)
|
if (!forceDrink && solution.Volume > 0)
|
||||||
args.Repeat = true;
|
args.Repeat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddDrinkVerb(Entity<DrinkComponent> entity, ref GetVerbsEvent<AlternativeVerb> ev)
|
|
||||||
{
|
|
||||||
if (entity.Owner == ev.User ||
|
|
||||||
!ev.CanInteract ||
|
|
||||||
!ev.CanAccess ||
|
|
||||||
!TryComp<BodyComponent>(ev.User, out var body) ||
|
|
||||||
!_body.TryGetBodyOrganEntityComps<StomachComponent>((ev.User, body), out var stomachs))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Make sure the solution exists
|
|
||||||
if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solution))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// no drinking from living drinks, have to kill them first.
|
|
||||||
if (_mobState.IsAlive(entity))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var user = ev.User;
|
|
||||||
AlternativeVerb verb = new()
|
|
||||||
{
|
|
||||||
Act = () =>
|
|
||||||
{
|
|
||||||
TryDrink(user, user, entity.Comp, entity);
|
|
||||||
},
|
|
||||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
|
|
||||||
Text = Loc.GetString("drink-system-verb-drink"),
|
|
||||||
Priority = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
ev.Verbs.Add(verb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
using Content.Server.Atmos;
|
|
||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.DoAfter;
|
using Content.Server.DoAfter;
|
||||||
using Content.Server.Explosion.EntitySystems;
|
using Content.Server.Explosion.EntitySystems;
|
||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Emag.Components;
|
|
||||||
using Content.Shared.Emag.Systems;
|
using Content.Shared.Emag.Systems;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Nutrition;
|
using Content.Shared.Nutrition;
|
||||||
using System.Threading;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
using Content.Shared.Atmos;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// System for vapes
|
/// System for vapes
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
using Content.Shared.Containers.ItemSlots;
|
|
||||||
using Content.Server.Nutrition.Components;
|
|
||||||
using Content.Server.Popups;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Nutrition.Components;
|
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
|
||||||
using Content.Shared.Tools.EntitySystems;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles usage of the utensils on the food items
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class UtensilSystem : SharedUtensilSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
|
||||||
[Dependency] private readonly FoodSystem _foodSystem = default!;
|
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
||||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clicked with utensil
|
|
||||||
/// </summary>
|
|
||||||
private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
|
|
||||||
{
|
|
||||||
if (ev.Handled || ev.Target == null || !ev.CanReach)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
|
|
||||||
ev.Handled = result.Handled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
|
|
||||||
{
|
|
||||||
if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
|
|
||||||
return (false, false);
|
|
||||||
|
|
||||||
//Prevents food usage with a wrong utensil
|
|
||||||
if ((food.Utensil & utensil.Comp.Types) == 0)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
|
|
||||||
return (false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
|
|
||||||
return (false, true);
|
|
||||||
|
|
||||||
return _foodSystem.TryFeed(user, user, target, food);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempt to break the utensil after interaction.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid">Utensil.</param>
|
|
||||||
/// <param name="userUid">User of the utensil.</param>
|
|
||||||
public void TryBreak(EntityUid uid, EntityUid userUid, UtensilComponent? component = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref component))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_robustRandom.Prob(component.BreakChance))
|
|
||||||
{
|
|
||||||
_audio.PlayPvs(component.BreakSound, userUid, AudioParams.Default.WithVolume(-2f));
|
|
||||||
EntityManager.DeleteEntity(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,6 @@ using Content.Shared.Popups;
|
|||||||
using Robust.Server.Audio;
|
using Robust.Server.Audio;
|
||||||
using Robust.Server.Containers;
|
using Robust.Server.Containers;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
using Content.Shared.Security.Components;
|
using Content.Shared.Security.Components;
|
||||||
using Content.Shared.Security.Systems;
|
using Content.Shared.Security.Systems;
|
||||||
|
using Content.Shared.Wall;
|
||||||
|
|
||||||
namespace Content.Server.Security;
|
namespace Content.Server.Security;
|
||||||
|
|
||||||
public sealed class GenpopSystem : SharedGenpopSystem
|
public sealed class GenpopSystem : SharedGenpopSystem
|
||||||
{
|
{
|
||||||
|
private const float GenpopIDEjectDistanceFromWall = 1f;
|
||||||
protected override void CreateId(Entity<GenpopLockerComponent> ent, string name, float sentence, string crime)
|
protected override void CreateId(Entity<GenpopLockerComponent> ent, string name, float sentence, string crime)
|
||||||
{
|
{
|
||||||
|
// Default to prisoner locker coordinates for ID spawn
|
||||||
var xform = Transform(ent);
|
var xform = Transform(ent);
|
||||||
var uid = Spawn(ent.Comp.IdCardProto, xform.Coordinates);
|
var spawnCoordinates = xform.Coordinates;
|
||||||
|
// Offset prisoner wall locker coordinates in wallmount direction for ID spawn; avoids spawning ID inside wall
|
||||||
|
if (TryComp<WallMountComponent>(ent, out var wallMountComponent))
|
||||||
|
{
|
||||||
|
var offset = (wallMountComponent.Direction + xform.LocalRotation - Math.PI / 2).ToVec() * GenpopIDEjectDistanceFromWall;
|
||||||
|
spawnCoordinates = spawnCoordinates.Offset(offset);
|
||||||
|
}
|
||||||
|
var uid = Spawn(ent.Comp.IdCardProto, spawnCoordinates);
|
||||||
ent.Comp.LinkedId = uid;
|
ent.Comp.LinkedId = uid;
|
||||||
IdCard.TryChangeFullName(uid, name);
|
IdCard.TryChangeFullName(uid, name);
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public sealed class CargoGiftsRule : StationEventSystem<CargoGiftsRuleComponent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add some presents
|
// Add some presents
|
||||||
var outstanding = CargoSystem.GetOutstandingOrderCount(cargoDb, component.Account);
|
var outstanding = _cargoSystem.GetOutstandingOrderCount((station.Value, cargoDb), component.Account);
|
||||||
while (outstanding < cargoDb.Capacity - component.OrderSpaceToLeave && component.Gifts.Count > 0)
|
while (outstanding < cargoDb.Capacity - component.OrderSpaceToLeave && component.Gifts.Count > 0)
|
||||||
{
|
{
|
||||||
// I wish there was a nice way to pop this
|
// I wish there was a nice way to pop this
|
||||||
|
|||||||
@@ -227,14 +227,13 @@ namespace Content.Server.VendingMachines
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default spawn coordinates
|
// Default spawn coordinates
|
||||||
var spawnCoordinates = Transform(uid).Coordinates;
|
var xform = Transform(uid);
|
||||||
|
var spawnCoordinates = xform.Coordinates;
|
||||||
|
|
||||||
//Make sure the wallvends spawn outside of the wall.
|
//Make sure the wallvends spawn outside of the wall.
|
||||||
|
|
||||||
if (TryComp<WallMountComponent>(uid, out var wallMountComponent))
|
if (TryComp<WallMountComponent>(uid, out var wallMountComponent))
|
||||||
{
|
{
|
||||||
|
var offset = (wallMountComponent.Direction + xform.LocalRotation - Math.PI / 2).ToVec() * WallVendEjectDistanceFromWall;
|
||||||
var offset = wallMountComponent.Direction.ToWorldVec() * WallVendEjectDistanceFromWall;
|
|
||||||
spawnCoordinates = spawnCoordinates.Offset(offset);
|
spawnCoordinates = spawnCoordinates.Offset(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Content.Shared.Hands.Components;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Power;
|
using Content.Shared.Power;
|
||||||
|
using Content.Shared.Tools;
|
||||||
using Content.Shared.Tools.Components;
|
using Content.Shared.Tools.Components;
|
||||||
using Content.Shared.Wires;
|
using Content.Shared.Wires;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -29,6 +30,9 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ConstructionSystem _construction = default!;
|
[Dependency] private readonly ConstructionSystem _construction = default!;
|
||||||
|
|
||||||
|
private static readonly ProtoId<ToolQualityPrototype> CuttingQuality = "Cutting";
|
||||||
|
private static readonly ProtoId<ToolQualityPrototype> PulsingQuality = "Pulsing";
|
||||||
|
|
||||||
// This is where all the wire layouts are stored.
|
// This is where all the wire layouts are stored.
|
||||||
[ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
|
[ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
|
||||||
|
|
||||||
@@ -443,8 +447,8 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
if (!IsPanelOpen(uid))
|
if (!IsPanelOpen(uid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Tool.HasQuality(args.Used, "Cutting", tool) ||
|
if (Tool.HasQuality(args.Used, CuttingQuality, tool) ||
|
||||||
Tool.HasQuality(args.Used, "Pulsing", tool))
|
Tool.HasQuality(args.Used, PulsingQuality, tool))
|
||||||
{
|
{
|
||||||
if (TryComp(args.User, out ActorComponent? actor))
|
if (TryComp(args.User, out ActorComponent? actor))
|
||||||
{
|
{
|
||||||
@@ -623,7 +627,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case WiresAction.Cut:
|
case WiresAction.Cut:
|
||||||
if (!Tool.HasQuality(toolEntity, "Cutting", tool))
|
if (!Tool.HasQuality(toolEntity, CuttingQuality, tool))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
||||||
return;
|
return;
|
||||||
@@ -637,7 +641,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case WiresAction.Mend:
|
case WiresAction.Mend:
|
||||||
if (!Tool.HasQuality(toolEntity, "Cutting", tool))
|
if (!Tool.HasQuality(toolEntity, CuttingQuality, tool))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
||||||
return;
|
return;
|
||||||
@@ -651,7 +655,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case WiresAction.Pulse:
|
case WiresAction.Pulse:
|
||||||
if (!Tool.HasQuality(toolEntity, "Pulsing", tool))
|
if (!Tool.HasQuality(toolEntity, PulsingQuality, tool))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user);
|
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user);
|
||||||
return;
|
return;
|
||||||
@@ -710,7 +714,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case WiresAction.Cut:
|
case WiresAction.Cut:
|
||||||
if (!Tool.HasQuality(toolEntity, "Cutting", tool))
|
if (!Tool.HasQuality(toolEntity, CuttingQuality, tool))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
||||||
break;
|
break;
|
||||||
@@ -731,7 +735,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
UpdateUserInterface(used);
|
UpdateUserInterface(used);
|
||||||
break;
|
break;
|
||||||
case WiresAction.Mend:
|
case WiresAction.Mend:
|
||||||
if (!Tool.HasQuality(toolEntity, "Cutting", tool))
|
if (!Tool.HasQuality(toolEntity, CuttingQuality, tool))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
|
||||||
break;
|
break;
|
||||||
@@ -752,7 +756,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||||||
UpdateUserInterface(used);
|
UpdateUserInterface(used);
|
||||||
break;
|
break;
|
||||||
case WiresAction.Pulse:
|
case WiresAction.Pulse:
|
||||||
if (!Tool.HasQuality(toolEntity, "Pulsing", tool))
|
if (!Tool.HasQuality(toolEntity, PulsingQuality, tool))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user);
|
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ using Content.Shared.Ghost.Roles.Components;
|
|||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Content.Shared.NPC.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Zombies;
|
namespace Content.Server.Zombies;
|
||||||
|
|
||||||
@@ -66,6 +67,8 @@ public sealed partial class ZombieSystem
|
|||||||
|
|
||||||
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
||||||
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
||||||
|
private static readonly ProtoId<NpcFactionPrototype> ZombieFaction = "Zombie";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles an entity turning into a zombie when they die or go into crit
|
/// Handles an entity turning into a zombie when they die or go into crit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -223,7 +226,7 @@ public sealed partial class ZombieSystem
|
|||||||
_mobState.ChangeMobState(target, MobState.Alive);
|
_mobState.ChangeMobState(target, MobState.Alive);
|
||||||
|
|
||||||
_faction.ClearFactions(target, dirty: false);
|
_faction.ClearFactions(target, dirty: false);
|
||||||
_faction.AddFaction(target, "Zombie");
|
_faction.AddFaction(target, ZombieFaction);
|
||||||
|
|
||||||
//gives it the funny "Zombie ___" name.
|
//gives it the funny "Zombie ___" name.
|
||||||
_nameMod.RefreshNameModifiers(target);
|
_nameMod.RefreshNameModifiers(target);
|
||||||
|
|||||||
@@ -472,5 +472,10 @@ public enum LogType
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Damaging grid collision has occurred.
|
/// Damaging grid collision has occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ShuttleImpact = 102
|
ShuttleImpact = 102,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Events relating to midi playback.
|
||||||
|
/// </summary>
|
||||||
|
Instrument = 103,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,8 +320,7 @@ namespace Content.Shared.Atmos
|
|||||||
/// (The pressure threshold is so low that it doesn't make sense to do any calculations,
|
/// (The pressure threshold is so low that it doesn't make sense to do any calculations,
|
||||||
/// so it just applies this flat value).
|
/// so it just applies this flat value).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// Original value is 4, buff back when we have proper ways for players to deal with breaches.
|
public const int LowPressureDamage = 4;
|
||||||
public const int LowPressureDamage = 1;
|
|
||||||
|
|
||||||
public const float WindowHeatTransferCoefficient = 0.1f;
|
public const float WindowHeatTransferCoefficient = 0.1f;
|
||||||
|
|
||||||
|
|||||||
@@ -119,14 +119,16 @@ public abstract partial class SharedAtmosPipeLayersSystem : EntitySystem
|
|||||||
if (ent.Comp.NumberOfPipeLayers <= 1 || ent.Comp.PipeLayersLocked)
|
if (ent.Comp.NumberOfPipeLayers <= 1 || ent.Comp.PipeLayersLocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<ToolComponent>(args.Used, out var tool) || !_tool.HasQuality(args.Used, ent.Comp.Tool, tool))
|
||||||
|
return;
|
||||||
|
|
||||||
if (TryComp<SubFloorHideComponent>(ent, out var subFloorHide) && subFloorHide.IsUnderCover)
|
if (TryComp<SubFloorHideComponent>(ent, out var subFloorHide) && subFloorHide.IsUnderCover)
|
||||||
{
|
{
|
||||||
_popup.PopupPredicted(Loc.GetString("atmos-pipe-layers-component-cannot-adjust-pipes"), ent, args.User);
|
_popup.PopupClient(Loc.GetString("atmos-pipe-layers-component-cannot-adjust-pipes"), ent, args.User);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp<ToolComponent>(args.Used, out var tool) && _tool.HasQuality(args.Used, ent.Comp.Tool, tool))
|
_tool.UseTool(args.Used, args.User, ent, ent.Comp.Delay, tool.Qualities, new TrySetNextPipeLayerCompletedEvent());
|
||||||
_tool.UseTool(args.Used, args.User, ent, ent.Comp.Delay, tool.Qualities, new TrySetNextPipeLayerCompletedEvent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUseInHandEvent(Entity<AtmosPipeLayersComponent> ent, ref UseInHandEvent args)
|
private void OnUseInHandEvent(Entity<AtmosPipeLayersComponent> ent, ref UseInHandEvent args)
|
||||||
@@ -141,7 +143,7 @@ public abstract partial class SharedAtmosPipeLayersSystem : EntitySystem
|
|||||||
var toolName = Loc.GetString(toolProto.ToolName).ToLower();
|
var toolName = Loc.GetString(toolProto.ToolName).ToLower();
|
||||||
var message = Loc.GetString("atmos-pipe-layers-component-tool-missing", ("toolName", toolName));
|
var message = Loc.GetString("atmos-pipe-layers-component-tool-missing", ("toolName", toolName));
|
||||||
|
|
||||||
_popup.PopupPredicted(message, ent, args.User);
|
_popup.PopupClient(message, ent, args.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -217,7 +219,7 @@ public abstract partial class SharedAtmosPipeLayersSystem : EntitySystem
|
|||||||
var layerName = GetPipeLayerName(ent.Comp.CurrentPipeLayer);
|
var layerName = GetPipeLayerName(ent.Comp.CurrentPipeLayer);
|
||||||
var message = Loc.GetString("atmos-pipe-layers-component-change-layer", ("layerName", layerName));
|
var message = Loc.GetString("atmos-pipe-layers-component-change-layer", ("layerName", layerName));
|
||||||
|
|
||||||
_popup.PopupPredicted(message, ent, user);
|
_popup.PopupClient(message, ent, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Server.Nutrition.EntitySystems;
|
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Body.Components
|
namespace Content.Shared.Body.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
|
[RegisterComponent, NetworkedComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
|
||||||
public sealed partial class StomachComponent : Component
|
public sealed partial class StomachComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -32,7 +33,7 @@ namespace Content.Server.Body.Components
|
|||||||
/// What solution should this stomach push reagents into, on the body?
|
/// What solution should this stomach push reagents into, on the body?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
|
public string BodySolutionName = "chemicals";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time between reagents being ingested and them being
|
/// Time between reagents being ingested and them being
|
||||||
26
Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs
Normal file
26
Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Content.Shared.Body.Events;
|
||||||
|
|
||||||
|
// TODO REFACTOR THIS
|
||||||
|
// This will cause rates to slowly drift over time due to floating point errors.
|
||||||
|
// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct ApplyMetabolicMultiplierEvent(
|
||||||
|
EntityUid Uid,
|
||||||
|
float Multiplier,
|
||||||
|
bool Apply)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The entity whose metabolism is being modified.
|
||||||
|
/// </summary>
|
||||||
|
public readonly EntityUid Uid = Uid;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What the metabolism's update rate will be multiplied by.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float Multiplier = Multiplier;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, apply the multiplier. If false, revert it.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool Apply = Apply;
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Body.Organ;
|
using Content.Shared.Body.Organ;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems
|
namespace Content.Shared.Body.Systems
|
||||||
{
|
{
|
||||||
public sealed class StomachSystem : EntitySystem
|
public sealed class StomachSystem : EntitySystem
|
||||||
{
|
{
|
||||||
@@ -19,6 +21,7 @@ namespace Content.Server.Body.Systems
|
|||||||
{
|
{
|
||||||
SubscribeLocalEvent<StomachComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<StomachComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<StomachComponent, EntityUnpausedEvent>(OnUnpaused);
|
SubscribeLocalEvent<StomachComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
|
SubscribeLocalEvent<StomachComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||||
SubscribeLocalEvent<StomachComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
SubscribeLocalEvent<StomachComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +35,16 @@ namespace Content.Server.Body.Systems
|
|||||||
ent.Comp.NextUpdate += args.PausedTime;
|
ent.Comp.NextUpdate += args.PausedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEntRemoved(Entity<StomachComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
// Make sure the removed entity was our contained solution
|
||||||
|
if (ent.Comp.Solution is not { } solution || args.Entity != solution.Owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Cleared our cached reference to the solution entity
|
||||||
|
ent.Comp.Solution = null;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<StomachComponent, OrganComponent, SolutionContainerManagerComponent>();
|
var query = EntityQueryEnumerator<StomachComponent, OrganComponent, SolutionContainerManagerComponent>();
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Burial;
|
|
||||||
using Content.Shared.Burial.Components;
|
using Content.Shared.Burial.Components;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
@@ -10,7 +9,7 @@ using Content.Shared.Storage.Components;
|
|||||||
using Content.Shared.Storage.EntitySystems;
|
using Content.Shared.Storage.EntitySystems;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
|
|
||||||
namespace Content.Server.Burial.Systems;
|
namespace Content.Shared.Burial;
|
||||||
|
|
||||||
public sealed class BurialSystem : EntitySystem
|
public sealed class BurialSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,4 +15,10 @@ public sealed partial class CCVars
|
|||||||
|
|
||||||
public static readonly CVarDef<int> MaxMidiLaggedBatches =
|
public static readonly CVarDef<int> MaxMidiLaggedBatches =
|
||||||
CVarDef.Create("midi.max_lagged_batches", 8, CVar.SERVERONLY);
|
CVarDef.Create("midi.max_lagged_batches", 8, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the max amount of characters to allow in the "Midi channel selector".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<int> MidiMaxChannelNameLength =
|
||||||
|
CVarDef.Create("midi.max_channel_name_length", 64, CVar.SERVERONLY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public sealed partial class CCVars
|
|||||||
/// some food object won't spam a user with flavors.
|
/// some food object won't spam a user with flavors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<int>
|
public static readonly CVarDef<int>
|
||||||
FlavorLimit = CVarDef.Create("flavor.limit", 10, CVar.SERVERONLY);
|
FlavorLimit = CVarDef.Create("flavor.limit", 10, CVar.SERVER | CVar.REPLICATED);
|
||||||
|
|
||||||
public static readonly CVarDef<string> DestinationFile =
|
public static readonly CVarDef<string> DestinationFile =
|
||||||
CVarDef.Create("autogen.destination_file", "", CVar.SERVER | CVar.SERVERONLY);
|
CVarDef.Create("autogen.destination_file", "", CVar.SERVER | CVar.SERVERONLY);
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ public sealed partial class CargoOrderConsoleComponent : Component
|
|||||||
public static readonly ProtoId<RadioChannelPrototype> BaseAnnouncementChannel = "Supply";
|
public static readonly ProtoId<RadioChannelPrototype> BaseAnnouncementChannel = "Supply";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If set to true, restricts this console from ordering and has it print slips instead
|
/// The behaviour of the cargo console regarding orders
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool SlipPrinter;
|
public CargoOrderConsoleMode Mode = CargoOrderConsoleMode.DirectOrder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the console will be able to print a slip again.
|
/// The time at which the console will be able to print a slip again.
|
||||||
@@ -146,6 +146,26 @@ public sealed partial class CargoOrderConsoleComponent : Component
|
|||||||
public TimeSpan DenySoundDelay = TimeSpan.FromSeconds(2);
|
public TimeSpan DenySoundDelay = TimeSpan.FromSeconds(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviour of the cargo order console
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum CargoOrderConsoleMode : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Place orders directly
|
||||||
|
/// </summary>
|
||||||
|
DirectOrder,
|
||||||
|
/// <summary>
|
||||||
|
/// Print a slip to be inserted into a DirectOrder console
|
||||||
|
/// </summary>
|
||||||
|
PrintSlip,
|
||||||
|
/// <summary>
|
||||||
|
/// Transfers the order to the primary account
|
||||||
|
/// </summary>
|
||||||
|
SendToPrimary,
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Withdraw funds from an account
|
/// Withdraw funds from an account
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ public sealed partial class ClimbSystem : VirtualController
|
|||||||
|
|
||||||
private void AddClimbableVerb(EntityUid uid, ClimbableComponent component, GetVerbsEvent<AlternativeVerb> args)
|
private void AddClimbableVerb(EntityUid uid, ClimbableComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||||
{
|
{
|
||||||
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User))
|
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User) || !component.Vaultable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing || !climbingComponent.CanClimb)
|
if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing || !climbingComponent.CanClimb)
|
||||||
|
|||||||
@@ -17,33 +17,46 @@ namespace Content.Shared.CombatMode.Pacification;
|
|||||||
[Access(typeof(PacificationSystem))]
|
[Access(typeof(PacificationSystem))]
|
||||||
public sealed partial class PacifiedComponent : Component
|
public sealed partial class PacifiedComponent : Component
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If true, this will prevent you from disarming opponents in combat.
|
||||||
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool DisallowDisarm = false;
|
public bool DisallowDisarm = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If true, this will disable combat entirely instead of only disallowing attacking living creatures and harmful things.
|
/// If true, this will disable combat entirely instead of only disallowing attacking living creatures and harmful things.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool DisallowAllCombat = false;
|
public bool DisallowAllCombat = false;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When attempting attack against the same entity multiple times,
|
/// When attempting attack against the same entity multiple times,
|
||||||
/// don't spam popups every frame and instead have a cooldown.
|
/// don't spam popups every frame and instead have a cooldown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public TimeSpan PopupCooldown = TimeSpan.FromSeconds(3.0);
|
public TimeSpan PopupCooldown = TimeSpan.FromSeconds(3.0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time at which the next popup can be shown.
|
||||||
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
[AutoPausedField]
|
[AutoPausedField]
|
||||||
public TimeSpan? NextPopupTime = null;
|
public TimeSpan? NextPopupTime = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The last entity attacked, used for popup purposes (avoid spam)
|
/// The last entity attacked, used for popup purposes (avoid spam)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public EntityUid? LastAttackedEntity = null;
|
public EntityUid? LastAttackedEntity = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The alert to show to owners of this component.
|
||||||
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public ProtoId<AlertPrototype> PacifiedAlert = "Pacified";
|
public ProtoId<AlertPrototype> PacifiedAlert = "Pacified";
|
||||||
|
|
||||||
|
// Prevent cheat clients from using this to identify thieves and players that cannot fight back.
|
||||||
|
// This should not matter for prediction reasons since it only blocks user input.
|
||||||
|
public override bool SendOnlyToOwner => true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.EntityEffects;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.EntityEffects.EffectConditions;
|
namespace Content.Shared.EntityEffects.EffectConditions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Condition for if the entity is or isn't wearing internals.
|
/// Condition for if the entity is or isn't wearing internals.
|
||||||
|
|||||||
@@ -303,15 +303,7 @@ namespace Content.Shared.FixedPoint
|
|||||||
|
|
||||||
public readonly int CompareTo(FixedPoint2 other)
|
public readonly int CompareTo(FixedPoint2 other)
|
||||||
{
|
{
|
||||||
if (other.Value > Value)
|
return Value.CompareTo(other.Value);
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (other.Value < Value)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Server.Ghost.Roles.Raffles;
|
namespace Content.Shared.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines settings for a ghost role raffle.
|
/// Defines settings for a ghost role raffle.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Content.Server.Ghost.Roles.Raffles;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.Ghost.Roles.Raffles;
|
namespace Content.Shared.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ namespace Content.Shared.HealthExaminable;
|
|||||||
public sealed partial class HealthExaminableComponent : Component
|
public sealed partial class HealthExaminableComponent : Component
|
||||||
{
|
{
|
||||||
public List<FixedPoint2> Thresholds = new()
|
public List<FixedPoint2> Thresholds = new()
|
||||||
{ FixedPoint2.New(10), FixedPoint2.New(25), FixedPoint2.New(50), FixedPoint2.New(75) };
|
{ FixedPoint2.New(8), FixedPoint2.New(15), FixedPoint2.New(30), FixedPoint2.New(50), FixedPoint2.New(75), FixedPoint2.New(100), FixedPoint2.New(200) };
|
||||||
|
|
||||||
[DataField(required: true)]
|
[DataField(required: true)]
|
||||||
public HashSet<ProtoId<DamageTypePrototype>> ExaminableTypes = default!;
|
public HashSet<ProtoId<DamageTypePrototype>> ExaminableTypes = default!;
|
||||||
|
|||||||
@@ -38,7 +38,13 @@ public abstract partial class SharedInstrumentComponent : Component
|
|||||||
/// Component that indicates that musical instrument was activated (ui opened).
|
/// Component that indicates that musical instrument was activated (ui opened).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, NetworkedComponent]
|
[RegisterComponent, NetworkedComponent]
|
||||||
public sealed partial class ActiveInstrumentComponent : Component;
|
[AutoGenerateComponentState(true)]
|
||||||
|
public sealed partial class ActiveInstrumentComponent : Component
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
[AutoNetworkedField]
|
||||||
|
public MidiTrack?[] Tracks = [];
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class InstrumentComponentState : ComponentState
|
public sealed class InstrumentComponentState : ComponentState
|
||||||
@@ -144,3 +150,72 @@ public enum InstrumentUiKey
|
|||||||
{
|
{
|
||||||
Key,
|
Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the MIDI channels on an instrument.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class InstrumentSetChannelsEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public NetEntity Uid { get; }
|
||||||
|
public MidiTrack?[] Tracks { get; set; }
|
||||||
|
|
||||||
|
public InstrumentSetChannelsEvent(NetEntity uid, MidiTrack?[] tracks)
|
||||||
|
{
|
||||||
|
Uid = uid;
|
||||||
|
Tracks = tracks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single midi track with the track name, instrument name and bank instrument name extracted.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class MidiTrack
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The first specified Track Name
|
||||||
|
/// </summary>
|
||||||
|
public string? TrackName;
|
||||||
|
/// <summary>
|
||||||
|
/// The first specified instrument name
|
||||||
|
/// </summary>
|
||||||
|
public string? InstrumentName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first program change resolved to the name.
|
||||||
|
/// </summary>
|
||||||
|
public string? ProgramName;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Track Name: {TrackName}; Instrument Name: {InstrumentName}; Program Name: {ProgramName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Truncates the fields based on the limit inputted into this method.
|
||||||
|
/// </summary>
|
||||||
|
public void TruncateFields(int limit)
|
||||||
|
{
|
||||||
|
if (InstrumentName != null)
|
||||||
|
InstrumentName = Truncate(InstrumentName, limit);
|
||||||
|
|
||||||
|
if (TrackName != null)
|
||||||
|
TrackName = Truncate(TrackName, limit);
|
||||||
|
|
||||||
|
if (ProgramName != null)
|
||||||
|
ProgramName = Truncate(ProgramName, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string Postfix = "…";
|
||||||
|
// TODO: Make a general method to use in RT? idk if we have that.
|
||||||
|
private string Truncate(string input, int limit)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input) || limit <= 0 || input.Length <= limit)
|
||||||
|
return input;
|
||||||
|
|
||||||
|
var truncatedLength = limit - Postfix.Length;
|
||||||
|
|
||||||
|
return input.Substring(0, truncatedLength) + Postfix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Robust.Shared.Audio;
|
|||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Server.Light.Components;
|
namespace Content.Shared.Light.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Device that allows user to quikly change bulbs in <see cref="PoweredLightComponent"/>
|
/// Device that allows user to quikly change bulbs in <see cref="PoweredLightComponent"/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Light.Components;
|
namespace Content.Shared.Light.Components;
|
||||||
|
|
||||||
@@ -25,11 +26,32 @@ public sealed partial class SunShadowCycleComponent : Component
|
|||||||
/// Time to have each direction applied. Will lerp from the current value to the next one.
|
/// Time to have each direction applied. Will lerp from the current value to the next one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField, AutoNetworkedField]
|
[DataField, AutoNetworkedField]
|
||||||
public List<(float Ratio, Vector2 Direction, float Alpha)> Directions = new()
|
public List<SunShadowCycleDirection> Directions = new()
|
||||||
{
|
{
|
||||||
(0f, new Vector2(0f, 3f), 0f),
|
new SunShadowCycleDirection(0f, new Vector2(0f, 3f), 0f),
|
||||||
(0.25f, new Vector2(-3f, -0.1f), 0.5f),
|
new SunShadowCycleDirection(0.25f, new Vector2(-3f, -0.1f), 0.5f),
|
||||||
(0.5f, new Vector2(0f, -3f), 0.8f),
|
new SunShadowCycleDirection(0.5f, new Vector2(0f, -3f), 0.8f),
|
||||||
(0.75f, new Vector2(3f, -0.1f), 0.5f),
|
new SunShadowCycleDirection(0.75f, new Vector2(3f, -0.1f), 0.5f),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
[DataDefinition]
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public partial record struct SunShadowCycleDirection
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public float Ratio;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public Vector2 Direction;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public float Alpha;
|
||||||
|
|
||||||
|
public SunShadowCycleDirection(float ratio, Vector2 direction, float alpha)
|
||||||
|
{
|
||||||
|
Ratio = ratio;
|
||||||
|
Direction = direction;
|
||||||
|
Alpha = alpha;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Server.Medical.Components;
|
namespace Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracking component for an enabled cryo pod (which periodically tries to inject chemicals in the occupant, if one exists)
|
/// Tracking component for an enabled cryo pod (which periodically tries to inject chemicals in the occupant, if one exists)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Server.Medical.Components;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether an entity is a member of a faction.
|
/// Returns whether an entity is a member of a faction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMember(Entity<NpcFactionMemberComponent?> ent, string faction)
|
public bool IsMember(Entity<NpcFactionMemberComponent?> ent, [ForbidLiteral] string faction)
|
||||||
{
|
{
|
||||||
if (!Resolve(ent, ref ent.Comp, false))
|
if (!Resolve(ent, ref ent.Comp, false))
|
||||||
return false;
|
return false;
|
||||||
@@ -85,7 +85,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// Returns whether an entity is a member of any listed faction.
|
/// Returns whether an entity is a member of any listed faction.
|
||||||
/// If the list is empty this returns false.
|
/// If the list is empty this returns false.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMemberOfAny(Entity<NpcFactionMemberComponent?> ent, IEnumerable<ProtoId<NpcFactionPrototype>> factions)
|
public bool IsMemberOfAny(Entity<NpcFactionMemberComponent?> ent, [ForbidLiteral] IEnumerable<ProtoId<NpcFactionPrototype>> factions)
|
||||||
{
|
{
|
||||||
if (!Resolve(ent, ref ent.Comp, false))
|
if (!Resolve(ent, ref ent.Comp, false))
|
||||||
return false;
|
return false;
|
||||||
@@ -102,7 +102,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds this entity to the particular faction.
|
/// Adds this entity to the particular faction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddFaction(Entity<NpcFactionMemberComponent?> ent, string faction, bool dirty = true)
|
public void AddFaction(Entity<NpcFactionMemberComponent?> ent, [ForbidLiteral] string faction, bool dirty = true)
|
||||||
{
|
{
|
||||||
if (!_proto.HasIndex<NpcFactionPrototype>(faction))
|
if (!_proto.HasIndex<NpcFactionPrototype>(faction))
|
||||||
{
|
{
|
||||||
@@ -121,7 +121,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds this entity to the particular faction.
|
/// Adds this entity to the particular faction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddFactions(Entity<NpcFactionMemberComponent?> ent, HashSet<ProtoId<NpcFactionPrototype>> factions, bool dirty = true)
|
public void AddFactions(Entity<NpcFactionMemberComponent?> ent, [ForbidLiteral] HashSet<ProtoId<NpcFactionPrototype>> factions, bool dirty = true)
|
||||||
{
|
{
|
||||||
ent.Comp ??= EnsureComp<NpcFactionMemberComponent>(ent);
|
ent.Comp ??= EnsureComp<NpcFactionMemberComponent>(ent);
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes this entity from the particular faction.
|
/// Removes this entity from the particular faction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RemoveFaction(Entity<NpcFactionMemberComponent?> ent, string faction, bool dirty = true)
|
public void RemoveFaction(Entity<NpcFactionMemberComponent?> ent, [ForbidLiteral] string faction, bool dirty = true)
|
||||||
{
|
{
|
||||||
if (!_proto.HasIndex<NpcFactionPrototype>(faction))
|
if (!_proto.HasIndex<NpcFactionPrototype>(faction))
|
||||||
{
|
{
|
||||||
@@ -202,7 +202,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
return GetNearbyFactions(ent, range, ent.Comp.FriendlyFactions);
|
return GetNearbyFactions(ent, range, ent.Comp.FriendlyFactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<EntityUid> GetNearbyFactions(EntityUid entity, float range, HashSet<ProtoId<NpcFactionPrototype>> factions)
|
private IEnumerable<EntityUid> GetNearbyFactions(EntityUid entity, float range, [ForbidLiteral] HashSet<ProtoId<NpcFactionPrototype>> factions)
|
||||||
{
|
{
|
||||||
var xform = Transform(entity);
|
var xform = Transform(entity);
|
||||||
foreach (var ent in _lookup.GetEntitiesInRange<NpcFactionMemberComponent>(_xform.GetMapCoordinates((entity, xform)), range))
|
foreach (var ent in _lookup.GetEntitiesInRange<NpcFactionMemberComponent>(_xform.GetMapCoordinates((entity, xform)), range))
|
||||||
@@ -228,12 +228,12 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
return ent.Comp.Factions.Overlaps(other.Comp.Factions) || ent.Comp.FriendlyFactions.Overlaps(other.Comp.Factions);
|
return ent.Comp.Factions.Overlaps(other.Comp.Factions) || ent.Comp.FriendlyFactions.Overlaps(other.Comp.Factions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsFactionFriendly(string target, string with)
|
public bool IsFactionFriendly([ForbidLiteral] string target, [ForbidLiteral] string with)
|
||||||
{
|
{
|
||||||
return _factions[target].Friendly.Contains(with) && _factions[with].Friendly.Contains(target);
|
return _factions[target].Friendly.Contains(with) && _factions[with].Friendly.Contains(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsFactionFriendly(string target, Entity<NpcFactionMemberComponent?> with)
|
public bool IsFactionFriendly([ForbidLiteral] string target, Entity<NpcFactionMemberComponent?> with)
|
||||||
{
|
{
|
||||||
if (!Resolve(with, ref with.Comp, false))
|
if (!Resolve(with, ref with.Comp, false))
|
||||||
return false;
|
return false;
|
||||||
@@ -242,12 +242,12 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
with.Comp.FriendlyFactions.Contains(target);
|
with.Comp.FriendlyFactions.Contains(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsFactionHostile(string target, string with)
|
public bool IsFactionHostile([ForbidLiteral] string target, [ForbidLiteral] string with)
|
||||||
{
|
{
|
||||||
return _factions[target].Hostile.Contains(with) && _factions[with].Hostile.Contains(target);
|
return _factions[target].Hostile.Contains(with) && _factions[with].Hostile.Contains(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsFactionHostile(string target, Entity<NpcFactionMemberComponent?> with)
|
public bool IsFactionHostile([ForbidLiteral] string target, Entity<NpcFactionMemberComponent?> with)
|
||||||
{
|
{
|
||||||
if (!Resolve(with, ref with.Comp, false))
|
if (!Resolve(with, ref with.Comp, false))
|
||||||
return false;
|
return false;
|
||||||
@@ -256,7 +256,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
with.Comp.HostileFactions.Contains(target);
|
with.Comp.HostileFactions.Contains(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsFactionNeutral(string target, string with)
|
public bool IsFactionNeutral([ForbidLiteral] string target, [ForbidLiteral] string with)
|
||||||
{
|
{
|
||||||
return !IsFactionFriendly(target, with) && !IsFactionHostile(target, with);
|
return !IsFactionFriendly(target, with) && !IsFactionHostile(target, with);
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Makes the source faction friendly to the target faction, 1-way.
|
/// Makes the source faction friendly to the target faction, 1-way.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MakeFriendly(string source, string target)
|
public void MakeFriendly([ForbidLiteral] string source, [ForbidLiteral] string target)
|
||||||
{
|
{
|
||||||
if (!_factions.TryGetValue(source, out var sourceFaction))
|
if (!_factions.TryGetValue(source, out var sourceFaction))
|
||||||
{
|
{
|
||||||
@@ -286,7 +286,7 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Makes the source faction hostile to the target faction, 1-way.
|
/// Makes the source faction hostile to the target faction, 1-way.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MakeHostile(string source, string target)
|
public void MakeHostile([ForbidLiteral] string source, [ForbidLiteral] string target)
|
||||||
{
|
{
|
||||||
if (!_factions.TryGetValue(source, out var sourceFaction))
|
if (!_factions.TryGetValue(source, out var sourceFaction))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
namespace Content.Server.Nutrition.Components;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
[RegisterComponent]
|
namespace Content.Shared.Nutrition.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
public sealed partial class FlavorProfileComponent : Component
|
public sealed partial class FlavorProfileComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Localized string containing the base flavor of this entity.
|
/// Localized string containing the base flavor of this entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("flavors")]
|
[DataField]
|
||||||
public HashSet<string> Flavors { get; private set; } = new();
|
public HashSet<string> Flavors { get; private set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reagent IDs to ignore when processing this flavor profile. Defaults to nutriment.
|
/// Reagent IDs to ignore when processing this flavor profile. Defaults to nutriment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("ignoreReagents")]
|
[DataField]
|
||||||
public HashSet<string> IgnoreReagents { get; private set; } = new()
|
public HashSet<string> IgnoreReagents { get; private set; } = new()
|
||||||
{
|
{
|
||||||
"Nutriment",
|
"Nutriment",
|
||||||
"Vitamin",
|
"Vitamin",
|
||||||
"Protein"
|
"Protein",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Nutrition.Components;
|
|
||||||
using Content.Server.Nutrition.EntitySystems;
|
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.Components;
|
namespace Content.Shared.Nutrition.Components;
|
||||||
|
|
||||||
[RegisterComponent, Access(typeof(FoodSystem), typeof(FoodSequenceSystem))]
|
[RegisterComponent, Access(typeof(FoodSystem), typeof(FoodSequenceSystem))]
|
||||||
public sealed partial class FoodComponent : Component
|
public sealed partial class FoodComponent : Component
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.Components;
|
namespace Content.Shared.Nutrition.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking.
|
/// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking.
|
||||||
@@ -9,7 +9,7 @@ namespace Content.Server.Nutrition.Components;
|
|||||||
/// In the event that more head-wear & mask functionality is added (like identity systems, or raising/lowering of
|
/// In the event that more head-wear & mask functionality is added (like identity systems, or raising/lowering of
|
||||||
/// masks), then this component might become redundant.
|
/// masks), then this component might become redundant.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[RegisterComponent, Access(typeof(FoodSystem), typeof(DrinkSystem), typeof(IngestionBlockerSystem))]
|
[RegisterComponent, Access(typeof(FoodSystem), typeof(SharedDrinkSystem), typeof(IngestionBlockerSystem))]
|
||||||
public sealed partial class IngestionBlockerComponent : Component
|
public sealed partial class IngestionBlockerComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -4,7 +4,7 @@ using Robust.Shared.GameStates;
|
|||||||
|
|
||||||
namespace Content.Shared.Nutrition.Components
|
namespace Content.Shared.Nutrition.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedUtensilSystem))]
|
[RegisterComponent, NetworkedComponent, Access(typeof(UtensilSystem))]
|
||||||
public sealed partial class UtensilComponent : Component
|
public sealed partial class UtensilComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("types")]
|
[DataField("types")]
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
using System.Linq;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Nutrition;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems;
|
namespace Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deals with flavor profiles when you eat something.
|
/// Deals with flavor profiles when you eat something.
|
||||||
@@ -1,21 +1,17 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Content.Server.Nutrition.Components;
|
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Nutrition;
|
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
|
||||||
using Content.Shared.Nutrition.Prototypes;
|
using Content.Shared.Nutrition.Prototypes;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Storage.Components;
|
using Content.Shared.Storage.Components;
|
||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems;
|
namespace Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
|
public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
|
||||||
{
|
{
|
||||||
@@ -26,7 +22,7 @@ public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
|
|||||||
[Dependency] private readonly TagSystem _tag = default!;
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly TransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -126,7 +122,7 @@ public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
|
|||||||
if (start.Comp.FoodLayers.Count >= start.Comp.MaxLayers && !elementIndexed.Final || start.Comp.Finished)
|
if (start.Comp.FoodLayers.Count >= start.Comp.MaxLayers && !elementIndexed.Final || start.Comp.Finished)
|
||||||
{
|
{
|
||||||
if (user is not null)
|
if (user is not null)
|
||||||
_popup.PopupEntity(Loc.GetString("food-sequence-no-space"), start, user.Value);
|
_popup.PopupClient(Loc.GetString("food-sequence-no-space"), start, user.Value);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
using Content.Server.Body.Components;
|
using System.Linq;
|
||||||
using Content.Server.Body.Systems;
|
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
|
||||||
using Content.Server.Inventory;
|
|
||||||
using Content.Server.Nutrition.Components;
|
|
||||||
using Content.Shared.Nutrition.Components;
|
|
||||||
using Content.Server.Popups;
|
|
||||||
using Content.Server.Stack;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Organ;
|
using Content.Shared.Body.Organ;
|
||||||
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Destructible;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
@@ -21,42 +18,38 @@ using Content.Shared.Interaction.Components;
|
|||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Nutrition;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Stacks;
|
using Content.Shared.Stacks;
|
||||||
using Content.Shared.Storage;
|
using Content.Shared.Storage;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.Containers.ItemSlots;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Content.Shared.Whitelist;
|
|
||||||
using Content.Shared.Destructible;
|
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems;
|
namespace Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles feeding attempts both on yourself and on the target.
|
/// Handles feeding attempts both on yourself and on the target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FoodSystem : EntitySystem
|
public sealed class FoodSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly BodySystem _body = default!;
|
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||||
[Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
|
[Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||||
[Dependency] private readonly PopupSystem _popup = default!;
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
[Dependency] private readonly ReactiveSystem _reaction = default!;
|
[Dependency] private readonly ReactiveSystem _reaction = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||||
[Dependency] private readonly TransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly StackSystem _stack = default!;
|
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||||
[Dependency] private readonly StomachSystem _stomach = default!;
|
[Dependency] private readonly StomachSystem _stomach = default!;
|
||||||
[Dependency] private readonly UtensilSystem _utensil = default!;
|
[Dependency] private readonly UtensilSystem _utensil = default!;
|
||||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||||
@@ -69,7 +62,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
|
|
||||||
// TODO add InteractNoHandEvent for entities like mice.
|
// TODO add InteractNoHandEvent for entities like mice.
|
||||||
// run after openable for wrapped/peelable foods
|
// run after openable for wrapped/peelable foods
|
||||||
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand, after: new[] { typeof(OpenableSystem), typeof(ServerInventorySystem) });
|
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand, after: new[] { typeof(OpenableSystem), typeof(InventorySystem) });
|
||||||
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
|
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
|
||||||
SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
|
SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
|
||||||
SubscribeLocalEvent<FoodComponent, ConsumeDoAfterEvent>(OnDoAfter);
|
SubscribeLocalEvent<FoodComponent, ConsumeDoAfterEvent>(OnDoAfter);
|
||||||
@@ -116,7 +109,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
if (HasComp<UnremoveableComponent>(food))
|
if (HasComp<UnremoveableComponent>(food))
|
||||||
return (false, false);
|
return (false, false);
|
||||||
|
|
||||||
if (_openable.IsClosed(food, user))
|
if (_openable.IsClosed(food, user, predicted: true))
|
||||||
return (false, true);
|
return (false, true);
|
||||||
|
|
||||||
if (!_solutionContainer.TryGetSolution(food, foodComp.Solution, out _, out var foodSolution))
|
if (!_solutionContainer.TryGetSolution(food, foodComp.Solution, out _, out var foodSolution))
|
||||||
@@ -135,7 +128,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
// Check for used storage on the food item
|
// Check for used storage on the food item
|
||||||
if (TryComp<StorageComponent>(food, out var storageState) && storageState.Container.ContainedEntities.Any())
|
if (TryComp<StorageComponent>(food, out var storageState) && storageState.Container.ContainedEntities.Any())
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
|
_popup.PopupClient(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
|
||||||
return (false, true);
|
return (false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +137,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
if (itemSlots.Slots.Any(slot => slot.Value.HasItem))
|
if (itemSlots.Slots.Any(slot => slot.Value.HasItem))
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
|
_popup.PopupClient(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
|
||||||
return (false, true);
|
return (false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +146,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
|
|
||||||
if (GetUsesRemaining(food, foodComp) <= 0)
|
if (GetUsesRemaining(food, foodComp) <= 0)
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
|
_popup.PopupClient(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
|
||||||
DeleteAndSpawnTrash(foodComp, food, user);
|
DeleteAndSpawnTrash(foodComp, food, user);
|
||||||
return (false, true);
|
return (false, true);
|
||||||
}
|
}
|
||||||
@@ -171,7 +164,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
if (!_transform.GetMapCoordinates(user).InRange(_transform.GetMapCoordinates(target), MaxFeedDistance))
|
if (!_transform.GetMapCoordinates(user).InRange(_transform.GetMapCoordinates(target), MaxFeedDistance))
|
||||||
{
|
{
|
||||||
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
|
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
|
||||||
_popup.PopupEntity(message, user, user);
|
_popup.PopupClient(message, user, user);
|
||||||
return (false, true);
|
return (false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +261,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
if (stomachToUse == null)
|
if (stomachToUse == null)
|
||||||
{
|
{
|
||||||
_solutionContainer.TryAddSolution(soln.Value, split);
|
_solutionContainer.TryAddSolution(soln.Value, split);
|
||||||
_popup.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other", ("target", args.Target.Value)) : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
|
_popup.PopupClient(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other", ("target", args.Target.Value)) : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,20 +276,20 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
var userName = Identity.Entity(args.User, EntityManager);
|
var userName = Identity.Entity(args.User, EntityManager);
|
||||||
_popup.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)), entity.Owner, entity.Owner);
|
_popup.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)), entity.Owner, entity.Owner);
|
||||||
|
|
||||||
_popup.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.User, args.User);
|
_popup.PopupClient(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.User, args.User);
|
||||||
|
|
||||||
// log successful force feed
|
// log successful force feed
|
||||||
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity.Owner):user} forced {ToPrettyString(args.User):target} to eat {ToPrettyString(entity.Owner):food}");
|
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity.Owner):user} forced {ToPrettyString(args.User):target} to eat {ToPrettyString(entity.Owner):food}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString(entity.Comp.EatMessage, ("food", entity.Owner), ("flavors", flavors)), args.User, args.User);
|
_popup.PopupClient(Loc.GetString(entity.Comp.EatMessage, ("food", entity.Owner), ("flavors", flavors)), args.User, args.User);
|
||||||
|
|
||||||
// log successful voluntary eating
|
// log successful voluntary eating
|
||||||
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(entity.Owner):food}");
|
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(entity.Owner):food}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f));
|
_audio.PlayPredicted(entity.Comp.UseSound, args.Target.Value, args.User, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f));
|
||||||
|
|
||||||
// Try to break all used utensils
|
// Try to break all used utensils
|
||||||
foreach (var utensil in utensils)
|
foreach (var utensil in utensils)
|
||||||
@@ -484,7 +477,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
// If "required" field is set, try to block eating without proper utensils used
|
// If "required" field is set, try to block eating without proper utensils used
|
||||||
if (component.UtensilRequired && (usedTypes & component.Utensil) != component.Utensil)
|
if (component.UtensilRequired && (usedTypes & component.Utensil) != component.Utensil)
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("food-you-need-to-hold-utensil", ("utensil", component.Utensil ^ usedTypes)), user, user);
|
_popup.PopupClient(Loc.GetString("food-you-need-to-hold-utensil", ("utensil", component.Utensil ^ usedTypes)), user, user);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +526,7 @@ public sealed class FoodSystem : EntitySystem
|
|||||||
RaiseLocalEvent(uid, attempt, false);
|
RaiseLocalEvent(uid, attempt, false);
|
||||||
if (attempt.Cancelled && attempt.Blocker != null && popupUid != null)
|
if (attempt.Cancelled && attempt.Blocker != null && popupUid != null)
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", attempt.Blocker.Value)),
|
_popup.PopupClient(Loc.GetString("food-system-remove-mask", ("entity", attempt.Blocker.Value)),
|
||||||
uid, popupUid.Value);
|
uid, popupUid.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
using Content.Shared.Clothing;
|
||||||
using Content.Shared.Clothing;
|
using Content.Shared.Nutrition.Components;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems;
|
namespace Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
public sealed class IngestionBlockerSystem : EntitySystem
|
public sealed class IngestionBlockerSystem : EntitySystem
|
||||||
{
|
{
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Lock;
|
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
|
using Content.Shared.Lock;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
@@ -166,7 +166,7 @@ public sealed partial class OpenableSystem : EntitySystem
|
|||||||
/// Drinks that don't have OpenableComponent are automatically open, so it returns false.
|
/// Drinks that don't have OpenableComponent are automatically open, so it returns false.
|
||||||
/// If user is not null a popup will be shown to them.
|
/// If user is not null a popup will be shown to them.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null)
|
public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null, bool predicted = false)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref comp, false))
|
if (!Resolve(uid, ref comp, false))
|
||||||
return false;
|
return false;
|
||||||
@@ -175,7 +175,12 @@ public sealed partial class OpenableSystem : EntitySystem
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
_popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
|
{
|
||||||
|
if (predicted)
|
||||||
|
_popup.PopupClient(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
|
||||||
|
else
|
||||||
|
_popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,36 @@
|
|||||||
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Hands.EntitySystems;
|
||||||
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Nutrition.EntitySystems;
|
namespace Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
public abstract partial class SharedDrinkSystem : EntitySystem
|
public abstract partial class SharedDrinkSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||||
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
|
[Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
|
||||||
|
[Dependency] private readonly FoodSystem _food = default!;
|
||||||
|
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||||
|
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||||
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -17,6 +38,7 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
|||||||
|
|
||||||
SubscribeLocalEvent<DrinkComponent, AttemptShakeEvent>(OnAttemptShake);
|
SubscribeLocalEvent<DrinkComponent, AttemptShakeEvent>(OnAttemptShake);
|
||||||
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
|
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
|
||||||
|
SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnAttemptShake(Entity<DrinkComponent> entity, ref AttemptShakeEvent args)
|
protected void OnAttemptShake(Entity<DrinkComponent> entity, ref AttemptShakeEvent args)
|
||||||
@@ -28,7 +50,7 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
|||||||
protected void OnExamined(Entity<DrinkComponent> entity, ref ExaminedEvent args)
|
protected void OnExamined(Entity<DrinkComponent> entity, ref ExaminedEvent args)
|
||||||
{
|
{
|
||||||
TryComp<OpenableComponent>(entity, out var openable);
|
TryComp<OpenableComponent>(entity, out var openable);
|
||||||
if (_openable.IsClosed(entity.Owner, null, openable) || !args.IsInDetailsRange || !entity.Comp.Examinable)
|
if (_openable.IsClosed(entity.Owner, null, openable, true) || !args.IsInDetailsRange || !entity.Comp.Examinable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var empty = IsEmpty(entity, entity.Comp);
|
var empty = IsEmpty(entity, entity.Comp);
|
||||||
@@ -57,6 +79,38 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddDrinkVerb(Entity<DrinkComponent> entity, ref GetVerbsEvent<AlternativeVerb> ev)
|
||||||
|
{
|
||||||
|
if (entity.Owner == ev.User ||
|
||||||
|
!ev.CanInteract ||
|
||||||
|
!ev.CanAccess ||
|
||||||
|
!TryComp<BodyComponent>(ev.User, out var body) ||
|
||||||
|
!_body.TryGetBodyOrganEntityComps<StomachComponent>((ev.User, body), out var stomachs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Make sure the solution exists
|
||||||
|
if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solution))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// no drinking from living drinks, have to kill them first.
|
||||||
|
if (_mobState.IsAlive(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = ev.User;
|
||||||
|
AlternativeVerb verb = new()
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
TryDrink(user, user, entity.Comp, entity);
|
||||||
|
},
|
||||||
|
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
|
||||||
|
Text = Loc.GetString("drink-system-verb-drink"),
|
||||||
|
Priority = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
ev.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
protected FixedPoint2 DrinkVolume(EntityUid uid, DrinkComponent? component = null)
|
protected FixedPoint2 DrinkVolume(EntityUid uid, DrinkComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
@@ -87,4 +141,74 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
|||||||
|
|
||||||
return remainingString;
|
return remainingString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to feed the drink item to the target entity
|
||||||
|
/// </summary>
|
||||||
|
protected bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
|
||||||
|
{
|
||||||
|
if (!HasComp<BodyComponent>(target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_openable.IsClosed(item, user, predicted: true))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!_solutionContainer.TryGetSolution(item, drink.Solution, out _, out var drinkSolution) || drinkSolution.Volume <= 0)
|
||||||
|
{
|
||||||
|
if (drink.IgnoreEmpty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_popup.PopupClient(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", item)), item, user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_food.IsMouthBlocked(target, user))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!_interaction.InRangeUnobstructed(user, item, popup: true))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var forceDrink = user != target;
|
||||||
|
|
||||||
|
if (forceDrink)
|
||||||
|
{
|
||||||
|
var userName = Identity.Entity(user, EntityManager);
|
||||||
|
|
||||||
|
_popup.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, target);
|
||||||
|
|
||||||
|
// logging
|
||||||
|
_adminLogger.Add(LogType.ForceFeed, LogImpact.High, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// log voluntary drinking
|
||||||
|
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var flavors = _flavorProfile.GetLocalizedFlavorsMessage(user, drinkSolution);
|
||||||
|
|
||||||
|
var doAfterEventArgs = new DoAfterArgs(EntityManager,
|
||||||
|
user,
|
||||||
|
forceDrink ? drink.ForceFeedDelay : drink.Delay,
|
||||||
|
new ConsumeDoAfterEvent(drink.Solution, flavors),
|
||||||
|
eventTarget: item,
|
||||||
|
target: target,
|
||||||
|
used: item)
|
||||||
|
{
|
||||||
|
BreakOnHandChange = false,
|
||||||
|
BreakOnMove = forceDrink,
|
||||||
|
BreakOnDamage = true,
|
||||||
|
MovementThreshold = 0.01f,
|
||||||
|
DistanceThreshold = 1.0f,
|
||||||
|
// do-after will stop if item is dropped when trying to feed someone else
|
||||||
|
// or if the item started out in the user's own hands
|
||||||
|
NeedHand = forceDrink || _hands.IsHolding(user, item),
|
||||||
|
};
|
||||||
|
|
||||||
|
_doAfter.TryStartDoAfter(doAfterEventArgs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace Content.Shared.Nutrition.EntitySystems;
|
|
||||||
|
|
||||||
public abstract class SharedUtensilSystem : EntitySystem
|
|
||||||
{
|
|
||||||
}
|
|
||||||
73
Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs
Normal file
73
Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Nutrition.Components;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Tools.EntitySystems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
|
public sealed class UtensilSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly FoodSystem _foodSystem = default!;
|
||||||
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clicked with utensil
|
||||||
|
/// </summary>
|
||||||
|
private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
|
||||||
|
{
|
||||||
|
if (ev.Handled || ev.Target == null || !ev.CanReach)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
|
||||||
|
ev.Handled = result.Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
|
||||||
|
return (false, false);
|
||||||
|
|
||||||
|
//Prevents food usage with a wrong utensil
|
||||||
|
if ((food.Utensil & utensil.Comp.Types) == 0)
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
|
||||||
|
return (false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
|
||||||
|
return (false, true);
|
||||||
|
|
||||||
|
return _foodSystem.TryFeed(user, user, target, food);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to break the utensil after interaction.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">Utensil.</param>
|
||||||
|
/// <param name="userUid">User of the utensil.</param>
|
||||||
|
public void TryBreak(EntityUid uid, EntityUid userUid, UtensilComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_robustRandom.Prob(component.BreakChance))
|
||||||
|
{
|
||||||
|
_audio.PlayPredicted(component.BreakSound, userUid, userUid, AudioParams.Default.WithVolume(-2f));
|
||||||
|
EntityManager.DeleteEntity(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Lathe;
|
using Content.Shared.Lathe;
|
||||||
|
using Content.Shared.NameModifier.EntitySystems;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Random.Helpers;
|
using Content.Shared.Random.Helpers;
|
||||||
using Content.Shared.Research.Components;
|
using Content.Shared.Research.Components;
|
||||||
@@ -20,6 +21,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
[Dependency] private readonly SharedResearchSystem _research = default!;
|
[Dependency] private readonly SharedResearchSystem _research = default!;
|
||||||
[Dependency] private readonly SharedLatheSystem _lathe = default!;
|
[Dependency] private readonly SharedLatheSystem _lathe = default!;
|
||||||
|
[Dependency] private readonly NameModifierSystem _nameModifier = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -28,6 +30,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<TechnologyDiskComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<TechnologyDiskComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<TechnologyDiskComponent, AfterInteractEvent>(OnAfterInteract);
|
SubscribeLocalEvent<TechnologyDiskComponent, AfterInteractEvent>(OnAfterInteract);
|
||||||
SubscribeLocalEvent<TechnologyDiskComponent, ExaminedEvent>(OnExamine);
|
SubscribeLocalEvent<TechnologyDiskComponent, ExaminedEvent>(OnExamine);
|
||||||
|
SubscribeLocalEvent<TechnologyDiskComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(Entity<TechnologyDiskComponent> ent, ref MapInitEvent args)
|
private void OnMapInit(Entity<TechnologyDiskComponent> ent, ref MapInitEvent args)
|
||||||
@@ -55,6 +58,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
|
|||||||
ent.Comp.Recipes = [];
|
ent.Comp.Recipes = [];
|
||||||
ent.Comp.Recipes.Add(_random.Pick(techs));
|
ent.Comp.Recipes.Add(_random.Pick(techs));
|
||||||
Dirty(ent);
|
Dirty(ent);
|
||||||
|
_nameModifier.RefreshNameModifiers(ent.Owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAfterInteract(Entity<TechnologyDiskComponent> ent, ref AfterInteractEvent args)
|
private void OnAfterInteract(Entity<TechnologyDiskComponent> ent, ref AfterInteractEvent args)
|
||||||
@@ -90,4 +94,16 @@ public sealed class TechnologyDiskSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
args.PushMarkup(message);
|
args.PushMarkup(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnRefreshNameModifiers(Entity<TechnologyDiskComponent> entity, ref RefreshNameModifiersEvent args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Recipes != null)
|
||||||
|
{
|
||||||
|
foreach (var recipe in entity.Comp.Recipes)
|
||||||
|
{
|
||||||
|
var proto = _protoMan.Index(recipe);
|
||||||
|
args.AddModifier("tech-disk-name-format", extraArgs: ("technology", _lathe.GetRecipeName(proto)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ public sealed partial class BinComponent : Component
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public Container ItemContainer = default!;
|
public Container ItemContainer = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the container used to hold the items in the bin.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public string ContainerId = "bin-container";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list representing the order in which
|
/// A list representing the order in which
|
||||||
/// all the entities are stored in the bin.
|
/// all the entities are stored in the bin.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
|
||||||
namespace Content.Server.Storage.Components;
|
namespace Content.Shared.Storage.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies an ongoing pickup area around the attached entity.
|
/// Applies an ongoing pickup area around the attached entity.
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ public sealed class BinSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||||
|
|
||||||
public const string BinContainerId = "bin-container";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<BinComponent, ComponentStartup>(OnStartup);
|
SubscribeLocalEvent<BinComponent, ComponentStartup>(OnStartup);
|
||||||
SubscribeLocalEvent<BinComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<BinComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<BinComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||||
SubscribeLocalEvent<BinComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
SubscribeLocalEvent<BinComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||||
SubscribeLocalEvent<BinComponent, InteractHandEvent>(OnInteractHand, before: new[] { typeof(SharedItemSystem) });
|
SubscribeLocalEvent<BinComponent, InteractHandEvent>(OnInteractHand, before: new[] { typeof(SharedItemSystem) });
|
||||||
SubscribeLocalEvent<BinComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
|
SubscribeLocalEvent<BinComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
|
||||||
@@ -45,7 +44,7 @@ public sealed class BinSystem : EntitySystem
|
|||||||
|
|
||||||
private void OnStartup(EntityUid uid, BinComponent component, ComponentStartup args)
|
private void OnStartup(EntityUid uid, BinComponent component, ComponentStartup args)
|
||||||
{
|
{
|
||||||
component.ItemContainer = _container.EnsureContainer<Container>(uid, BinContainerId);
|
component.ItemContainer = _container.EnsureContainer<Container>(uid, component.ContainerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, BinComponent component, MapInitEvent args)
|
private void OnMapInit(EntityUid uid, BinComponent component, MapInitEvent args)
|
||||||
@@ -66,6 +65,11 @@ public sealed class BinSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEntInserted(Entity<BinComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||||
|
{
|
||||||
|
ent.Comp.Items.Add(args.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEntRemoved(EntityUid uid, BinComponent component, EntRemovedFromContainerMessage args)
|
private void OnEntRemoved(EntityUid uid, BinComponent component, EntRemovedFromContainerMessage args)
|
||||||
{
|
{
|
||||||
component.Items.Remove(args.Entity);
|
component.Items.Remove(args.Entity);
|
||||||
@@ -96,7 +100,7 @@ public sealed class BinSystem : EntitySystem
|
|||||||
if (args.Using != null)
|
if (args.Using != null)
|
||||||
{
|
{
|
||||||
var canReach = args.CanAccess && args.CanInteract;
|
var canReach = args.CanAccess && args.CanInteract;
|
||||||
InsertIntoBin(args.User, args.Target, (EntityUid) args.Using, component, false, canReach);
|
InsertIntoBin(args.User, args.Target, (EntityUid)args.Using, component, false, canReach);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +140,6 @@ public sealed class BinSystem : EntitySystem
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
_container.Insert(toInsert, component.ItemContainer);
|
_container.Insert(toInsert, component.ItemContainer);
|
||||||
component.Items.Add(toInsert);
|
|
||||||
Dirty(uid, component);
|
Dirty(uid, component);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Content.Server.Storage.Components;
|
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.Storage.Components;
|
||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public abstract partial class SharedToolSystem : EntitySystem
|
|||||||
EntityUid user,
|
EntityUid user,
|
||||||
EntityUid? target,
|
EntityUid? target,
|
||||||
float doAfterDelay,
|
float doAfterDelay,
|
||||||
IEnumerable<string> toolQualitiesNeeded,
|
[ForbidLiteral] IEnumerable<string> toolQualitiesNeeded,
|
||||||
DoAfterEvent doAfterEv,
|
DoAfterEvent doAfterEv,
|
||||||
float fuel = 0,
|
float fuel = 0,
|
||||||
ToolComponent? toolComponent = null)
|
ToolComponent? toolComponent = null)
|
||||||
@@ -153,7 +153,7 @@ public abstract partial class SharedToolSystem : EntitySystem
|
|||||||
EntityUid user,
|
EntityUid user,
|
||||||
EntityUid? target,
|
EntityUid? target,
|
||||||
TimeSpan delay,
|
TimeSpan delay,
|
||||||
IEnumerable<string> toolQualitiesNeeded,
|
[ForbidLiteral] IEnumerable<string> toolQualitiesNeeded,
|
||||||
DoAfterEvent doAfterEv,
|
DoAfterEvent doAfterEv,
|
||||||
out DoAfterId? id,
|
out DoAfterId? id,
|
||||||
float fuel = 0,
|
float fuel = 0,
|
||||||
@@ -200,7 +200,7 @@ public abstract partial class SharedToolSystem : EntitySystem
|
|||||||
EntityUid user,
|
EntityUid user,
|
||||||
EntityUid? target,
|
EntityUid? target,
|
||||||
float doAfterDelay,
|
float doAfterDelay,
|
||||||
string toolQualityNeeded,
|
[ForbidLiteral] string toolQualityNeeded,
|
||||||
DoAfterEvent doAfterEv,
|
DoAfterEvent doAfterEv,
|
||||||
float fuel = 0,
|
float fuel = 0,
|
||||||
ToolComponent? toolComponent = null)
|
ToolComponent? toolComponent = null)
|
||||||
@@ -219,7 +219,7 @@ public abstract partial class SharedToolSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a tool entity has the specified quality or not.
|
/// Whether a tool entity has the specified quality or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null)
|
public bool HasQuality(EntityUid uid, [ForbidLiteral] string quality, ToolComponent? tool = null)
|
||||||
{
|
{
|
||||||
return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
|
return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ public abstract partial class SharedToolSystem : EntitySystem
|
|||||||
/// Whether a tool entity has all specified qualities or not.
|
/// Whether a tool entity has all specified qualities or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public bool HasAllQualities(EntityUid uid, IEnumerable<string> qualities, ToolComponent? tool = null)
|
public bool HasAllQualities(EntityUid uid, [ForbidLiteral] IEnumerable<string> qualities, ToolComponent? tool = null)
|
||||||
{
|
{
|
||||||
return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
|
return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
|
||||||
}
|
}
|
||||||
|
|||||||
36
Content.Shared/Toolshed/TypeParsers/FixedPoint2TypeParser.cs
Normal file
36
Content.Shared/Toolshed/TypeParsers/FixedPoint2TypeParser.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Toolshed;
|
||||||
|
using Robust.Shared.Toolshed.Syntax;
|
||||||
|
using Robust.Shared.Toolshed.TypeParsers;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Toolshed.TypeParsers;
|
||||||
|
|
||||||
|
public sealed class FixedPoint2TypeParser : TypeParser<FixedPoint2>
|
||||||
|
{
|
||||||
|
public override bool TryParse(ParserContext ctx, out FixedPoint2 result)
|
||||||
|
{
|
||||||
|
if (Toolshed.TryParse(ctx, out int? value))
|
||||||
|
{
|
||||||
|
result = FixedPoint2.New(value.Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Toolshed.TryParse(ctx, out float? fValue))
|
||||||
|
{
|
||||||
|
result = FixedPoint2.New(fValue.Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolshed's number parser (NumberBaseTypeParser) should have assigned ctx.Error so we don't have to.
|
||||||
|
DebugTools.AssertNotNull(ctx.Error);
|
||||||
|
result = FixedPoint2.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult? TryAutocomplete(ParserContext parserContext, CommandArgument? arg)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHint(GetArgHint(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ namespace Content.Shared.Weather;
|
|||||||
public abstract class SharedWeatherSystem : EntitySystem
|
public abstract class SharedWeatherSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
|
||||||
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
||||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user