Merge pull request #792 from DebugOk/Wizmerge-08/02

Merge wizden up to 08/02
This commit is contained in:
Debug 2024-02-08 16:47:37 +01:00 committed by GitHub
commit bde57bb886
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1041 changed files with 17706 additions and 6460 deletions

27
.github/workflows/close-master-pr.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Close PR's on master
on:
pull_request_target:
types: [ opened, ready_for_review ]
jobs:
run:
runs-on: ubuntu-latest
if: ${{github.head_ref == 'master' || github.head_ref == 'main' || github.head_ref == 'develop'}}
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
# If you prefer to just comment on the pr and not close it, uncomment the bellow and comment the above
# - uses: actions/github-script@v7
# with:
# script: |
# github.rest.issues.createComment({
# issue_number: ${{ github.event.number }},
# owner: context.repo.owner,
# repo: context.repo.repo,
# body: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch. \n\n This pr won't be automatically closed. However, a maintainer may close it for this reason."
# })

View File

@ -1,4 +1,4 @@
<component name="ProjectRunConfigurationManager">
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Content Server+Client" type="CompoundRunConfigurationType">
<toRun name="Content.Client" type="DotNetProject" />
<toRun name="Content.Server" type="DotNetProject" />

View File

@ -55,7 +55,7 @@ namespace Content.Client.Access.UI
foreach (var job in jobs)
{
if (!job.SetPreference)
if (!job.OverrideConsoleVisibility.GetValueOrDefault(job.SetPreference))
{
continue;
}

View File

@ -1,13 +1,8 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Administration;
// mfw they ported input() from BYOND
/// <summary>
/// This handles the client portion of quick dialogs.
/// </summary>
@ -21,149 +16,22 @@ public sealed class QuickDialogSystem : EntitySystem
private void OpenDialog(QuickDialogOpenEvent ev)
{
var window = new FancyWindow()
{
Title = ev.Title
};
var ok = (ev.Buttons & QuickDialogButtonFlag.OkButton) != 0;
var cancel = (ev.Buttons & QuickDialogButtonFlag.CancelButton) != 0;
var window = new DialogWindow(ev.Title, ev.Prompts, ok: ok, cancel: cancel);
var entryContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(8),
};
var promptsDict = new Dictionary<string, LineEdit>();
for (var index = 0; index < ev.Prompts.Count; index++)
{
var entry = ev.Prompts[index];
var entryBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal
};
entryBox.AddChild(new Label { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f });
var edit = new LineEdit() { HorizontalExpand = true };
entryBox.AddChild(edit);
switch (entry.Type)
{
case QuickDialogEntryType.Integer:
edit.IsValid += VerifyInt;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-integer");
break;
case QuickDialogEntryType.Float:
edit.IsValid += VerifyFloat;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-float");
break;
case QuickDialogEntryType.ShortText:
edit.IsValid += VerifyShortText;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-short-text");
break;
case QuickDialogEntryType.LongText:
edit.IsValid += VerifyLongText;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-long-text");
break;
default:
throw new ArgumentOutOfRangeException();
}
promptsDict.Add(entry.FieldId, edit);
entryContainer.AddChild(entryBox);
if (index == ev.Prompts.Count - 1)
{
// Last text box gets enter confirmation.
// Only the last so you don't accidentally confirm early.
edit.OnTextEntered += _ => Confirm();
}
}
var buttonsBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalAlignment = Control.HAlignment.Center,
};
var alreadyReplied = false;
if ((ev.Buttons & QuickDialogButtonFlag.OkButton) != 0)
{
var okButton = new Button()
{
Text = Loc.GetString("quick-dialog-ui-ok"),
};
okButton.OnPressed += _ => Confirm();
buttonsBox.AddChild(okButton);
}
if ((ev.Buttons & QuickDialogButtonFlag.OkButton) != 0)
{
var cancelButton = new Button()
{
Text = Loc.GetString("quick-dialog-ui-cancel"),
};
cancelButton.OnPressed += _ =>
{
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
new(),
QuickDialogButtonFlag.CancelButton));
alreadyReplied = true;
window.Close();
};
buttonsBox.AddChild(cancelButton);
}
window.OnClose += () =>
{
if (!alreadyReplied)
{
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
new(),
QuickDialogButtonFlag.CancelButton));
}
};
entryContainer.AddChild(buttonsBox);
window.ContentsContainer.AddChild(entryContainer);
window.MinWidth *= 2; // Just double it.
window.OpenCentered();
return;
void Confirm()
window.OnConfirmed += responses =>
{
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text),
responses,
QuickDialogButtonFlag.OkButton));
alreadyReplied = true;
window.Close();
}
}
};
private bool VerifyInt(string input)
{
return int.TryParse(input, out var _);
}
private bool VerifyFloat(string input)
{
return float.TryParse(input, out var _);
}
private bool VerifyShortText(string input)
{
return input.Length <= 100;
}
private bool VerifyLongText(string input)
{
return input.Length <= 2000;
window.OnCancelled += () =>
{
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
new(),
QuickDialogButtonFlag.CancelButton));
};
}
}

View File

@ -1,6 +1,8 @@
using Content.Shared.Ghost;
using Content.Shared.Antag;
using Content.Shared.Revolutionary.Components;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
@ -9,24 +11,44 @@ namespace Content.Client.Antag;
/// <summary>
/// Used for assigning specified icons for antags.
/// </summary>
public abstract class AntagStatusIconSystem<T> : SharedStatusIconSystem
where T : IComponent
public sealed class AntagStatusIconSystem : SharedStatusIconSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetIcon);
}
/// <summary>
/// Will check if the local player has the same component as the one who called it and give the status icon.
/// Adds a Status Icon on an entity if the player is supposed to see it.
/// </summary>
/// <param name="antagStatusIcon">The status icon that your antag uses</param>
/// <param name="args">The GetStatusIcon event.</param>
protected virtual void GetStatusIcon(string antagStatusIcon, ref GetStatusIconsEvent args)
private void GetIcon<T>(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent
{
var ent = _player.LocalPlayer?.ControlledEntity;
var ent = _player.LocalSession?.AttachedEntity;
if (!HasComp<T>(ent) && !HasComp<GhostComponent>(ent))
var canEv = new CanDisplayStatusIconsEvent(ent);
RaiseLocalEvent(uid, ref canEv);
if (!canEv.Cancelled)
ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon));
}
/// <summary>
/// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal
/// with a special case where if someone is a head rev we only want to display the headrev icon.
/// </summary>
private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev)
{
if (HasComp<HeadRevolutionaryComponent>(uid))
return;
args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(antagStatusIcon));
GetIcon(uid, comp, ref ev);
}
}

View File

@ -0,0 +1,8 @@
using Content.Shared.Atmos.EntitySystems;
namespace Content.Client.Atmos.EntitySystems;
public sealed class FirestarterSystem : SharedFirestarterSystem
{
}

View File

@ -18,7 +18,7 @@ namespace Content.Client.Atmos.UI
_window = new GasAnalyzerWindow();
_window.OnClose += OnClose;
_window.OpenCentered();
_window.OpenCenteredLeft();
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)

View File

@ -25,8 +25,10 @@ public sealed class BackgroundAudioSystem : EntitySystem
[Dependency] private readonly IStateManager _stateManager = default!;
private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f);
private readonly AudioParams _roundEndParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f);
public EntityUid? LobbyStream;
public EntityUid? LobbyMusicStream;
public EntityUid? LobbyRoundRestartAudioStream;
public override void Initialize()
{
@ -115,7 +117,7 @@ public sealed class BackgroundAudioSystem : EntitySystem
public void StartLobbyMusic()
{
if (LobbyStream != null || !_configManager.GetCVar(CCVars.LobbyMusicEnabled))
if (LobbyMusicStream != null || !_configManager.GetCVar(CCVars.LobbyMusicEnabled))
return;
var file = _gameTicker.LobbySong;
@ -124,18 +126,21 @@ public sealed class BackgroundAudioSystem : EntitySystem
return;
}
LobbyStream = _audio.PlayGlobal(file, Filter.Local(), false,
LobbyMusicStream = _audio.PlayGlobal(
file,
Filter.Local(),
false,
_lobbyParams.WithVolume(_lobbyParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))))?.Entity;
}
private void EndLobbyMusic()
{
LobbyStream = _audio.Stop(LobbyStream);
LobbyMusicStream = _audio.Stop(LobbyMusicStream);
}
private void PlayRestartSound(RoundRestartCleanupEvent ev)
{
if (!_configManager.GetCVar(CCVars.LobbyMusicEnabled))
if (!_configManager.GetCVar(CCVars.RestartSoundsEnabled))
return;
var file = _gameTicker.RestartSound;
@ -144,10 +149,11 @@ public sealed class BackgroundAudioSystem : EntitySystem
return;
}
var volume = _lobbyParams.WithVolume(_lobbyParams.Volume +
SharedAudioSystem.GainToVolume(
_configManager.GetCVar(CCVars.LobbyMusicVolume)));
_audio.PlayGlobal(file, Filter.Local(), false, volume);
LobbyRoundRestartAudioStream = _audio.PlayGlobal(
file,
Filter.Local(),
false,
_roundEndParams.WithVolume(_roundEndParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
)?.Entity;
}
}

View File

@ -50,15 +50,24 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
_fadingOut.Clear();
// Preserve lobby music but everything else should get dumped.
var lobbyStream = EntityManager.System<BackgroundAudioSystem>().LobbyStream;
TryComp(lobbyStream, out AudioComponent? audioComp);
var oldGain = audioComp?.Gain;
var lobbyMusic = EntityManager.System<BackgroundAudioSystem>().LobbyMusicStream;
TryComp(lobbyMusic, out AudioComponent? lobbyMusicComp);
var oldMusicGain = lobbyMusicComp?.Gain;
var restartAudio = EntityManager.System<BackgroundAudioSystem>().LobbyRoundRestartAudioStream;
TryComp(restartAudio, out AudioComponent? restartComp);
var oldAudioGain = restartComp?.Gain;
SilenceAudio();
if (oldGain != null)
if (oldMusicGain != null)
{
Audio.SetGain(lobbyStream, oldGain.Value, audioComp);
Audio.SetGain(lobbyMusic, oldMusicGain.Value, lobbyMusicComp);
}
if (oldAudioGain != null)
{
Audio.SetGain(restartAudio, oldAudioGain.Value, restartComp);
}
}

View File

@ -0,0 +1,8 @@
using Content.Shared.Botany.Components;
namespace Content.Client.Botany.Components;
[RegisterComponent]
public sealed partial class ProduceComponent : SharedProduceComponent
{
}

View File

@ -0,0 +1,8 @@
using Content.Shared.Botany.Components;
namespace Content.Client.Botany.Components;
[RegisterComponent]
public sealed partial class SeedComponent : SharedSeedComponent
{
}

View File

@ -2,7 +2,6 @@ using Content.Client.Rotation;
using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
using Content.Shared.Rotation;
using Content.Shared.Vehicle.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Buckle;
@ -26,9 +25,6 @@ internal sealed class BuckleSystem : SharedBuckleSystem
if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
return;
if (HasComp<VehicleComponent>(component.LastEntityBuckledTo))
return;
// Adjust draw depth when the chair faces north so that the seat back is drawn over the player.
// Reset the draw depth when rotated in any other direction.
// TODO when ECSing, make this a visualizer

View File

@ -1,14 +1,34 @@
using System.Numerics;
using Content.Shared.Camera;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
namespace Content.Client.Camera;
public sealed class CameraRecoilSystem : SharedCameraRecoilSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
protected float Intensity;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<CameraKickEvent>(OnCameraKick);
_configManager.OnValueChanged(CCVars.ScreenShakeIntensity, OnCvarChanged, true);
}
public override void Shutdown()
{
base.Shutdown();
_configManager.UnsubValueChanged(CCVars.ScreenShakeIntensity, OnCvarChanged);
}
private void OnCvarChanged(float value)
{
Intensity = value;
}
private void OnCameraKick(CameraKickEvent ev)
@ -18,9 +38,14 @@ public sealed class CameraRecoilSystem : SharedCameraRecoilSystem
public override void KickCamera(EntityUid uid, Vector2 recoil, CameraRecoilComponent? component = null)
{
if (Intensity == 0)
return;
if (!Resolve(uid, ref component, false))
return;
recoil *= Intensity;
// Use really bad math to "dampen" kicks when we're already kicked.
var existing = component.CurrentKick.Length();
var dampen = existing / KickMagnitudeMax;

View File

@ -4,16 +4,16 @@
Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Name="NumberLabel"
Align="Center"
SetWidth="60"
Align="Right"
SetWidth="26"
ClipText="True"/>
<Label Name="TimeLabel"
Align="Center"
SetWidth="280"
SetWidth="100"
ClipText="True"/>
<Label Name="AccessorLabel"
Align="Center"
SetWidth="110"
Align="Left"
SetWidth="390"
ClipText="True"/>
</BoxContainer>
<customControls:HSeparator Margin="0 5 0 5"/>

View File

@ -9,10 +9,10 @@
BorderColor="#5a5a5a"
BorderThickness="0 0 0 1"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Horizontal" Align="Center" Margin="8">
<Label HorizontalExpand="True" Text="{Loc 'log-probe-label-number'}"/>
<Label HorizontalExpand="True" Text="{Loc 'log-probe-label-time'}"/>
<Label HorizontalExpand="True" Text="{Loc 'log-probe-label-accessor'}"/>
<BoxContainer Orientation="Horizontal" Margin="4 8">
<Label Align="Right" SetWidth="26" ClipText="True" Text="{Loc 'log-probe-label-number'}"/>
<Label Align="Center" SetWidth="100" ClipText="True" Text="{Loc 'log-probe-label-time'}"/>
<Label Align="Left" SetWidth="390" ClipText="True" Text="{Loc 'log-probe-label-accessor'}"/>
</BoxContainer>
</PanelContainer>
<ScrollContainer VerticalExpand="True" HScrollEnabled="True">

View File

@ -1,6 +1,8 @@
using Content.Client.Items.Systems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Hands;
using Content.Shared.Rounding;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
@ -10,11 +12,13 @@ namespace Content.Client.Chemistry.Visualizers;
public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionContainerVisualsComponent>
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ItemSystem _itemSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionContainerVisualsComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals);
}
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
@ -111,6 +115,35 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
}
}
// in-hand visuals
_itemSystem.VisualsChanged(uid);
}
private void OnGetHeldVisuals(EntityUid uid, SolutionContainerVisualsComponent component, GetInhandVisualsEvent args)
{
if (component.InHandsFillBaseName == null)
return;
if (!TryComp(uid, out AppearanceComponent? appearance))
return;
if (!AppearanceSystem.TryGetData<float>(uid, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
return;
int closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, component.InHandsMaxFillLevels + 1);
if (closestFillSprite > 0)
{
var layer = new PrototypeLayerData();
var key = "inhand-" + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
layer.State = key;
if (component.ChangeColor && AppearanceSystem.TryGetData<Color>(uid, SolutionContainerVisuals.Color, out var color, appearance))
layer.Color = color;
args.Layers.Add((key, layer));
}
}
}

View File

@ -79,12 +79,11 @@ namespace Content.Client.ContextMenu.UI
var representation = _entityManager.ToPrettyString(entity);
var name = representation.Name;
var id = representation.Uid.ToString();
var prototype = representation.Prototype;
var playerName = representation.Session?.Name ?? SearchPlayerName(entity);
var deleted = representation.Deleted;
return $"{name} ({id} / {_entityManager.GetNetEntity(entity).ToString()}{(prototype != null ? $", {prototype}" : "")}{(playerName != null ? $", {playerName}" : "")}){(deleted ? "D" : "")}";
return $"{name} ({_entityManager.GetNetEntity(entity).ToString()}{(prototype != null ? $", {prototype}" : "")}{(playerName != null ? $", {playerName}" : "")}){(deleted ? "D" : "")}";
}
private string GetEntityDescription(EntityUid entity)
@ -94,7 +93,7 @@ namespace Content.Client.ContextMenu.UI
return GetEntityDescriptionAdmin(entity);
}
return Identity.Name(entity, _entityManager, _playerManager.LocalPlayer!.ControlledEntity!);
return Identity.Name(entity, _entityManager, _playerManager.LocalEntity!);
}
/// <summary>

View File

@ -24,7 +24,7 @@ namespace Content.Client.Crayon.UI
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var crayonDecals = prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
_menu.Populate(crayonDecals);
_menu.OpenCentered();
_menu.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)

View File

@ -0,0 +1,15 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'criminal-records-console-crime-history'}"
MinSize="660 400">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="5">
<BoxContainer Name="Editing" Orientation="Horizontal" HorizontalExpand="True" Align="Center" Margin="5">
<Button Name="AddButton" Text="{Loc 'criminal-records-add-history'}"/>
<Button Name="DeleteButton" Text="{Loc 'criminal-records-delete-history'}" Disabled="True"/>
</BoxContainer>
<Label Name="NoHistory" Text="{Loc 'criminal-records-no-history'}" HorizontalExpand="True" HorizontalAlignment="Center"/>
<ScrollContainer VerticalExpand="True">
<ItemList Name="History"/> <!-- Populated when window opened -->
</ScrollContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@ -0,0 +1,107 @@
using Content.Shared.Administration;
using Content.Shared.CriminalRecords;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.CriminalRecords;
/// <summary>
/// Window opened when Crime History button is pressed
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class CrimeHistoryWindow : FancyWindow
{
public Action<string>? OnAddHistory;
public Action<uint>? OnDeleteHistory;
private uint _maxLength;
private uint? _index;
private DialogWindow? _dialog;
public CrimeHistoryWindow(uint maxLength)
{
RobustXamlLoader.Load(this);
_maxLength = maxLength;
OnClose += () =>
{
_dialog?.Close();
// deselect so when reopening the window it doesnt try to use invalid index
_index = null;
};
AddButton.OnPressed += _ =>
{
if (_dialog != null)
{
_dialog.MoveToFront();
return;
}
var field = "line";
var prompt = Loc.GetString("criminal-records-console-reason");
var placeholder = Loc.GetString("criminal-records-history-placeholder");
var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder);
var entries = new List<QuickDialogEntry> { entry };
_dialog = new DialogWindow(Title!, entries);
_dialog.OnConfirmed += responses =>
{
var line = responses[field];
if (line.Length < 1 || line.Length > _maxLength)
return;
OnAddHistory?.Invoke(line);
// adding deselects so prevent deleting yeah
_index = null;
DeleteButton.Disabled = true;
};
// prevent MoveToFront being called on a closed window and double closing
_dialog.OnClose += () => { _dialog = null; };
};
DeleteButton.OnPressed += _ =>
{
if (_index is not {} index)
return;
OnDeleteHistory?.Invoke(index);
// prevent total spam wiping
History.ClearSelected();
_index = null;
DeleteButton.Disabled = true;
};
History.OnItemSelected += args =>
{
_index = (uint) args.ItemIndex;
DeleteButton.Disabled = false;
};
History.OnItemDeselected += args =>
{
_index = null;
DeleteButton.Disabled = true;
};
}
public void UpdateHistory(CriminalRecord record, bool access)
{
History.Clear();
Editing.Visible = access;
NoHistory.Visible = record.History.Count == 0;
foreach (var entry in record.History)
{
var time = entry.AddTime;
var line = $"{time.Hours:00}:{time.Minutes:00}:{time.Seconds:00} - {entry.Crime}";
History.AddItem(line);
}
// deselect if something goes wrong
if (_index is {} index && record.History.Count >= index)
_index = null;
}
}

View File

@ -0,0 +1,81 @@
using Content.Shared.Access.Systems;
using Content.Shared.CriminalRecords;
using Content.Shared.CriminalRecords.Components;
using Content.Shared.Security;
using Content.Shared.StationRecords;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Client.CriminalRecords;
public sealed class CriminalRecordsConsoleBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly AccessReaderSystem _accessReader;
private CriminalRecordsConsoleWindow? _window;
private CrimeHistoryWindow? _historyWindow;
public CriminalRecordsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_accessReader = EntMan.System<AccessReaderSystem>();
}
protected override void Open()
{
base.Open();
var comp = EntMan.GetComponent<CriminalRecordsConsoleComponent>(Owner);
_window = new(Owner, comp.MaxStringLength, _playerManager, _proto, _random, _accessReader);
_window.OnKeySelected += key =>
SendMessage(new SelectStationRecord(key));
_window.OnFiltersChanged += (type, filterValue) =>
SendMessage(new SetStationRecordFilter(type, filterValue));
_window.OnStatusSelected += status =>
SendMessage(new CriminalRecordChangeStatus(status, null));
_window.OnDialogConfirmed += (_, reason) =>
SendMessage(new CriminalRecordChangeStatus(SecurityStatus.Wanted, reason));
_window.OnHistoryUpdated += UpdateHistory;
_window.OnHistoryClosed += () => _historyWindow?.Close();
_window.OnClose += Close;
_historyWindow = new(comp.MaxStringLength);
_historyWindow.OnAddHistory += line => SendMessage(new CriminalRecordAddHistory(line));
_historyWindow.OnDeleteHistory += index => SendMessage(new CriminalRecordDeleteHistory(index));
_historyWindow.Close(); // leave closed until user opens it
}
/// <summary>
/// Updates or opens a new history window.
/// </summary>
private void UpdateHistory(CriminalRecord record, bool access, bool open)
{
_historyWindow!.UpdateHistory(record, access);
if (open)
_historyWindow.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not CriminalRecordsConsoleState cast)
return;
_window?.UpdateState(cast);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_window?.Close();
_historyWindow?.Close();
}
}

View File

@ -0,0 +1,37 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'criminal-records-console-window-title'}"
MinSize="660 400">
<BoxContainer Orientation="Vertical">
<!-- Record search bar
TODO: make this into a control shared with general records -->
<BoxContainer Margin="5 5 5 10" HorizontalExpand="true" VerticalAlignment="Center">
<OptionButton Name="FilterType" MinWidth="200" Margin="0 0 10 0"/> <!-- Populated in constructor -->
<LineEdit Name="FilterText" PlaceHolder="{Loc 'criminal-records-filter-placeholder'}" HorizontalExpand="True"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
<!-- Record listing -->
<BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250">
<Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/>
<Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
<ScrollContainer VerticalExpand="True">
<ItemList Name="RecordListing"/> <!-- Populated when loading state -->
</ScrollContainer>
</BoxContainer>
<Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
<!-- Selected record info -->
<BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False">
<Label Name="PersonName" StyleClasses="LabelBig"/>
<Label Name="PersonPrints"/>
<Label Name="PersonDna"/>
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5">
<Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/>
<OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor -->
</BoxContainer>
<RichTextLabel Name="WantedReason" Visible="False"/>
<Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@ -0,0 +1,264 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Access.Systems;
using Content.Shared.Administration;
using Content.Shared.CriminalRecords;
using Content.Shared.Dataset;
using Content.Shared.Security;
using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Client.CriminalRecords;
// TODO: dedupe shitcode from general records theres a lot
[GenerateTypedNameReferences]
public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
{
private readonly IPlayerManager _player;
private readonly IPrototypeManager _proto;
private readonly IRobustRandom _random;
private readonly AccessReaderSystem _accessReader;
public readonly EntityUid Console;
[ValidatePrototypeId<DatasetPrototype>]
private const string ReasonPlaceholders = "CriminalRecordsWantedReasonPlaceholders";
public Action<uint?>? OnKeySelected;
public Action<StationRecordFilterType, string>? OnFiltersChanged;
public Action<SecurityStatus>? OnStatusSelected;
public Action<CriminalRecord, bool, bool>? OnHistoryUpdated;
public Action? OnHistoryClosed;
public Action<SecurityStatus, string>? OnDialogConfirmed;
private uint _maxLength;
private bool _isPopulating;
private bool _access;
private uint? _selectedKey;
private CriminalRecord? _selectedRecord;
private DialogWindow? _reasonDialog;
private StationRecordFilterType _currentFilterType;
public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader)
{
RobustXamlLoader.Load(this);
Console = console;
_player = playerManager;
_proto = prototypeManager;
_random = robustRandom;
_accessReader = accessReader;
_maxLength = maxLength;
_currentFilterType = StationRecordFilterType.Name;
OpenCentered();
foreach (var item in Enum.GetValues<StationRecordFilterType>())
{
FilterType.AddItem(GetTypeFilterLocals(item), (int)item);
}
foreach (var status in Enum.GetValues<SecurityStatus>())
{
AddStatusSelect(status);
}
OnClose += () => _reasonDialog?.Close();
RecordListing.OnItemSelected += args =>
{
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast)
return;
OnKeySelected?.Invoke(cast);
};
RecordListing.OnItemDeselected += _ =>
{
if (!_isPopulating)
OnKeySelected?.Invoke(null);
};
FilterType.OnItemSelected += eventArgs =>
{
var type = (StationRecordFilterType)eventArgs.Id;
if (_currentFilterType != type)
{
_currentFilterType = type;
FilterListingOfRecords(FilterText.Text);
}
};
FilterText.OnTextEntered += args =>
{
FilterListingOfRecords(args.Text);
};
StatusOptionButton.OnItemSelected += args =>
{
SetStatus((SecurityStatus) args.Id);
};
HistoryButton.OnPressed += _ =>
{
if (_selectedRecord is {} record)
OnHistoryUpdated?.Invoke(record, _access, true);
};
}
public void UpdateState(CriminalRecordsConsoleState state)
{
if (state.Filter != null)
{
if (state.Filter.Type != _currentFilterType)
{
_currentFilterType = state.Filter.Type;
}
if (state.Filter.Value != FilterText.Text)
{
FilterText.Text = state.Filter.Value;
}
}
_selectedKey = state.SelectedKey;
FilterType.SelectId((int)_currentFilterType);
// set up the records listing panel
RecordListing.Clear();
var hasRecords = state.RecordListing != null && state.RecordListing.Count > 0;
NoRecords.Visible = !hasRecords;
if (hasRecords)
PopulateRecordListing(state.RecordListing!);
// set up the selected person's record
var selected = _selectedKey != null;
PersonContainer.Visible = selected;
RecordUnselected.Visible = !selected;
_access = _player.LocalSession?.AttachedEntity is {} player
&& _accessReader.IsAllowed(player, Console);
// hide access-required editing parts when no access
var editing = _access && selected;
StatusOptionButton.Disabled = !editing;
if (state is { CriminalRecord: not null, StationRecord: not null })
{
PopulateRecordContainer(state.StationRecord, state.CriminalRecord);
OnHistoryUpdated?.Invoke(state.CriminalRecord, _access, false);
_selectedRecord = state.CriminalRecord;
}
else
{
_selectedRecord = null;
OnHistoryClosed?.Invoke();
}
}
private void PopulateRecordListing(Dictionary<uint, string> listing)
{
_isPopulating = true;
foreach (var (key, name) in listing)
{
var item = RecordListing.AddItem(name);
item.Metadata = key;
item.Selected = key == _selectedKey;
}
_isPopulating = false;
RecordListing.SortItemsByText();
}
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
{
var na = Loc.GetString("generic-not-available-shorthand");
PersonName.Text = stationRecord.Name;
PersonPrints.Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", stationRecord.Fingerprint ?? na));
PersonDna.Text = Loc.GetString("general-station-record-console-record-dna", ("dna", stationRecord.DNA ?? na));
StatusOptionButton.SelectId((int) criminalRecord.Status);
if (criminalRecord.Reason is {} reason)
{
var message = FormattedMessage.FromMarkup(Loc.GetString("criminal-records-console-wanted-reason"));
message.AddText($": {reason}");
WantedReason.SetMessage(message);
WantedReason.Visible = true;
}
else
{
WantedReason.Visible = false;
}
}
private void AddStatusSelect(SecurityStatus status)
{
var name = Loc.GetString($"criminal-records-status-{status.ToString().ToLower()}");
StatusOptionButton.AddItem(name, (int)status);
}
private void FilterListingOfRecords(string text = "")
{
if (!_isPopulating)
{
OnFiltersChanged?.Invoke(_currentFilterType, text);
}
}
private void SetStatus(SecurityStatus status)
{
if (status == SecurityStatus.Wanted)
{
GetWantedReason();
return;
}
OnStatusSelected?.Invoke(status);
}
private void GetWantedReason()
{
if (_reasonDialog != null)
{
_reasonDialog.MoveToFront();
return;
}
var field = "reason";
var title = Loc.GetString("criminal-records-status-wanted");
var placeholders = _proto.Index<DatasetPrototype>(ReasonPlaceholders);
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used
var prompt = Loc.GetString("criminal-records-console-reason");
var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder);
var entries = new List<QuickDialogEntry>() { entry };
_reasonDialog = new DialogWindow(title, entries);
_reasonDialog.OnConfirmed += responses =>
{
var reason = responses[field];
if (reason.Length < 1 || reason.Length > _maxLength)
return;
OnDialogConfirmed?.Invoke(SecurityStatus.Wanted, reason);
};
_reasonDialog.OnClose += () => { _reasonDialog = null; };
}
private string GetTypeFilterLocals(StationRecordFilterType type)
{
return Loc.GetString($"criminal-records-{type.ToString().ToLower()}-filter");
}
}

View File

@ -0,0 +1,7 @@
using Content.Shared.CriminalRecords.Systems;
namespace Content.Client.CriminalRecords.Systems;
public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleSystem
{
}

View File

@ -1,4 +1,5 @@
using Content.Shared.Explosion;
using Content.Shared.Explosion.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Explosion;

View File

@ -1,5 +1,5 @@
using System.Numerics;
using Content.Shared.Explosion;
using Content.Shared.Explosion.Components;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Enums;

View File

@ -1,4 +1,5 @@
using Content.Shared.Explosion;
using Content.Shared.Explosion.Components;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameStates;

View File

@ -0,0 +1,7 @@
using Content.Shared.Explosion.EntitySystems;
namespace Content.Client.Explosion;
public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
{
}

View File

@ -1,4 +1,5 @@
using Content.Shared.Explosion;
using Content.Shared.Explosion.Components;
namespace Content.Client.Explosion;

View File

@ -20,8 +20,6 @@ namespace Content.Client.GameTicking.Managers
{
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[ViewVariables] private bool _initialized;
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();

View File

@ -1,4 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="250 100">
<ScrollContainer
VerticalExpand="True">
@ -12,6 +14,16 @@
Name="PatientDataContainer"
Orientation="Vertical"
Margin="0 0 5 10">
<BoxContainer Name="ScanModePanel" HorizontalExpand="True" Visible="False" Margin="0 5 0 0">
<Label
Name="ScanMode"
Align="Left"
Text="{Loc health-analyzer-window-scan-mode-text}"/>
<Label
Name="ScanModeText"
Align="Right"
HorizontalExpand="True"/>
</BoxContainer>
<Label
Name="PatientName"/>
<Label
@ -30,4 +42,4 @@
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</DefaultWindow>
</controls:FancyWindow>

View File

@ -1,24 +1,26 @@
using System.Linq;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
using Content.Shared.Nutrition.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI
{
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : DefaultWindow
public sealed partial class HealthAnalyzerWindow : FancyWindow
{
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
@ -60,13 +62,24 @@ namespace Content.Client.HealthAnalyzer.UI
entityName = Identity.Name(target.Value, _entityManager);
}
if (msg.ScanMode.HasValue)
{
ScanModePanel.Visible = true;
ScanModeText.Text = Loc.GetString(msg.ScanMode.Value ? "health-analyzer-window-scan-mode-active" : "health-analyzer-window-scan-mode-inactive");
ScanModeText.FontColorOverride = msg.ScanMode.Value ? Color.Green : Color.Red;
}
else
{
ScanModePanel.Visible = false;
}
PatientName.Text = Loc.GetString(
"health-analyzer-window-entity-health-text",
("entityName", entityName)
);
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C")
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C ({msg.Temperature:F1} °K)")
);
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
@ -85,6 +98,19 @@ namespace Content.Client.HealthAnalyzer.UI
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
if (_entityManager.TryGetComponent(target, out HungerComponent? hunger)
&& hunger.StarvationDamage != null
&& hunger.CurrentThreshold <= HungerThreshold.Starving)
{
var box = new Control { Margin = new Thickness(0, 0, 0, 15) };
box.AddChild(CreateDiagnosticGroupTitle(
Loc.GetString("health-analyzer-window-malnutrition"),
"malnutrition"));
GroupsContainer.AddChild(box);
}
SetHeight = AnalyzerHeight;
SetWidth = AnalyzerWidth;
}
@ -113,7 +139,7 @@ namespace Content.Client.HealthAnalyzer.UI
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId, damageAmount.Int()));
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
@ -166,7 +192,7 @@ namespace Content.Client.HealthAnalyzer.UI
};
}
private BoxContainer CreateDiagnosticGroupTitle(string text, string id, int damageAmount)
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{

View File

@ -23,17 +23,20 @@ public sealed class LatheSystem : SharedLatheSystem
return;
if (_appearance.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component) &&
args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out _))
args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out var powerLayer))
{
args.Sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, powered);
args.Sprite.LayerSetVisible(powerLayer, powered);
}
// Lathe specific stuff
if (_appearance.TryGetData<bool>(uid, LatheVisuals.IsRunning, out var isRunning, args.Component))
if (_appearance.TryGetData<bool>(uid, LatheVisuals.IsRunning, out var isRunning, args.Component) &&
args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
component.RunningState != null &&
component.IdleState != null)
{
var state = isRunning ? component.RunningState : component.IdleState;
args.Sprite.LayerSetAnimationTime(LatheVisualLayers.IsRunning, 0f);
args.Sprite.LayerSetState(LatheVisualLayers.IsRunning, state);
args.Sprite.LayerSetAnimationTime(runningLayer, 0f);
args.Sprite.LayerSetState(runningLayer, state);
}
}

View File

@ -31,7 +31,7 @@ namespace Content.Client.Lathe.UI
SendMessage(new LatheQueueRecipeMessage(recipe, amount));
};
_menu.OpenCentered();
_menu.OpenCenteredRight();
}
protected override void UpdateState(BoundUserInterfaceState state)

View File

@ -22,6 +22,15 @@
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'ui-options-screen-shake-intensity'}" Margin="8 0" />
<Slider Name="ScreenShakeIntensitySlider"
MinValue="0"
MaxValue="100"
Rounded="True"
MinWidth="200" />
<Label Name="ScreenShakeIntensityLabel" Margin="8 0" />
</BoxContainer>
<Label Text="{Loc 'ui-options-general-discord'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/>

View File

@ -9,6 +9,7 @@ using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Range = Robust.Client.UserInterface.Controls.Range;
namespace Content.Client.Options.UI.Tabs
{
@ -63,6 +64,7 @@ namespace Content.Client.Options.UI.Tabs
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
// ToggleWalk.OnToggled += OnCheckBoxToggled;
StaticStorageUI.OnToggled += OnCheckBoxToggled;
@ -74,6 +76,8 @@ namespace Content.Client.Options.UI.Tabs
OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
@ -93,6 +97,12 @@ namespace Content.Client.Options.UI.Tabs
UpdateApplyButton();
}
private void OnScreenShakeIntensitySliderChanged(Range obj)
{
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
UpdateApplyButton();
}
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
{
foreach (var theme in _prototypeManager.EnumeratePrototypes<HudThemePrototype>())
@ -111,6 +121,7 @@ namespace Content.Client.Options.UI.Tabs
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
@ -135,6 +146,7 @@ namespace Content.Client.Options.UI.Tabs
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
@ -148,6 +160,7 @@ namespace Content.Client.Options.UI.Tabs
isFancyChatSame &&
isFancyBackgroundSame &&
isReducedMotionSame &&
isScreenShakeIntensitySame &&
// isToggleWalkSame &&
isStaticStorageUISame;
}

View File

@ -1,19 +1,12 @@
using System.Numerics;
using Content.Shared.Pointing.Components;
using System.Numerics;
namespace Content.Client.Pointing.Components;
[RegisterComponent]
public sealed partial class PointingArrowComponent : SharedPointingArrowComponent
{
/// <summary>
/// How long it takes to go from the bottom of the animation to the top.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("animationTime")]
public float AnimationTime = 0.5f;
/// <summary>
/// How far it goes in any direction.
/// How far the arrow moves up and down during the floating phase.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("offset")]

View File

@ -0,0 +1,62 @@
using Content.Client.Pointing.Components;
using Content.Shared.Pointing;
using Robust.Client.GameObjects;
using Robust.Client.Animations;
using Robust.Shared.Animations;
using System.Numerics;
namespace Content.Client.Pointing;
public sealed partial class PointingSystem : SharedPointingSystem
{
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
public void InitializeVisualizer()
{
SubscribeLocalEvent<PointingArrowComponent, AnimationCompletedEvent>(OnAnimationCompleted);
}
private void OnAnimationCompleted(EntityUid uid, PointingArrowComponent component, AnimationCompletedEvent args)
{
if (args.Key == component.AnimationKey)
_animationPlayer.Stop(uid, component.AnimationKey);
}
private void BeginPointAnimation(EntityUid uid, Vector2 startPosition, Vector2 offset, string animationKey)
{
if (_animationPlayer.HasRunningAnimation(uid, animationKey))
return;
var animation = new Animation
{
Length = PointDuration,
AnimationTracks =
{
new AnimationTrackComponentProperty
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
InterpolationMode = AnimationInterpolationMode.Cubic,
KeyFrames =
{
// We pad here to prevent improper looping and tighten the overshoot, just a touch
new AnimationTrackProperty.KeyFrame(startPosition, 0f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startPosition, offset, 0.9f), PointKeyTimeMove),
new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeMove),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeMove),
new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover),
}
}
}
};
_animationPlayer.Play(uid, animation, animationKey);
}
}

View File

@ -1,32 +1,25 @@
using Content.Client.Pointing.Components;
using Content.Client.Gravity;
using Content.Shared.Mobs.Systems;
using Content.Shared.Pointing;
using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
namespace Content.Client.Pointing;
public sealed class PointingSystem : SharedPointingSystem
public sealed partial class PointingSystem : SharedPointingSystem
{
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly FloatingVisualizerSystem _floatingSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddPointingVerb);
SubscribeLocalEvent<PointingArrowComponent, ComponentStartup>(OnArrowStartup);
SubscribeLocalEvent<PointingArrowComponent, AnimationCompletedEvent>(OnArrowAnimation);
SubscribeLocalEvent<RoguePointingArrowComponent, ComponentStartup>(OnRogueArrowStartup);
}
SubscribeLocalEvent<PointingArrowComponent, ComponentHandleState>(HandleCompState);
private void OnArrowAnimation(EntityUid uid, PointingArrowComponent component, AnimationCompletedEvent args)
{
_floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime);
InitializeVisualizer();
}
private void AddPointingVerb(GetVerbsEvent<Verb> args)
@ -38,15 +31,11 @@ public sealed class PointingSystem : SharedPointingSystem
// I'm just adding this verb exclusively to clients so that the verb-loading pop-in on the verb menu isn't
// as bad. Important for this verb seeing as its usually an option on just about any entity.
// this is a pointing arrow. no pointing here...
if (HasComp<PointingArrowComponent>(args.Target))
{
// this is a pointing arrow. no pointing here...
return;
}
// Can the user point? Checking mob state directly instead of some action blocker, as many action blockers are blocked for
// ghosts and there is no obvious choice for pointing (unless ghosts CanEmote?).
if (_mobState.IsIncapacitated(args.User))
if (!CanPoint(args.User))
return;
// We won't check in range or visibility, as this verb is currently only executable via the context menu,
@ -66,11 +55,9 @@ public sealed class PointingSystem : SharedPointingSystem
private void OnArrowStartup(EntityUid uid, PointingArrowComponent component, ComponentStartup args)
{
if (TryComp<SpriteComponent>(uid, out var sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
_floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime);
BeginPointAnimation(uid, component.StartPosition, component.Offset, component.AnimationKey);
}
private void OnRogueArrowStartup(EntityUid uid, RoguePointingArrowComponent arrow, ComponentStartup args)
@ -81,4 +68,13 @@ public sealed class PointingSystem : SharedPointingSystem
sprite.NoRotation = false;
}
}
private void HandleCompState(Entity<PointingArrowComponent> entity, ref ComponentHandleState args)
{
if (args.Current is not SharedPointingArrowComponentState state)
return;
entity.Comp.StartPosition = state.StartPosition;
entity.Comp.EndTime = state.EndTime;
}
}

View File

@ -55,8 +55,11 @@ namespace Content.Client.Popups
.RemoveOverlay<PopupOverlay>();
}
private void PopupMessage(string message, PopupType type, EntityCoordinates coordinates, EntityUid? entity, bool recordReplay)
private void PopupMessage(string? message, PopupType type, EntityCoordinates coordinates, EntityUid? entity, bool recordReplay)
{
if (message == null)
return;
if (recordReplay && _replayRecording.IsRecording)
{
if (entity != null)
@ -75,25 +78,28 @@ namespace Content.Client.Popups
}
#region Abstract Method Implementations
public override void PopupCoordinates(string message, EntityCoordinates coordinates, PopupType type = PopupType.Small)
public override void PopupCoordinates(string? message, EntityCoordinates coordinates, PopupType type = PopupType.Small)
{
PopupMessage(message, type, coordinates, null, true);
}
public override void PopupCoordinates(string message, EntityCoordinates coordinates, ICommonSession recipient, PopupType type = PopupType.Small)
public override void PopupCoordinates(string? message, EntityCoordinates coordinates, ICommonSession recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.Session == recipient)
if (_playerManager.LocalSession == recipient)
PopupMessage(message, type, coordinates, null, true);
}
public override void PopupCoordinates(string message, EntityCoordinates coordinates, EntityUid recipient, PopupType type = PopupType.Small)
public override void PopupCoordinates(string? message, EntityCoordinates coordinates, EntityUid recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.ControlledEntity == recipient)
if (_playerManager.LocalEntity == recipient)
PopupMessage(message, type, coordinates, null, true);
}
private void PopupCursorInternal(string message, PopupType type, bool recordReplay)
private void PopupCursorInternal(string? message, PopupType type, bool recordReplay)
{
if (message == null)
return;
if (recordReplay && _replayRecording.IsRecording)
_replayRecording.RecordClientMessage(new PopupCursorEvent(message, type));
@ -106,53 +112,53 @@ namespace Content.Client.Popups
_aliveCursorLabels.Add(label);
}
public override void PopupCursor(string message, PopupType type = PopupType.Small)
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
=> PopupCursorInternal(message, type, true);
public override void PopupCursor(string message, ICommonSession recipient, PopupType type = PopupType.Small)
public override void PopupCursor(string? message, ICommonSession recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.Session == recipient)
if (_playerManager.LocalSession == recipient)
PopupCursor(message, type);
}
public override void PopupCursor(string message, EntityUid recipient, PopupType type = PopupType.Small)
public override void PopupCursor(string? message, EntityUid recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.ControlledEntity == recipient)
if (_playerManager.LocalEntity == recipient)
PopupCursor(message, type);
}
public override void PopupCoordinates(string message, EntityCoordinates coordinates, Filter filter, bool replayRecord, PopupType type = PopupType.Small)
public override void PopupCoordinates(string? message, EntityCoordinates coordinates, Filter filter, bool replayRecord, PopupType type = PopupType.Small)
{
PopupCoordinates(message, coordinates, type);
}
public override void PopupEntity(string message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
public override void PopupEntity(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.ControlledEntity == recipient)
if (_playerManager.LocalEntity == recipient)
PopupEntity(message, uid, type);
}
public override void PopupEntity(string message, EntityUid uid, ICommonSession recipient, PopupType type = PopupType.Small)
public override void PopupEntity(string? message, EntityUid uid, ICommonSession recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.Session == recipient)
if (_playerManager.LocalSession == recipient)
PopupEntity(message, uid, type);
}
public override void PopupEntity(string message, EntityUid uid, Filter filter, bool recordReplay, PopupType type=PopupType.Small)
public override void PopupEntity(string? message, EntityUid uid, Filter filter, bool recordReplay, PopupType type=PopupType.Small)
{
if (!filter.Recipients.Contains(_playerManager.LocalPlayer?.Session))
if (!filter.Recipients.Contains(_playerManager.LocalSession))
return;
PopupEntity(message, uid, type);
}
public override void PopupClient(string message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
public override void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
{
if (_timing.IsFirstTimePredicted)
PopupEntity(message, uid, recipient, type);
}
public override void PopupEntity(string message, EntityUid uid, PopupType type = PopupType.Small)
public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small)
{
if (TryComp(uid, out TransformComponent? transform))
PopupMessage(message, type, transform.Coordinates, uid, true);

View File

@ -135,6 +135,14 @@ public sealed partial class GeneratorWindow : FancyWindow
private bool TryGetStartProgress(out float progress)
{
// Try to check progress of auto-revving first
if (_entityManager.TryGetComponent<ActiveGeneratorRevvingComponent>(_entity, out var activeGeneratorRevvingComponent) && _entityManager.TryGetComponent<PortableGeneratorComponent>(_entity, out var portableGeneratorComponent))
{
var calculatedProgress = activeGeneratorRevvingComponent.CurrentTime / portableGeneratorComponent.StartTime;
progress = (float) calculatedProgress;
return true;
}
var doAfterSystem = _entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
return doAfterSystem.TryFindActiveDoAfter<GeneratorStartedEvent>(_entity, out _, out _, out progress);
}

View File

@ -96,6 +96,12 @@
<Control HorizontalExpand="True"/>
<OptionButton Name="CBackpackButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Spawn Priority -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-spawn-priority-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="CSpawnPriorityButton" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
<!-- Skin -->
<BoxContainer Margin="10" HorizontalExpand="True" Orientation="Vertical">

View File

@ -72,6 +72,7 @@ namespace Content.Client.Preferences.UI
private Slider _skinColor => CSkin;
private OptionButton _clothingButton => CClothingButton;
private OptionButton _backpackButton => CBackpackButton;
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
private EyeColorPicker _eyesPicker => CEyeColorPicker;
@ -340,6 +341,21 @@ namespace Content.Client.Preferences.UI
#endregion Backpack
#region SpawnPriority
foreach (var value in Enum.GetValues<SpawnPriorityPreference>())
{
_spawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value);
}
_spawnPriorityButton.OnItemSelected += args =>
{
_spawnPriorityButton.SelectId(args.Id);
SetSpawnPriority((SpawnPriorityPreference) args.Id);
};
#endregion SpawnPriority
#region Eyes
_eyesPicker.OnEyeColorPicked += newColor =>
@ -831,6 +847,12 @@ namespace Content.Client.Preferences.UI
IsDirty = true;
}
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
{
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
IsDirty = true;
}
public void Save()
{
IsDirty = false;
@ -1006,6 +1028,16 @@ namespace Content.Client.Preferences.UI
_backpackButton.SelectId((int) Profile.Backpack);
}
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
{
return;
}
_spawnPriorityButton.SelectId((int) Profile.SpawnPriority);
}
private void UpdateHairPickers()
{
if (Profile == null)
@ -1145,6 +1177,7 @@ namespace Content.Client.Preferences.UI
UpdateSpecies();
UpdateClothingControls();
UpdateBackpackControls();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();

View File

@ -1,5 +1,6 @@
using Content.Shared.Antag;
using Content.Shared.Revolutionary.Components;
using Content.Client.Antag;
using Content.Shared.Ghost;
using Content.Shared.StatusIcon.Components;
namespace Content.Client.Revolutionary;
@ -7,29 +8,37 @@ namespace Content.Client.Revolutionary;
/// <summary>
/// Used for the client to get status icons from other revs.
/// </summary>
public sealed class RevolutionarySystem : AntagStatusIconSystem<RevolutionaryComponent>
public sealed class RevolutionarySystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetHeadRevIcon);
SubscribeLocalEvent<RevolutionaryComponent, CanDisplayStatusIconsEvent>(OnCanShowRevIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, CanDisplayStatusIconsEvent>(OnCanShowRevIcon);
}
/// <summary>
/// Checks if the person who triggers the GetStatusIcon event is also a Rev or a HeadRev.
/// Determine whether a client should display the rev icon.
/// </summary>
private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent args)
private void OnCanShowRevIcon<T>(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent
{
if (!HasComp<HeadRevolutionaryComponent>(uid))
{
GetStatusIcon(comp.RevStatusIcon, ref args);
}
args.Cancelled = !CanDisplayIcon(args.User, comp.IconVisibleToGhost);
}
private void GetHeadRevIcon(EntityUid uid, HeadRevolutionaryComponent comp, ref GetStatusIconsEvent args)
/// <summary>
/// The criteria that determine whether a client should see Rev/Head rev icons.
/// </summary>
private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost)
{
GetStatusIcon(comp.HeadRevStatusIcon, ref args);
if (HasComp<HeadRevolutionaryComponent>(uid) || HasComp<RevolutionaryComponent>(uid))
return true;
if (visibleToGhost && HasComp<GhostComponent>(uid))
return true;
return HasComp<ShowRevIconsComponent>(uid);
}
}

View File

@ -37,7 +37,7 @@ namespace Content.Client.RoundEnd
Contents.AddChild(roundEndTabs);
OpenCentered();
OpenCenteredRight();
MoveToFront();
}

View File

@ -1,4 +1,7 @@
using Content.Shared.CCVar;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC;
using Content.Shared.SSDIndicator;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
@ -14,6 +17,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
@ -24,11 +28,15 @@ public sealed class SSDIndicatorSystem : EntitySystem
private void OnGetStatusIcon(EntityUid uid, SSDIndicatorComponent component, ref GetStatusIconsEvent args)
{
if (!component.IsSSD ||
!_cfg.GetCVar(CCVars.ICShowSSDIndicator) ||
args.InContainer)
return;
args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(component.Icon));
if (component.IsSSD &&
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
!args.InContainer &&
!_mobState.IsDead(uid) &&
!HasComp<ActiveNPCComponent>(uid) &&
TryComp<MindContainerComponent>(uid, out var mindContainer) &&
mindContainer.ShowExamineInfo)
{
args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(component.Icon));
}
}
}

View File

@ -14,29 +14,31 @@ public sealed class SprayPainterSystem : SharedSprayPainterSystem
public List<SprayPainterEntry> Entries { get; private set; } = new();
public override void Initialize()
protected override void CacheStyles()
{
base.Initialize();
base.CacheStyles();
foreach (string style in Styles)
Entries.Clear();
foreach (var style in Styles)
{
var name = style.Name;
string? iconPath = Groups
.FindAll(x => x.StylePaths.ContainsKey(style))?
.MaxBy(x => x.IconPriority)?.StylePaths[style];
.FindAll(x => x.StylePaths.ContainsKey(name))?
.MaxBy(x => x.IconPriority)?.StylePaths[name];
if (iconPath == null)
{
Entries.Add(new SprayPainterEntry(style, null));
Entries.Add(new SprayPainterEntry(name, null));
continue;
}
RSIResource doorRsi = _resourceCache.GetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / new ResPath(iconPath));
if (!doorRsi.RSI.TryGetState("closed", out var icon))
{
Entries.Add(new SprayPainterEntry(style, null));
Entries.Add(new SprayPainterEntry(name, null));
continue;
}
Entries.Add(new SprayPainterEntry(style, icon.Frame0));
Entries.Add(new SprayPainterEntry(name, icon.Frame0));
}
}
}

View File

@ -1,4 +1,5 @@
using Content.Shared.SprayPainter;
using Content.Shared.SprayPainter.Components;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
@ -20,14 +21,20 @@ public sealed class SprayPainterBoundUserInterface : BoundUserInterface
{
base.Open();
if (!EntMan.TryGetComponent<SprayPainterComponent>(Owner, out var comp))
return;
_window = new SprayPainterWindow();
_painter = EntMan.System<SprayPainterSystem>();
_window.OpenCentered();
_window.OnClose += Close;
_window.OnSpritePicked = OnSpritePicked;
_window.OnColorPicked = OnColorPicked;
_window.Populate(_painter.Entries, comp.Index, comp.PickedColor, comp.ColorPalette);
_window.OpenCentered();
}
protected override void Dispose(bool disposing)
@ -37,25 +44,6 @@ public sealed class SprayPainterBoundUserInterface : BoundUserInterface
_window?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_window == null)
return;
if (_painter == null)
return;
if (state is not SprayPainterBoundUserInterfaceState stateCast)
return;
_window.Populate(_painter.Entries,
stateCast.SelectedStyle,
stateCast.SelectedColorKey,
stateCast.Palette);
}
private void OnSpritePicked(ItemList.ItemListSelectedEventArgs args)
{
SendMessage(new SprayPainterSpritePickedMessage(args.ItemIndex));

View File

@ -1,5 +1,4 @@
using Content.Shared.StationRecords;
using Robust.Client.GameObjects;
namespace Content.Client.StationRecords;
@ -17,33 +16,21 @@ public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInt
base.Open();
_window = new();
_window.OnKeySelected += OnKeySelected;
_window.OnFiltersChanged += OnFiltersChanged;
_window.OnKeySelected += key =>
SendMessage(new SelectStationRecord(key));
_window.OnFiltersChanged += (type, filterValue) =>
SendMessage(new SetStationRecordFilter(type, filterValue));
_window.OnClose += Close;
_window.OpenCentered();
}
private void OnKeySelected((NetEntity, uint)? key)
{
SendMessage(new SelectGeneralStationRecord(key));
}
private void OnFiltersChanged(
GeneralStationRecordFilterType type, string filterValue)
{
GeneralStationRecordsFilterMsg msg = new(type, filterValue);
SendMessage(msg);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not GeneralStationRecordConsoleState cast)
{
return;
}
_window?.UpdateState(cast);
}

View File

@ -1,4 +1,3 @@
using System.Linq;
using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
@ -11,31 +10,29 @@ namespace Content.Client.StationRecords;
[GenerateTypedNameReferences]
public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
{
public Action<(NetEntity, uint)?>? OnKeySelected;
public Action<uint?>? OnKeySelected;
public Action<GeneralStationRecordFilterType, string>? OnFiltersChanged;
public Action<StationRecordFilterType, string>? OnFiltersChanged;
private bool _isPopulating;
private GeneralStationRecordFilterType _currentFilterType;
private StationRecordFilterType _currentFilterType;
public GeneralStationRecordConsoleWindow()
{
RobustXamlLoader.Load(this);
_currentFilterType = GeneralStationRecordFilterType.Name;
_currentFilterType = StationRecordFilterType.Name;
foreach (var item in Enum.GetValues<GeneralStationRecordFilterType>())
foreach (var item in Enum.GetValues<StationRecordFilterType>())
{
StationRecordsFilterType.AddItem(GetTypeFilterLocals(item), (int)item);
}
RecordListing.OnItemSelected += args =>
{
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not ValueTuple<NetEntity, uint> cast)
{
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast)
return;
}
OnKeySelected?.Invoke(cast);
};
@ -48,7 +45,7 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
StationRecordsFilterType.OnItemSelected += eventArgs =>
{
var type = (GeneralStationRecordFilterType)eventArgs.Id;
var type = (StationRecordFilterType) eventArgs.Id;
if (_currentFilterType != type)
{
@ -123,7 +120,7 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
RecordContainer.RemoveAllChildren();
}
}
private void PopulateRecordListing(Dictionary<(NetEntity, uint), string> listing, (NetEntity, uint)? selected)
private void PopulateRecordListing(Dictionary<uint, string> listing, uint? selected)
{
RecordListing.Clear();
RecordListing.ClearSelected();
@ -134,10 +131,7 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
{
var item = RecordListing.AddItem(name);
item.Metadata = key;
if (selected != null && key.Item1 == selected.Value.Item1 && key.Item2 == selected.Value.Item2)
{
item.Selected = true;
}
item.Selected = key == selected;
}
_isPopulating = false;
@ -197,7 +191,7 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
}
}
private string GetTypeFilterLocals(GeneralStationRecordFilterType type)
private string GetTypeFilterLocals(StationRecordFilterType type)
{
return Loc.GetString($"general-station-record-{type.ToString().ToLower()}-filter");
}

View File

@ -17,6 +17,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EntityStorageComponent, EntityUnpausedEvent>(OnEntityUnpausedEvent);
SubscribeLocalEvent<EntityStorageComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<EntityStorageComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<EntityStorageComponent, ActivateInWorldEvent>(OnInteract, after: new[] { typeof(LockSystem) });

View File

@ -48,6 +48,11 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
{
SendMessage(new StoreRequestUpdateInterfaceMessage());
};
_menu.OnRefundAttempt += (_) =>
{
SendMessage(new StoreRequestRefundMessage());
};
}
protected override void UpdateState(BoundUserInterfaceState state)
{
@ -64,6 +69,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
_menu.UpdateListing(msg.Listings.ToList());
_menu.SetFooterVisibility(msg.ShowFooter);
_menu.UpdateRefund(msg.AllowRefund);
break;
case StoreInitializeState msg:
_windowName = msg.Name;

View File

@ -22,6 +22,11 @@
MinWidth="64"
HorizontalAlignment="Right"
Text="{Loc 'store-ui-default-withdraw-text'}" />
<Button
Name="RefundButton"
MinWidth="64"
HorizontalAlignment="Right"
Text="Refund" />
</BoxContainer>
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>

View File

@ -31,6 +31,7 @@ public sealed partial class StoreMenu : DefaultWindow
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
public event Action<BaseButton.ButtonEventArgs>? OnRefreshButtonPressed;
public event Action<BaseButton.ButtonEventArgs>? OnRefundAttempt;
public Dictionary<string, FixedPoint2> Balance = new();
public string CurrentCategory = string.Empty;
@ -44,6 +45,8 @@ public sealed partial class StoreMenu : DefaultWindow
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
RefreshButton.OnButtonDown += OnRefreshButtonDown;
RefundButton.OnButtonDown += OnRefundButtonDown;
if (Window != null)
Window.Title = name;
}
@ -116,6 +119,11 @@ public sealed partial class StoreMenu : DefaultWindow
_withdrawWindow.OnWithdrawAttempt += OnWithdrawAttempt;
}
private void OnRefundButtonDown(BaseButton.ButtonEventArgs args)
{
OnRefundAttempt?.Invoke(args);
}
private void AddListingGui(ListingData listing)
{
if (!listing.Categories.Contains(CurrentCategory))
@ -262,6 +270,11 @@ public sealed partial class StoreMenu : DefaultWindow
_withdrawWindow?.Close();
}
public void UpdateRefund(bool allowRefund)
{
RefundButton.Disabled = !allowRefund;
}
private sealed class StoreCategoryButton : Button
{
public string? Id;

View File

@ -31,7 +31,7 @@ namespace Content.Client.Stylesheets
{
$"/Fonts/NotoSans{ds}/NotoSans{ds}-{variation}.ttf",
$"/Fonts/NotoSans/NotoSansSymbols-{sv}.ttf",
"/Fonts/NotoSans/NotoSansSymbols2-Regular.ttf",
"/Fonts/NotoSans/NotoSansSymbols2-Regular.ttf"
},
size
);

View File

@ -0,0 +1,87 @@
using Content.Shared.Throwing;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
namespace Content.Client.Throwing;
/// <summary>
/// Handles animating thrown items.
/// </summary>
public sealed class ThrownItemVisualizerSystem : EntitySystem
{
[Dependency] private readonly AnimationPlayerSystem _anim = default!;
private const string AnimationKey = "thrown-item";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ThrownItemComponent, AfterAutoHandleStateEvent>(OnAutoHandleState);
SubscribeLocalEvent<ThrownItemComponent, ComponentShutdown>(OnShutdown);
}
private void OnAutoHandleState(EntityUid uid, ThrownItemComponent component, ref AfterAutoHandleStateEvent args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_anim.HasRunningAnimation(uid, animationPlayer, AnimationKey))
return;
var anim = GetAnimation((uid, component, sprite));
if (anim == null)
return;
component.OriginalScale = sprite.Scale;
_anim.Play((uid, animationPlayer), anim, AnimationKey);
}
private void OnShutdown(EntityUid uid, ThrownItemComponent component, ComponentShutdown args)
{
if (!_anim.HasRunningAnimation(uid, AnimationKey))
return;
if (TryComp<SpriteComponent>(uid, out var sprite) && component.OriginalScale != null)
sprite.Scale = component.OriginalScale.Value;
_anim.Stop(uid, AnimationKey);
}
private static Animation? GetAnimation(Entity<ThrownItemComponent, SpriteComponent> ent)
{
if (ent.Comp1.LandTime - ent.Comp1.ThrownTime is not { } length)
return null;
if (length <= TimeSpan.Zero)
return null;
length += TimeSpan.FromSeconds(ThrowingSystem.FlyTime);
var scale = ent.Comp2.Scale;
var lenFloat = (float) length.TotalSeconds;
// TODO use like actual easings here
return new Animation
{
Length = length,
AnimationTracks =
{
new AnimationTrackComponentProperty
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Scale),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(scale, 0.0f),
new AnimationTrackProperty.KeyFrame(scale * 1.4f, lenFloat * 0.25f),
new AnimationTrackProperty.KeyFrame(scale, lenFloat * 0.75f)
},
InterpolationMode = AnimationInterpolationMode.Linear
}
}
};
}
}

View File

@ -17,11 +17,11 @@ public sealed class WelderStatusControl : Control
private readonly ItemToggleComponent? _toggleComponent;
private readonly RichTextLabel _label;
public WelderStatusControl(WelderComponent parent, EntityUid? uid = null)
public WelderStatusControl(Entity<WelderComponent> parent)
{
_parent = parent;
_entMan = IoCManager.Resolve<IEntityManager>();
if (_entMan.TryGetComponent<ItemToggleComponent>(uid, out var itemToggle))
if (_entMan.TryGetComponent<ItemToggleComponent>(parent, out var itemToggle))
_toggleComponent = itemToggle;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);

View File

@ -0,0 +1,9 @@
<controls:FancyWindow xmlns="https://spacestation14.io" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical" Margin="8">
<BoxContainer Name="Prompts" Orientation="Vertical"/> <!-- Populated in constructor -->
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="OkButton" Text="{Loc 'quick-dialog-ui-ok'}"/>
<Button Name="CancelButton" Text="{Loc 'quick-dialog-ui-cancel'}"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@ -0,0 +1,147 @@
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.UserInterface.Controls;
// mfw they ported input() from BYOND
/// <summary>
/// Client-side dialog with multiple prompts.
/// Used by admin tools quick dialog system among other things.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class DialogWindow : FancyWindow
{
/// <summary>
/// Action for when the ok button is pressed or the last field has enter pressed.
/// Results maps prompt FieldIds to the LineEdit's text contents.
/// </summary>
public Action<Dictionary<string, string>>? OnConfirmed;
/// <summary>
/// Action for when the cancel button is pressed or the window is closed.
/// </summary>
public Action? OnCancelled;
/// <summary>
/// Used to ensure that only one output action is invoked.
/// E.g. Pressing cancel will invoke then close the window, but OnClose will not invoke.
/// </summary>
private bool _finished;
private List<(string, LineEdit)> _promptLines;
/// <summary>
/// Create and open a new dialog with some prompts.
/// </summary>
/// <param name="title">String to use for the window title.</param>
/// <param name="entries">Quick dialog entries to create prompts with.</param>
/// <param name="ok">Whether to have an Ok button.</param>
/// <param name="cancel">Whether to have a Cancel button. Closing the window will still cancel it.</param>
/// <remarks>
/// Won't do anything on its own, you need to handle or network with <see cref="OnConfirmed"/> and <see cref="OnCancelled"/>.
/// </remarks>
public DialogWindow(string title, List<QuickDialogEntry> entries, bool ok = true, bool cancel = true)
{
RobustXamlLoader.Load(this);
Title = title;
OkButton.Visible = ok;
CancelButton.Visible = cancel;
_promptLines = new(entries.Count);
for (int i = 0; i < entries.Count; i++)
{
var entry = entries[i];
var box = new BoxContainer();
box.AddChild(new Label() { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f });
var edit = new LineEdit() { HorizontalExpand = true };
(Func<string, bool>, string) pair = entry.Type switch
{
QuickDialogEntryType.Integer => (VerifyInt, "integer"),
QuickDialogEntryType.Float => (VerifyFloat, "float"),
QuickDialogEntryType.ShortText => (VerifyShortText, "short-text"),
QuickDialogEntryType.LongText => (VerifyLongText, "long-text"),
_ => throw new ArgumentOutOfRangeException()
};
var (valid, name) = pair;
edit.IsValid += valid;
// try use placeholder from the caller, fall back to the generic one for whatever type is being validated.
edit.PlaceHolder = entry.Placeholder ?? Loc.GetString($"quick-dialog-ui-{name}");
// Last text box gets enter confirmation.
// Only the last so you don't accidentally confirm early.
if (i == entries.Count - 1)
edit.OnTextEntered += _ => Confirm();
_promptLines.Add((entry.FieldId, edit));
box.AddChild(edit);
Prompts.AddChild(box);
}
OkButton.OnPressed += _ => Confirm();
CancelButton.OnPressed += _ =>
{
_finished = true;
OnCancelled?.Invoke();
Close();
};
OnClose += () =>
{
if (!_finished)
OnCancelled?.Invoke();
};
MinWidth *= 2; // Just double it.
OpenCentered();
}
private void Confirm()
{
var results = new Dictionary<string, string>();
foreach (var (field, edit) in _promptLines)
{
results[field] = edit.Text;
}
_finished = true;
OnConfirmed?.Invoke(results);
Close();
}
#region Input validation
private bool VerifyInt(string input)
{
return int.TryParse(input, out var _);
}
private bool VerifyFloat(string input)
{
return float.TryParse(input, out var _);
}
private bool VerifyShortText(string input)
{
return input.Length <= 100;
}
private bool VerifyLongText(string input)
{
return input.Length <= 2000;
}
#endregion
}

View File

@ -1,80 +0,0 @@
using Content.Shared.Vehicle;
using Content.Shared.Vehicle.Components;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
namespace Content.Client.Vehicle;
public sealed class VehicleSystem : SharedVehicleSystem
{
[Dependency] private EyeSystem _eye = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
SubscribeLocalEvent<VehicleComponent, AppearanceChangeEvent>(OnVehicleAppearanceChange);
}
private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
{
// Center the player's eye on the vehicle
if (TryComp(uid, out EyeComponent? eyeComp))
{
_eye.SetTarget(uid, eyeComp.Target ?? component.Vehicle, eyeComp);
}
}
private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
{
// reset the riders eye centering.
if (TryComp(uid, out EyeComponent? eyeComp))
{
_eye.SetTarget(uid, null, eyeComp);
}
}
private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
{
if (args.Current is not RiderComponentState state)
return;
var entity = EnsureEntity<RiderComponent>(state.Entity, uid);
if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
{
_eye.SetTarget(uid, entity, eyeComp);
}
component.Vehicle = entity;
}
private void OnVehicleAppearanceChange(EntityUid uid, VehicleComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (component.HideRider
&& Appearance.TryGetData<bool>(uid, VehicleVisuals.HideRider, out var hide, args.Component)
&& TryComp<SpriteComponent>(component.LastRider, out var riderSprite))
riderSprite.Visible = !hide;
// First check is for the sprite itself
if (Appearance.TryGetData<int>(uid, VehicleVisuals.DrawDepth, out var drawDepth, args.Component))
args.Sprite.DrawDepth = drawDepth;
// Set vehicle layer to animated or not (i.e. are the wheels turning or not)
if (component.AutoAnimate
&& Appearance.TryGetData<bool>(uid, VehicleVisuals.AutoAnimate, out var autoAnimate, args.Component))
args.Sprite.LayerSetAutoAnimated(VehicleVisualLayers.AutoAnimate, autoAnimate);
}
}
public enum VehicleVisualLayers : byte
{
/// Layer for the vehicle's wheels
AutoAnimate,
}

View File

@ -36,7 +36,7 @@ namespace Content.Client.VendingMachines
_menu.Populate(_cachedInventory, out _cachedFilteredIndex);
_menu.OpenCentered();
_menu.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)

View File

@ -56,11 +56,11 @@ public sealed class GunSpreadOverlay : Overlay
return;
// (☞゚ヮ゚)☞
var maxSpread = gun.MaxAngle;
var minSpread = gun.MinAngle;
var maxSpread = gun.MaxAngleModified;
var minSpread = gun.MinAngleModified;
var timeSinceLastFire = (_timing.CurTime - gun.NextFire).TotalSeconds;
var currentAngle = new Angle(MathHelper.Clamp(gun.CurrentAngle.Theta - gun.AngleDecay.Theta * timeSinceLastFire,
gun.MinAngle.Theta, gun.MaxAngle.Theta));
var currentAngle = new Angle(MathHelper.Clamp(gun.CurrentAngle.Theta - gun.AngleDecayModified.Theta * timeSinceLastFire,
gun.MinAngleModified.Theta, gun.MaxAngleModified.Theta));
var direction = (mousePos.Position - mapPos.Position);
worldHandle.DrawLine(mapPos.Position, mousePos.Position + direction, Color.Orange);

View File

@ -3,7 +3,6 @@ using Content.Client.Items;
using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Camera;
using Content.Shared.CombatMode;
using Robust.Shared.Spawners;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
@ -195,7 +194,7 @@ public sealed partial class GunSystem : SharedGunSystem
{
if (throwItems)
{
Recoil(user, direction, gun.CameraRecoilScalar);
Recoil(user, direction, gun.CameraRecoilScalarModified);
if (IsClientSide(ent!.Value))
Del(ent.Value);
else
@ -210,8 +209,8 @@ public sealed partial class GunSystem : SharedGunSystem
{
SetCartridgeSpent(ent!.Value, cartridge, true);
MuzzleFlash(gunUid, cartridge, user);
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalar);
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalarModified);
// TODO: Can't predict entity deletions.
//if (cartridge.DeleteOnSpawn)
// Del(cartridge.Owner);
@ -228,16 +227,16 @@ public sealed partial class GunSystem : SharedGunSystem
break;
case AmmoComponent newAmmo:
MuzzleFlash(gunUid, newAmmo, user);
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalar);
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalarModified);
if (IsClientSide(ent!.Value))
Del(ent.Value);
else
RemoveShootable(ent.Value);
break;
case HitscanPrototype:
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalar);
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalarModified);
break;
}
}

View File

@ -1,5 +1,5 @@
using System.Linq;
using Content.Client.Antag;
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
@ -7,15 +7,14 @@ using Robust.Client.GameObjects;
namespace Content.Client.Zombies;
public sealed class ZombieSystem : AntagStatusIconSystem<ZombieComponent>
public sealed class ZombieSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ZombieComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(OnGetStatusIcon);
SubscribeLocalEvent<ZombieComponent, CanDisplayStatusIconsEvent>(OnCanDisplayStatusIcons);
}
private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args)
@ -32,8 +31,17 @@ public sealed class ZombieSystem : AntagStatusIconSystem<ZombieComponent>
}
}
private void OnGetStatusIcon(EntityUid uid, ZombieComponent component, ref GetStatusIconsEvent args)
/// <summary>
/// Determines whether a player should be able to see the StatusIcon for zombies.
/// </summary>
private void OnCanDisplayStatusIcons(EntityUid uid, ZombieComponent component, ref CanDisplayStatusIconsEvent args)
{
GetStatusIcon(component.ZombieStatusIcon, ref args);
if (HasComp<ZombieComponent>(args.User) || HasComp<ShowZombieIconsComponent>(args.User))
return;
if (component.IconVisibleToGhost && HasComp<GhostComponent>(args.User))
return;
args.Cancelled = true;
}
}

View File

@ -21,7 +21,10 @@ public sealed class SecretStartsTest
await server.WaitAssertion(() =>
{
gameTicker.StartGameRule("Secret");
// this mimics roundflow:
// rules added, then round starts
gameTicker.AddGameRule("Secret");
gameTicker.StartGamePresetRules();
});
// Wait three ticks for any random update loops that might happen

View File

@ -1,5 +1,6 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Server.Explosion.Components;
using Content.Shared.Explosion.Components;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;

View File

@ -56,6 +56,7 @@ namespace Content.IntegrationTests.Tests.Preferences
),
ClothingPreference.Jumpskirt,
BackpackPreference.Backpack,
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}

View File

@ -0,0 +1,45 @@
using Content.Shared.Hands.Components;
using Content.Shared.Prototypes;
using Content.Shared.Pulling.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Puller;
#nullable enable
[TestFixture]
public sealed class PullerTest
{
/// <summary>
/// Checks that needsHands on PullerComponent is not set on mobs that don't even have hands.
/// </summary>
[Test]
public async Task PullerSanityTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var compFactory = server.ResolveDependency<IComponentFactory>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
{
if (!proto.TryGetComponent(out SharedPullerComponent? puller))
continue;
if (!puller.NeedsHands)
continue;
Assert.That(proto.HasComponent<HandsComponent>(compFactory), $"Found puller {proto} with NeedsHand pulling but has no hands?");
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@ -0,0 +1,81 @@
using Content.Server.Storage.EntitySystems;
using Content.Shared.Damage;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
namespace Content.IntegrationTests.Tests.Storage;
[TestFixture]
public sealed class EntityStorageTests
{
[TestPrototypes]
private const string Prototypes = @"
- type: entity
id: EntityStorageTest
name: box
components:
- type: EntityStorage
- type: Damageable
damageContainer: Inorganic
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 10
behaviors:
- !type:DoActsBehavior
acts: [ Destruction ]
";
[Test]
public async Task TestContainerDestruction()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var map = await pair.CreateTestMap();
EntityUid box = default;
EntityUid crowbar = default;
await server.WaitPost(() => box = server.EntMan.SpawnEntity("EntityStorageTest", map.GridCoords));
await server.WaitPost(() => crowbar = server.EntMan.SpawnEntity("Crowbar", map.GridCoords));
// Initially the crowbar is not in a contaienr.
var sys = server.System<SharedContainerSystem>();
Assert.That(sys.IsEntityInContainer(crowbar), Is.False);
// Open then close the storage entity
var storage = server.System<EntityStorageSystem>();
await server.WaitPost(() =>
{
storage.OpenStorage(box);
storage.CloseStorage(box);
});
// Crowbar is now in the box
Assert.That(sys.IsEntityInContainer(crowbar));
// Damage the box
var damage = new DamageSpecifier();
damage.DamageDict.Add("Blunt", 100);
await server.WaitPost(() => server.System<DamageableSystem>().TryChangeDamage(box, damage));
// Box has been destroyed, contents have been emptied. Destruction uses deffered deletion.
Assert.That(server.EntMan.IsQueuedForDeletion(box));
Assert.That(sys.IsEntityInContainer(crowbar), Is.False);
// Opening and closing the soon-to-be-deleted box should not re-insert the crowbar
await server.WaitPost(() =>
{
storage.OpenStorage(box);
storage.CloseStorage(box);
});
Assert.That(sys.IsEntityInContainer(crowbar), Is.False);
// Entity gets deleted after a few ticks
await server.WaitRunTicks(5);
Assert.That(server.EntMan.Deleted(box));
Assert.That(server.EntMan.Deleted(crowbar), Is.False);
await pair.CleanReturnAsync();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class SpawnPriorityPreference : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "spawn_priority",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "spawn_priority",
table: "profile");
}
}
}

View File

@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using NpgsqlTypes;
#nullable disable
@ -19,7 +20,7 @@ namespace Content.Server.Database.Migrations.Postgres
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.4")
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@ -803,6 +804,10 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("integer")
.HasColumnName("slot");
b.Property<int>("SpawnPriority")
.HasColumnType("integer")
.HasColumnName("spawn_priority");
b.Property<string>("Species")
.IsRequired()
.HasColumnType("text")
@ -879,7 +884,7 @@ namespace Content.Server.Database.Migrations.Postgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<ValueTuple<IPAddress, int>?>("Address")
b.Property<NpgsqlInet?>("Address")
.HasColumnType("inet")
.HasColumnName("address");
@ -1021,7 +1026,7 @@ namespace Content.Server.Database.Migrations.Postgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<ValueTuple<IPAddress, int>?>("Address")
b.Property<NpgsqlInet?>("Address")
.HasColumnType("inet")
.HasColumnName("address");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class SpawnPriorityPreference : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "spawn_priority",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "spawn_priority",
table: "profile");
}
}
}

View File

@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
@ -757,6 +757,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("slot");
b.Property<int>("SpawnPriority")
.HasColumnType("INTEGER")
.HasColumnName("spawn_priority");
b.Property<string>("Species")
.IsRequired()
.HasColumnType("TEXT")

View File

@ -338,6 +338,7 @@ namespace Content.Server.Database
public string SkinColor { get; set; } = null!;
public string Clothing { get; set; } = null!;
public string Backpack { get; set; } = null!;
public int SpawnPriority { get; set; } = 0;
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();
public List<Trait> Traits { get; } = new();

View File

@ -1,6 +1,6 @@
using Content.Server.Access.Components;
using Content.Server.Popups;
using Content.Server.UserInterface;
using Content.Shared.UserInterface;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Interaction;

View File

@ -1,4 +1,3 @@
using Content.Server.Station.Systems;
using Content.Server.StationRecords.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@ -21,7 +20,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly StationRecordsSystem _record = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly AccessSystem _access = default!;
@ -85,10 +83,9 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
var targetAccessComponent = EntityManager.GetComponent<AccessComponent>(targetId);
var jobProto = string.Empty;
if (_station.GetOwningStation(uid) is { } station
&& EntityManager.TryGetComponent<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
&& keyStorage.Key != null
&& _record.TryGetRecord<GeneralStationRecord>(station, keyStorage.Key.Value, out var record))
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
&& keyStorage.Key is {} key
&& _record.TryGetRecord<GeneralStationRecord>(key, out var record))
{
jobProto = record.JobPrototype;
}
@ -103,7 +100,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
possibleAccess,
jobProto,
privilegedIdName,
EntityManager.GetComponent<MetaDataComponent>(targetId).EntityName);
Name(targetId));
}
_userInterface.TrySetUiState(uid, IdCardConsoleUiKey.Key, newState);
@ -184,7 +181,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
if (!Resolve(uid, ref component))
return true;
if (!EntityManager.TryGetComponent<AccessReaderComponent>(uid, out var reader))
if (!TryComp<AccessReaderComponent>(uid, out var reader))
return true;
var privilegedId = component.PrivilegedIdSlot.Item;
@ -193,10 +190,9 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
private void UpdateStationRecord(EntityUid uid, EntityUid targetId, string newFullName, string newJobTitle, JobPrototype? newJobProto)
{
if (_station.GetOwningStation(uid) is not { } station
|| !EntityManager.TryGetComponent<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
if (!TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|| keyStorage.Key is not { } key
|| !_record.TryGetRecord<GeneralStationRecord>(station, key, out var record))
|| !_record.TryGetRecord<GeneralStationRecord>(key, out var record))
{
return;
}
@ -210,6 +206,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
record.JobIcon = newJobProto.Icon;
}
_record.Synchronize(station);
_record.Synchronize(key);
}
}

View File

@ -32,7 +32,7 @@ public sealed class AddPolymorphActionCommand : IConsoleCommand
var polySystem = _entityManager.EntitySysManager.GetEntitySystem<PolymorphSystem>();
_entityManager.EnsureComponent<PolymorphableComponent>(entityUid.Value);
polySystem.CreatePolymorphAction(args[1], entityUid.Value);
var polymorphable = _entityManager.EnsureComponent<PolymorphableComponent>(entityUid.Value);
polySystem.CreatePolymorphAction(args[1], (entityUid.Value, polymorphable));
}
}

View File

@ -0,0 +1,7 @@
namespace Content.Server.Administration.Components;
[RegisterComponent]
public sealed partial class AdminMinigunComponent : Component
{
}

View File

@ -0,0 +1,17 @@
using Content.Server.Administration.Components;
using Content.Shared.Weapons.Ranged.Events;
namespace Content.Server.Administration.Systems;
public sealed class AdminGunSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<AdminMinigunComponent, GunRefreshModifiersEvent>(OnGunRefreshModifiers);
}
private void OnGunRefreshModifiers(Entity<AdminMinigunComponent> ent, ref GunRefreshModifiersEvent args)
{
args.FireRate = 15;
}
}

View File

@ -349,7 +349,7 @@ namespace Content.Server.Administration.Systems
if (TryComp(item, out PdaComponent? pda) &&
TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) &&
keyStorage.Key is { } key &&
_stationRecords.TryGetRecord(key.OriginStation, key, out GeneralStationRecord? record))
_stationRecords.TryGetRecord(key, out GeneralStationRecord? record))
{
if (TryComp(entity, out DnaComponent? dna) &&
dna.DNA != record.DNA)
@ -363,7 +363,7 @@ namespace Content.Server.Administration.Systems
continue;
}
_stationRecords.RemoveRecord(key.OriginStation, key);
_stationRecords.RemoveRecord(key);
Del(item);
}
}

View File

@ -12,6 +12,7 @@ using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@ -52,6 +53,7 @@ public sealed partial class AdminVerbSystem
[Dependency] private readonly BatterySystem _batterySystem = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly GunSystem _gun = default!;
private void AddTricksVerbs(GetVerbsEvent<Verb> args)
{
@ -697,7 +699,8 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Weapons/Guns/HMGs/minigun.rsi"), "icon"),
Act = () =>
{
gun.FireRate = 15;
EnsureComp<AdminMinigunComponent>(args.Target);
_gun.RefreshModifiers((args.Target, gun));
},
Impact = LogImpact.Medium,
Message = Loc.GetString("admin-trick-minigun-fire-description"),

View File

@ -6,6 +6,7 @@ using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Afk;
using Content.Server.Discord;
using Content.Server.GameTicking;
using Content.Shared.Administration;
@ -33,6 +34,7 @@ namespace Content.Server.Administration.Systems
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly IAfkManager _afkManager = default!;
private ISawmill _sawmill = default!;
private readonly HttpClient _httpClient = new();
@ -327,7 +329,7 @@ namespace Content.Server.Administration.Systems
username += $" ({characterName})";
// If no admins are online, set embed color to red. Otherwise green
var color = GetTargetAdmins().Count > 0 ? 0x41F097 : 0xFF0000;
var color = GetNonAfkAdmins().Count > 0 ? 0x41F097 : 0xFF0000;
// Limit server name to 1500 characters, in case someone tries to be a little funny
var serverName = _serverName[..Math.Min(_serverName.Length, 1500)];
@ -471,7 +473,8 @@ namespace Content.Server.Administration.Systems
{
str = str[..(DescriptionMax - _maxAdditionalChars - unameLength)];
}
_messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(senderSession.Name, str, !personalChannel, _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel, admins.Count == 0));
var nonAfkAdmins = GetNonAfkAdmins();
_messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(senderSession.Name, str, !personalChannel, _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel, nonAfkAdmins.Count == 0));
}
if (admins.Count != 0 || sendsWebhook)
@ -483,19 +486,26 @@ namespace Content.Server.Administration.Systems
RaiseNetworkEvent(starMuteMsg, senderSession.Channel);
}
// Returns all online admins with AHelp access
private IList<INetChannel> GetNonAfkAdmins()
{
return _adminManager.ActiveAdmins
.Where(p => (_adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false) && !_afkManager.IsAfk(p))
.Select(p => p.Channel)
.ToList();
}
private IList<INetChannel> GetTargetAdmins()
{
return _adminManager.ActiveAdmins
.Where(p => _adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false)
.Select(p => p.Channel)
.ToList();
.Where(p => _adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false)
.Select(p => p.Channel)
.ToList();
}
private static string GenerateAHelpMessage(string username, string message, bool admin, string roundTime, GameRunLevel roundState, bool noReceivers = false)
{
var stringbuilder = new StringBuilder();
if (admin)
stringbuilder.Append(":outbox_tray:");
else if (noReceivers)

View File

@ -1,4 +1,5 @@
using Content.Shared.CCVar;
using Content.Server.Administration.Managers;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Configuration;
@ -38,6 +39,7 @@ namespace Content.Server.Afk
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IConsoleHost _consoleHost = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
private readonly Dictionary<ICommonSession, TimeSpan> _lastActionTimes = new();
@ -61,10 +63,15 @@ namespace Content.Server.Afk
public bool IsAfk(ICommonSession player)
{
if (!_lastActionTimes.TryGetValue(player, out var time))
{
// Some weird edge case like disconnected clients. Just say true I guess.
return true;
}
var timeOut = _adminManager.IsAdmin(player)
? TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.AdminAfkTime))
: TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.AfkTime));
var timeOut = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.AfkTime));
return _gameTiming.RealTime - time > timeOut;
}

View File

@ -181,7 +181,7 @@ public sealed class AmeNodeGroup : BaseNodeGroup
// Fuel is squared so more fuel vastly increases power and efficiency
// We divide by the number of cores so a larger AME is less efficient at the same fuel settings
// this results in all AMEs having the same efficiency at the same fuel-per-core setting
return 20000f * fuel * fuel / cores;
return 2000000f * fuel * fuel / cores;
}
public int GetTotalStability()

View File

@ -263,6 +263,7 @@ public sealed class AmeControllerSystem : EntitySystem
{
< 10 => AmeControllerState.Fuck,
< 50 => AmeControllerState.Critical,
< 80 => AmeControllerState.Warning,
_ => AmeControllerState.On,
};

View File

@ -119,6 +119,8 @@ public sealed partial class AnomalySystem
// don't spawn inside of solid objects
var physQuery = GetEntityQuery<PhysicsComponent>();
var valid = true;
// TODO: This should be using static lookup.
foreach (var ent in gridComp.GetAnchoredEntities(tile))
{
if (!physQuery.TryGetComponent(ent, out var body))
@ -143,9 +145,9 @@ public sealed partial class AnomalySystem
if (antiXform.MapID != mapPos.MapId)
continue;
var antiCoordinates = _transform.GetMapCoordinates(antiXform);
var antiCoordinates = _transform.GetWorldPosition(antiXform);
var delta = antiCoordinates.Position - mapPos.Position;
var delta = antiCoordinates - mapPos.Position;
if (delta.LengthSquared() < zone.ZoneRadius * zone.ZoneRadius)
{
valid = false;

View File

@ -4,6 +4,7 @@ using Content.Shared.Anomaly.Effects;
using Content.Shared.Anomaly.Effects.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
namespace Content.Server.Anomaly.Effects;
@ -13,10 +14,15 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
[Dependency] private readonly SharedAnomalySystem _anomaly = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
private EntityQuery<PhysicsComponent> _physicsQuery;
/// <inheritdoc/>
public override void Initialize()
{
_physicsQuery = GetEntityQuery<PhysicsComponent>();
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
@ -82,7 +88,7 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
private void SpawnEntities(Entity<EntitySpawnAnomalyComponent> anomaly, EntitySpawnSettingsEntry entry, float stability, float severity)
{
var xform = Transform(anomaly);
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
if (!TryComp(xform.GridUid, out MapGridComponent? grid))
return;
var tiles = _anomaly.GetSpawningPoints(anomaly, stability, severity, entry.Settings);
@ -91,7 +97,7 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
foreach (var tileref in tiles)
{
Spawn(_random.Pick(entry.Spawns), tileref.GridIndices.ToEntityCoordinates(xform.GridUid.Value, _map));
Spawn(_random.Pick(entry.Spawns), _mapSystem.ToCenterCoordinates(tileref, grid));
}
}
}

View File

@ -1,5 +1,5 @@
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.UserInterface;
using Content.Shared.Arcade;
using Robust.Server.GameObjects;
using Robust.Shared.Player;

View File

@ -1,5 +1,5 @@
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.UserInterface;
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;

View File

@ -100,7 +100,7 @@ namespace Content.Server.Atmos.EntitySystems
{
if(_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound))
{
var coordinates = tile.GridIndices.ToEntityCoordinates(tile.GridIndex, _mapManager);
var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices);
_audio.PlayPvs(SpaceWindSound, coordinates, AudioParams.Default.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100)));
}
}
@ -163,7 +163,7 @@ namespace Content.Server.Atmos.EntitySystems
gridAtmosphere.Comp.UpdateCounter,
tile.PressureDifference,
tile.PressureDirection, 0,
tile.PressureSpecificTarget?.GridIndices.ToEntityCoordinates(tile.GridIndex, _mapManager) ?? EntityCoordinates.Invalid,
tile.PressureSpecificTarget != null ? _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.PressureSpecificTarget.GridIndices) : EntityCoordinates.Invalid,
gridWorldRotation,
xforms.GetComponent(entity),
body);

View File

@ -11,8 +11,6 @@ namespace Content.Server.Atmos.EntitySystems
{
public sealed partial class AtmosphereSystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private const int HotspotSoundCooldownCycles = 200;
private int _hotspotSoundCooldown = 0;
@ -81,7 +79,8 @@ namespace Content.Server.Atmos.EntitySystems
if (_hotspotSoundCooldown++ == 0 && !string.IsNullOrEmpty(HotspotSound))
{
var coordinates = tile.GridIndices.ToEntityCoordinates(tile.GridIndex, _mapManager);
var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices);
// A few details on the audio parameters for fire.
// The greater the fire state, the lesser the pitch variation.
// The greater the fire state, the greater the volume.

View File

@ -25,12 +25,14 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly InternalsSystem _internals = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly MapSystem _map = default!;
[Dependency] public readonly PuddleSystem Puddle = default!;

View File

@ -1,24 +1,26 @@
using Content.Server.Atmos.Components;
using Content.Shared.Actions.Events;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Robust.Shared.Audio.Systems;
using Content.Shared.Abilities.Firestarter;
namespace Content.Server.Atmos.EntitySystems;
/// <summary>
/// Adds an action ability that will cause all flammable targets in a radius to ignite, also heals the owner
/// of the component when used.
/// </summary>
namespace Content.Server.Abilities.Firestarter;
public sealed class FirestarterSystem : EntitySystem
public sealed class FirestarterSystem : SharedFirestarterSystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
private readonly HashSet<Entity<FlammableComponent>> _flammables = new();
public override void Initialize()
{
base.Initialize();
@ -26,11 +28,10 @@ public sealed class FirestarterSystem : EntitySystem
}
/// <summary>
/// Checks Radius for igniting nearby flammable objects .
/// Checks Radius for igniting nearby flammable objects
/// </summary>
private void OnStartFire(EntityUid uid, FirestarterComponent component, FireStarterActionEvent args)
{
if (_container.IsEntityOrParentInContainer(uid))
return;
@ -47,10 +48,10 @@ public sealed class FirestarterSystem : EntitySystem
/// </summary>
public void IgniteNearby(EntityUid uid, EntityCoordinates coordinates, float severity, float radius)
{
var flammables = new HashSet<Entity<FlammableComponent>>();
_lookup.GetEntitiesInRange(coordinates, radius, flammables);
_flammables.Clear();
_lookup.GetEntitiesInRange(coordinates, radius, _flammables);
foreach (var flammable in flammables)
foreach (var flammable in _flammables)
{
var ent = flammable.Owner;
var stackAmount = 2 + (int) (severity / 0.15f);

View File

@ -402,7 +402,7 @@ namespace Content.Server.Atmos.EntitySystems
if (TryComp(uid, out TemperatureComponent? temp))
_temperatureSystem.ChangeHeat(uid, 12500 * damageScale, false, temp);
_damageableSystem.TryChangeDamage(uid, flammable.Damage * damageScale);
_damageableSystem.TryChangeDamage(uid, flammable.Damage * damageScale, interruptsDoAfters: false);
AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable);
}

View File

@ -4,7 +4,7 @@ using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Cargo.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.UserInterface;
using Content.Shared.Actions;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;

View File

@ -14,7 +14,7 @@ using Content.Shared.Atmos.Piping.Unary.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Content.Server.Power.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.UserInterface;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Examine;

View File

@ -133,7 +133,8 @@ public sealed class RottingSystem : SharedRottingSystem
return;
}
var description = "perishable-" + stage;
var isMob = HasComp<MobStateComponent>(perishable);
var description = "perishable-" + stage + (!isMob ? "-nonmob" : string.Empty);
args.PushMarkup(Loc.GetString(description, ("target", Identity.Entity(perishable, EntityManager))));
}

View File

@ -6,7 +6,7 @@ using Content.Server.Inventory;
using Content.Server.Popups;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.UserInterface;
using Content.Shared.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.Bed.Cryostorage;
using Content.Shared.Chat;

Some files were not shown because too many files have changed in this diff Show More