Merge remote-tracking branch 'upstream/master' into ed-23-02-2025-upstream
# Conflicts: # Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs # Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs # Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
This commit is contained in:
@@ -19,11 +19,11 @@ namespace Content.Client.Administration.Systems
|
||||
OnBwoinkTextMessageRecieved?.Invoke(this, message);
|
||||
}
|
||||
|
||||
public void Send(NetUserId channelId, string text, bool playSound)
|
||||
public void Send(NetUserId channelId, string text, bool playSound, bool adminOnly)
|
||||
{
|
||||
// Reuse the channel ID as the 'true sender'.
|
||||
// Server will ignore this and if someone makes it not ignore this (which is bad, allows impersonation!!!), that will help.
|
||||
RaiseNetworkEvent(new BwoinkTextMessage(channelId, channelId, text, playSound: playSound));
|
||||
RaiseNetworkEvent(new BwoinkTextMessage(channelId, channelId, text, playSound: playSound, adminOnly: adminOnly));
|
||||
SendInputTextUpdated(channelId, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Visible="True" Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
_adminManager.AdminStatusUpdated += UpdateButtons;
|
||||
UpdateButtons();
|
||||
|
||||
AdminOnly.OnToggled += args => PlaySound.Disabled = args.Pressed;
|
||||
|
||||
ChannelSelector.OnSelectionChanged += sel =>
|
||||
{
|
||||
_currentPlayer = sel;
|
||||
|
||||
@@ -512,39 +512,15 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
scroll.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
var castChild = child as VScrollBar;
|
||||
|
||||
if (castChild != null)
|
||||
{
|
||||
vScrollBar = castChild;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = null;
|
||||
|
||||
@@ -350,35 +350,15 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
scroll.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var control in scroll.Children)
|
||||
{
|
||||
if (control is not VScrollBar)
|
||||
continue;
|
||||
|
||||
vScrollBar = (VScrollBar)control;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
@@ -16,9 +17,7 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new GasAnalyzerWindow();
|
||||
_window.OnClose += OnClose;
|
||||
_window.OpenCenteredLeft();
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
{
|
||||
if(!_adminAudioEnabled) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
var stream = _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_adminAudio.Add(stream?.Entity);
|
||||
}
|
||||
|
||||
@@ -75,13 +75,13 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
// Either the cvar is disabled or it's already playing
|
||||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
var stream = _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_eventAudio.Add(soundEvent.Type, stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
|
||||
{
|
||||
_audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
|
||||
}
|
||||
|
||||
private void StopStationEventMusic(StopStationEventMusic soundEvent)
|
||||
|
||||
@@ -218,7 +218,7 @@ public sealed partial class ContentAudioSystem
|
||||
return;
|
||||
|
||||
var file = _gameTicker.RestartSound;
|
||||
if (string.IsNullOrEmpty(file))
|
||||
if (ResolvedSoundSpecifier.IsNullOrEmpty(file))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -125,6 +126,27 @@ namespace Content.Client.Changelog
|
||||
_sawmill = _logManager.GetSawmill(SawmillName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to return a human-readable version number from the build.json file
|
||||
/// </summary>
|
||||
public string GetClientVersion()
|
||||
{
|
||||
var fork = _configManager.GetCVar(CVars.BuildForkId);
|
||||
var version = _configManager.GetCVar(CVars.BuildVersion);
|
||||
|
||||
// This trimming might become annoying if down the line some codebases want to switch to a real
|
||||
// version format like "104.11.3" while others are still using the git hashes
|
||||
if (version.Length > 7)
|
||||
version = version[..7];
|
||||
|
||||
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(fork))
|
||||
return Loc.GetString("changelog-version-unknown");
|
||||
|
||||
return Loc.GetString("changelog-version-tag",
|
||||
("fork", fork),
|
||||
("version", version));
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Changelog
|
||||
{
|
||||
|
||||
@@ -70,22 +70,7 @@ namespace Content.Client.Changelog
|
||||
Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}"));
|
||||
}
|
||||
|
||||
// Try to get the current version from the build.json file
|
||||
var version = _cfg.GetCVar(CVars.BuildVersion);
|
||||
var forkId = _cfg.GetCVar(CVars.BuildForkId);
|
||||
|
||||
var versionText = Loc.GetString("changelog-version-unknown");
|
||||
|
||||
// Make sure these aren't empty, like in a dev env
|
||||
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(forkId))
|
||||
{
|
||||
versionText = Loc.GetString("changelog-version-tag",
|
||||
("fork", forkId),
|
||||
("version", version[..7])); // Only show the first 7 characters
|
||||
}
|
||||
|
||||
// if else statements are ugly, shut up
|
||||
VersionLabel.Text = versionText;
|
||||
VersionLabel.Text = _changelog.GetClientVersion();
|
||||
|
||||
TabsUpdated();
|
||||
}
|
||||
|
||||
@@ -141,6 +141,11 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
{
|
||||
return _reagentSources.GetValueOrDefault(id) ?? new List<ReagentSourceData>();
|
||||
}
|
||||
|
||||
// Is handled on server and updated on client via ReagentGuideRegistryChangedEvent
|
||||
public override void ReloadAllReagentPrototypes()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Content.Client.Construction
|
||||
[RegisterComponent]
|
||||
public sealed partial class ConstructionGhostComponent : Component
|
||||
{
|
||||
public int GhostId { get; set; }
|
||||
[ViewVariables] public ConstructionPrototype? Prototype { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@ namespace Content.Client.Construction
|
||||
.Register<ConstructionSystem>();
|
||||
|
||||
SubscribeLocalEvent<ConstructionGhostComponent, ExaminedEvent>(HandleConstructionGhostExamined);
|
||||
SubscribeLocalEvent<ConstructionGhostComponent, ComponentShutdown>(HandleGhostComponentShutdown);
|
||||
}
|
||||
|
||||
private void HandleGhostComponentShutdown(EntityUid uid, ConstructionGhostComponent component, ComponentShutdown args)
|
||||
{
|
||||
ClearGhost(component.GhostId);
|
||||
}
|
||||
|
||||
private void OnConstructionGuideReceived(ResponseConstructionGuide ev)
|
||||
@@ -205,8 +211,9 @@ namespace Content.Client.Construction
|
||||
ghost = EntityManager.SpawnEntity("constructionghost", loc);
|
||||
var comp = EntityManager.GetComponent<ConstructionGhostComponent>(ghost.Value);
|
||||
comp.Prototype = prototype;
|
||||
comp.GhostId = ghost.GetHashCode();
|
||||
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
|
||||
_ghosts.Add(ghost.GetHashCode(), ghost.Value);
|
||||
_ghosts.Add(comp.GhostId, ghost.Value);
|
||||
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
|
||||
sprite.Color = new Color(48, 255, 48, 128);
|
||||
|
||||
|
||||
@@ -21,11 +21,10 @@ namespace Content.Client.Crayon.UI
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_menu = this.CreateWindow<CrayonWindow>();
|
||||
_menu = this.CreateWindowCenteredLeft<CrayonWindow>();
|
||||
_menu.OnColorSelected += SelectColor;
|
||||
_menu.OnSelected += Select;
|
||||
PopulateCrayons();
|
||||
_menu.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
private void PopulateCrayons()
|
||||
|
||||
@@ -144,7 +144,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
{
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(unit.FlushSound), 0)
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(unit.FlushSound), 0)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Client.GameTicking.Managers
|
||||
{
|
||||
@@ -26,7 +27,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
|
||||
[ViewVariables] public bool AreWeReady { get; private set; }
|
||||
[ViewVariables] public bool IsGameStarted { get; private set; }
|
||||
[ViewVariables] public string? RestartSound { get; private set; }
|
||||
[ViewVariables] public ResolvedSoundSpecifier? RestartSound { get; private set; }
|
||||
[ViewVariables] public string? LobbyBackground { get; private set; }
|
||||
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
|
||||
[ViewVariables] public string? ServerInfoBlob { get; private set; }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.Hands;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Screens;
|
||||
@@ -7,6 +9,7 @@ using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -20,9 +23,11 @@ namespace Content.Client.Gameplay
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly ChangelogManager _changelog = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
private FpsCounter _fpsCounter = default!;
|
||||
private Label _version = default!;
|
||||
|
||||
public MainViewport Viewport => _uiManager.ActiveScreen!.GetWidget<MainViewport>()!;
|
||||
|
||||
@@ -40,6 +45,7 @@ namespace Content.Client.Gameplay
|
||||
base.Startup();
|
||||
|
||||
LoadMainScreen();
|
||||
_configurationManager.OnValueChanged(CCVars.UILayout, ReloadMainScreenValueChange);
|
||||
|
||||
// Add the hand-item overlay.
|
||||
_overlayManager.AddOverlay(new ShowHandItemOverlay());
|
||||
@@ -50,7 +56,24 @@ namespace Content.Client.Gameplay
|
||||
UserInterfaceManager.PopupRoot.AddChild(_fpsCounter);
|
||||
_fpsCounter.Visible = _configurationManager.GetCVar(CCVars.HudFpsCounterVisible);
|
||||
_configurationManager.OnValueChanged(CCVars.HudFpsCounterVisible, (show) => { _fpsCounter.Visible = show; });
|
||||
_configurationManager.OnValueChanged(CCVars.UILayout, ReloadMainScreenValueChange);
|
||||
|
||||
// Version number watermark.
|
||||
_version = new Label();
|
||||
_version.Text = _changelog.GetClientVersion();
|
||||
_version.Visible = VersionVisible();
|
||||
UserInterfaceManager.PopupRoot.AddChild(_version);
|
||||
_configurationManager.OnValueChanged(CCVars.HudVersionWatermark, (show) => { _version.Visible = VersionVisible(); });
|
||||
_configurationManager.OnValueChanged(CCVars.ForceClientHudVersionWatermark, (show) => { _version.Visible = VersionVisible(); });
|
||||
// TODO make this centered or something
|
||||
LayoutContainer.SetPosition(_version, new Vector2(800, 0));
|
||||
}
|
||||
|
||||
// This allows servers to force the watermark on clients
|
||||
private bool VersionVisible()
|
||||
{
|
||||
var client = _configurationManager.GetCVar(CCVars.HudVersionWatermark);
|
||||
var server = _configurationManager.GetCVar(CCVars.ForceClientHudVersionWatermark);
|
||||
return client || server;
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
|
||||
@@ -219,10 +219,12 @@ namespace Content.Client.Gameplay
|
||||
{
|
||||
entityToClick = GetClickedEntity(mousePosWorld);
|
||||
}
|
||||
var transformSystem = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
var mapSystem = _entitySystemManager.GetEntitySystem<MapSystem>();
|
||||
|
||||
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out _, out var grid) ?
|
||||
grid.MapToGrid(mousePosWorld) :
|
||||
EntityCoordinates.FromMap(_mapManager, mousePosWorld);
|
||||
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out var uid, out _) ?
|
||||
mapSystem.MapToGrid(uid, mousePosWorld) :
|
||||
transformSystem.ToCoordinates(mousePosWorld);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
35
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml
Normal file
35
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Margin="5 5 5 5">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="#777777"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<BoxContainer Name="IconContainer"/>
|
||||
<RichTextLabel Name="ResultName"/>
|
||||
</BoxContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#393c3f"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<GridContainer Columns="2" Margin="10">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'guidebook-microwave-ingredients-header'}"/>
|
||||
<GridContainer Columns="3" Name="IngredientsGrid"/>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'guidebook-microwave-cook-time-header'}"/>
|
||||
<RichTextLabel Name="CookTimeLabel"/>
|
||||
</BoxContainer>
|
||||
</GridContainer>
|
||||
|
||||
<BoxContainer Margin="10">
|
||||
<RichTextLabel Name="ResultDescription" HorizontalAlignment="Left"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
183
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs
Normal file
183
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Kitchen;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Control for embedding a microwave recipe into a guidebook.
|
||||
/// </summary>
|
||||
[UsedImplicitly, GenerateTypedNameReferences]
|
||||
public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag, ISearchableControl
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public GuideMicrowaveEmbed()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
_sawmill = _logManager.GetSawmill("guidemicrowaveembed");
|
||||
}
|
||||
|
||||
public GuideMicrowaveEmbed(string recipe) : this()
|
||||
{
|
||||
GenerateControl(_prototype.Index<FoodRecipePrototype>(recipe));
|
||||
}
|
||||
|
||||
public GuideMicrowaveEmbed(FoodRecipePrototype recipe) : this()
|
||||
{
|
||||
GenerateControl(recipe);
|
||||
}
|
||||
|
||||
public bool CheckMatchesSearch(string query)
|
||||
{
|
||||
return this.ChildrenContainText(query);
|
||||
}
|
||||
|
||||
public void SetHiddenState(bool state, string query)
|
||||
{
|
||||
Visible = CheckMatchesSearch(query) ? state : !state;
|
||||
}
|
||||
|
||||
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
control = null;
|
||||
if (!args.TryGetValue("Recipe", out var id))
|
||||
{
|
||||
_sawmill.Error("Recipe embed tag is missing recipe prototype argument");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_prototype.TryIndex<FoodRecipePrototype>(id, out var recipe))
|
||||
{
|
||||
_sawmill.Error($"Specified recipe prototype \"{id}\" is not a valid recipe prototype");
|
||||
return false;
|
||||
}
|
||||
|
||||
GenerateControl(recipe);
|
||||
|
||||
control = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GenerateHeader(FoodRecipePrototype recipe)
|
||||
{
|
||||
var entity = _prototype.Index<EntityPrototype>(recipe.Result);
|
||||
|
||||
IconContainer.AddChild(new GuideEntityEmbed(recipe.Result, false, false));
|
||||
ResultName.SetMarkup(entity.Name);
|
||||
ResultDescription.SetMarkup(entity.Description);
|
||||
}
|
||||
|
||||
private void GenerateSolidIngredients(FoodRecipePrototype recipe)
|
||||
{
|
||||
foreach (var (product, amount) in recipe.IngredientsSolids.OrderByDescending(p => p.Value))
|
||||
{
|
||||
var ingredient = _prototype.Index<EntityPrototype>(product);
|
||||
|
||||
IngredientsGrid.AddChild(new GuideEntityEmbed(product, false, false));
|
||||
|
||||
// solid name
|
||||
|
||||
var solidNameMsg = new FormattedMessage();
|
||||
solidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-name-display", ("ingredient", ingredient.Name)));
|
||||
solidNameMsg.Pop();
|
||||
|
||||
var solidNameLabel = new RichTextLabel();
|
||||
solidNameLabel.SetMessage(solidNameMsg);
|
||||
|
||||
IngredientsGrid.AddChild(solidNameLabel);
|
||||
|
||||
// solid quantity
|
||||
|
||||
var solidQuantityMsg = new FormattedMessage();
|
||||
solidQuantityMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-quantity-display", ("amount", amount)));
|
||||
solidQuantityMsg.Pop();
|
||||
|
||||
var solidQuantityLabel = new RichTextLabel();
|
||||
solidQuantityLabel.SetMessage(solidQuantityMsg);
|
||||
|
||||
IngredientsGrid.AddChild(solidQuantityLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateLiquidIngredients(FoodRecipePrototype recipe)
|
||||
{
|
||||
foreach (var (product, amount) in recipe.IngredientsReagents.OrderByDescending(p => p.Value))
|
||||
{
|
||||
var reagent = _prototype.Index<ReagentPrototype>(product);
|
||||
|
||||
// liquid color
|
||||
|
||||
var liquidColorMsg = new FormattedMessage();
|
||||
liquidColorMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-color-display", ("color", reagent.SubstanceColor)));
|
||||
liquidColorMsg.Pop();
|
||||
|
||||
var liquidColorLabel = new RichTextLabel();
|
||||
liquidColorLabel.SetMessage(liquidColorMsg);
|
||||
liquidColorLabel.HorizontalAlignment = Control.HAlignment.Center;
|
||||
|
||||
IngredientsGrid.AddChild(liquidColorLabel);
|
||||
|
||||
// liquid name
|
||||
|
||||
var liquidNameMsg = new FormattedMessage();
|
||||
liquidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-name-display", ("reagent", reagent.LocalizedName)));
|
||||
liquidNameMsg.Pop();
|
||||
|
||||
var liquidNameLabel = new RichTextLabel();
|
||||
liquidNameLabel.SetMessage(liquidNameMsg);
|
||||
|
||||
IngredientsGrid.AddChild(liquidNameLabel);
|
||||
|
||||
// liquid quantity
|
||||
|
||||
var liquidQuantityMsg = new FormattedMessage();
|
||||
liquidQuantityMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-quantity-display", ("amount", amount)));
|
||||
liquidQuantityMsg.Pop();
|
||||
|
||||
var liquidQuantityLabel = new RichTextLabel();
|
||||
liquidQuantityLabel.SetMessage(liquidQuantityMsg);
|
||||
|
||||
IngredientsGrid.AddChild(liquidQuantityLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateIngredients(FoodRecipePrototype recipe)
|
||||
{
|
||||
GenerateLiquidIngredients(recipe);
|
||||
GenerateSolidIngredients(recipe);
|
||||
}
|
||||
|
||||
private void GenerateCookTime(FoodRecipePrototype recipe)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-cook-time", ("time", recipe.CookTime)));
|
||||
msg.Pop();
|
||||
|
||||
CookTimeLabel.SetMessage(msg);
|
||||
}
|
||||
|
||||
private void GenerateControl(FoodRecipePrototype recipe)
|
||||
{
|
||||
GenerateHeader(recipe);
|
||||
GenerateIngredients(recipe);
|
||||
GenerateCookTime(recipe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Shared.Kitchen;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Control for listing microwave recipes in a guidebook
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class GuideMicrowaveGroupEmbed : BoxContainer, IDocumentTag
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public GuideMicrowaveGroupEmbed()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
}
|
||||
|
||||
public GuideMicrowaveGroupEmbed(string group) : this()
|
||||
{
|
||||
CreateEntries(group);
|
||||
}
|
||||
|
||||
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
control = null;
|
||||
if (!args.TryGetValue("Group", out var group))
|
||||
{
|
||||
Logger.Error("Microwave group embed tag is missing group argument");
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateEntries(group);
|
||||
|
||||
control = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateEntries(string group)
|
||||
{
|
||||
var prototypes = _prototype.EnumeratePrototypes<FoodRecipePrototype>()
|
||||
.Where(p => p.Group.Equals(group))
|
||||
.OrderBy(p => p.Name);
|
||||
|
||||
foreach (var recipe in prototypes)
|
||||
{
|
||||
var embed = new GuideMicrowaveEmbed(recipe);
|
||||
AddChild(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,24 +134,14 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
|
||||
foreach (var entry in _entries.Values)
|
||||
{
|
||||
if (entry.Children.Count > 0)
|
||||
{
|
||||
var sortedChildren = entry.Children
|
||||
.Select(childId => _entries[childId])
|
||||
.OrderBy(childEntry => childEntry.Priority)
|
||||
.ThenBy(childEntry => Loc.GetString(childEntry.Name))
|
||||
.Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
|
||||
.ToList();
|
||||
|
||||
entry.Children = sortedChildren;
|
||||
}
|
||||
|
||||
entries.ExceptWith(entry.Children);
|
||||
}
|
||||
|
||||
rootEntries = entries.ToList();
|
||||
}
|
||||
|
||||
// Only roots need to be sorted.
|
||||
// As defined in the SS14 Dev Wiki, children are already sorted based on their child field order within their parent's prototype definition.
|
||||
// Roots are sorted by priority. If there is no defined priority for a root then it is by definition sorted undefined.
|
||||
return rootEntries
|
||||
.Select(rootEntryId => _entries[rootEntryId])
|
||||
.OrderBy(rootEntry => rootEntry.Priority)
|
||||
|
||||
@@ -109,7 +109,17 @@ public sealed partial class DocumentParsingManager
|
||||
.Cast<Control>()))
|
||||
.Labelled("subheader");
|
||||
|
||||
private static readonly Parser<char, Control> TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
|
||||
private static readonly Parser<char, Control> TertiaryHeaderControlParser = Try(String("###"))
|
||||
.Then(SkipWhitespaces.Then(Map(text => new Label
|
||||
{
|
||||
Text = text,
|
||||
StyleClasses = { "LabelKeyText" }
|
||||
},
|
||||
AnyCharExcept('\n').AtLeastOnceString())
|
||||
.Cast<Control>()))
|
||||
.Labelled("tertiaryheader");
|
||||
|
||||
private static readonly Parser<char, Control> TryHeaderControl = OneOf(TertiaryHeaderControlParser, SubHeaderControlParser, HeaderControlParser);
|
||||
|
||||
private static readonly Parser<char, Control> ListControlParser = Try(Char('-'))
|
||||
.Then(SkipWhitespaces)
|
||||
|
||||
@@ -167,7 +167,9 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
if (!TryComp<SpeechComponent>(uid, out var speech) || speech.SpeechSounds is null)
|
||||
return;
|
||||
|
||||
_audioSystem.PlayGlobal(speech.SpeechSounds, Filter.Local(), false, speech.AudioParams);
|
||||
// This code is broken because SpeechSounds isn't a file name or sound specifier directly.
|
||||
// Commenting out to avoid compile failure with https://github.com/space-wizards/RobustToolbox/pull/5540
|
||||
// _audioSystem.PlayGlobal(speech.SpeechSounds, Filter.Local(), false, speech.AudioParams);
|
||||
}
|
||||
|
||||
public void FakeClientActivateInWorld(EntityUid activated)
|
||||
|
||||
@@ -21,14 +21,12 @@ public sealed class HumanoidMarkingModifierBoundUserInterface : BoundUserInterfa
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<HumanoidMarkingModifierWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<HumanoidMarkingModifierWindow>();
|
||||
_window.OnMarkingAdded += SendMarkingSet;
|
||||
_window.OnMarkingRemoved += SendMarkingSet;
|
||||
_window.OnMarkingColorChange += SendMarkingSetNoResend;
|
||||
_window.OnMarkingRankChange += SendMarkingSet;
|
||||
_window.OnLayerInfoModified += SendBaseLayer;
|
||||
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -64,11 +64,9 @@ namespace Content.Client.Inventory
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_strippingMenu = this.CreateWindow<StrippingMenu>();
|
||||
_strippingMenu = this.CreateWindowCenteredLeft<StrippingMenu>();
|
||||
_strippingMenu.OnDirty += UpdateMenu;
|
||||
_strippingMenu.Title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
|
||||
|
||||
_strippingMenu?.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -18,9 +18,8 @@ namespace Content.Client.Lathe.UI
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<LatheMenu>();
|
||||
_menu = this.CreateWindowCenteredRight<LatheMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OpenCenteredRight();
|
||||
|
||||
_menu.OnServerListButtonPressed += _ =>
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class BeforeLightTargetOverlay : Overlay
|
||||
/// <summary>
|
||||
/// In metres
|
||||
/// </summary>
|
||||
private float _skirting = 1.5f;
|
||||
private float _skirting = 2f;
|
||||
|
||||
public const int ContentZIndex = -10;
|
||||
|
||||
|
||||
@@ -39,6 +39,6 @@ public sealed class LightBlurOverlay : Overlay
|
||||
|
||||
var target = beforeOverlay.EnlargedLightTarget;
|
||||
// Yeah that's all this does keep walkin.
|
||||
//_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 2f);
|
||||
_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
@@ -17,9 +19,9 @@ public sealed class RoofOverlay : Overlay
|
||||
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedRoofSystem _roof = default!;
|
||||
private readonly SharedTransformSystem _xformSystem;
|
||||
|
||||
private readonly HashSet<Entity<OccluderComponent>> _occluders = new();
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
@@ -33,6 +35,7 @@ public sealed class RoofOverlay : Overlay
|
||||
|
||||
_lookup = _entManager.System<EntityLookupSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_roof = _entManager.System<SharedRoofSystem>();
|
||||
_xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
ZIndex = ContentZIndex;
|
||||
@@ -86,29 +89,14 @@ public sealed class RoofOverlay : Overlay
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
var tileEnumerator = _mapSystem.GetTilesEnumerator(grid.Owner, grid, bounds);
|
||||
var roofEnt = (grid.Owner, grid.Comp, roof);
|
||||
|
||||
// Due to stencilling we essentially draw on unrooved tiles
|
||||
while (tileEnumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0)
|
||||
if (!_roof.IsRooved(roofEnt, tileRef.GridIndices))
|
||||
{
|
||||
// Check if the tile is occluded in which case hide it anyway.
|
||||
// This is to avoid lit walls bleeding over to unlit tiles.
|
||||
_occluders.Clear();
|
||||
_lookup.GetLocalEntitiesIntersecting(grid.Owner, tileRef.GridIndices, _occluders);
|
||||
var found = false;
|
||||
|
||||
foreach (var occluder in _occluders)
|
||||
{
|
||||
if (!occluder.Comp.Enabled)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
var local = _lookup.GetLocalBounds(tileRef, grid.Comp.TileSize);
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
|
||||
|
||||
if (comp.BlinkingSound != null)
|
||||
{
|
||||
var sound = _audio.GetSound(comp.BlinkingSound);
|
||||
var sound = _audio.ResolveSound(comp.BlinkingSound);
|
||||
blinkingAnim.AnimationTracks.Add(new AnimationTrackPlaySound()
|
||||
{
|
||||
KeyFrames =
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<SpriteView Scale="2 2"
|
||||
Margin="0 4 4 4"
|
||||
OverrideDirection="South"
|
||||
Name="View"/>
|
||||
Name="View"
|
||||
SetSize="64 64"/>
|
||||
<Label Name="DescriptionLabel"
|
||||
ClipText="True"
|
||||
HorizontalExpand="True"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
MinSize="800 128">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
|
||||
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
|
||||
<Label Name="LoadoutNameLabel"/>
|
||||
<PanelContainer HorizontalExpand="True" SetHeight="24">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
|
||||
@@ -39,6 +39,10 @@ public sealed partial class LoadoutWindow : FancyWindow
|
||||
{
|
||||
var name = loadout.EntityName;
|
||||
|
||||
LoadoutNameLabel.Text = proto.NameDataset == null ?
|
||||
Loc.GetString("loadout-name-edit-label") :
|
||||
Loc.GetString("loadout-name-edit-label-dataset");
|
||||
|
||||
RoleNameEdit.ToolTip = Loc.GetString(
|
||||
"loadout-name-edit-tooltip",
|
||||
("max", HumanoidCharacterProfile.MaxLoadoutNameLength));
|
||||
|
||||
@@ -21,9 +21,8 @@ public sealed class MechBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<MechMenu>();
|
||||
_menu = this.CreateWindowCenteredLeft<MechMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OpenCenteredLeft();
|
||||
|
||||
_menu.OnRemoveButtonPressed += uid =>
|
||||
{
|
||||
|
||||
@@ -372,14 +372,11 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
if (!_tryToScrollToListFocus)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(SensorScroller, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
{
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
SensorScroller.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(SensorScroller.VScroll, SensorScroller.VScrollTarget))
|
||||
{
|
||||
_tryToScrollToListFocus = false;
|
||||
return;
|
||||
@@ -387,22 +384,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
vScrollBar = (VScrollBar) child;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -34,11 +35,12 @@ public sealed partial class StencilOverlay
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
var matty = Matrix3x2.Multiply(matrix, invMatrix);
|
||||
worldHandle.SetTransform(matty);
|
||||
_entManager.TryGetComponent(grid.Owner, out RoofComponent? roofComp);
|
||||
|
||||
foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid.Owner, grid, tile))
|
||||
if (_weather.CanWeatherAffect(grid.Owner, grid, tile, roofComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ namespace Content.Client.PDA
|
||||
|
||||
private void CreateMenu()
|
||||
{
|
||||
_menu = this.CreateWindow<PdaMenu>();
|
||||
_menu.OpenCenteredLeft();
|
||||
_menu = this.CreateWindowCenteredLeft<PdaMenu>();
|
||||
|
||||
_menu.FlashLightToggleButton.OnToggled += _ =>
|
||||
{
|
||||
|
||||
@@ -123,6 +123,12 @@ namespace Content.Client.Popups
|
||||
PopupMessage(message, type, coordinates, null, true);
|
||||
}
|
||||
|
||||
public override void PopupPredictedCoordinates(string? message, EntityCoordinates coordinates, EntityUid? recipient, PopupType type = PopupType.Small)
|
||||
{
|
||||
if (recipient != null && _timing.IsFirstTimePredicted)
|
||||
PopupCoordinates(message, coordinates, recipient.Value, type);
|
||||
}
|
||||
|
||||
private void PopupCursorInternal(string? message, PopupType type, bool recordReplay)
|
||||
{
|
||||
if (message == null)
|
||||
|
||||
8
Content.Client/Power/EntitySystems/PowerNetSystem.cs
Normal file
8
Content.Client/Power/EntitySystems/PowerNetSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
|
||||
namespace Content.Client.Power.EntitySystems;
|
||||
|
||||
public sealed class PowerNetSystem : SharedPowerNetSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public sealed class PortableGeneratorBoundUserInterface : BoundUserInterface
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<GeneratorWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<GeneratorWindow>();
|
||||
_window.SetEntity(Owner);
|
||||
_window.OnState += args =>
|
||||
{
|
||||
@@ -34,8 +34,6 @@ public sealed class PortableGeneratorBoundUserInterface : BoundUserInterface
|
||||
_window.OnPower += SetTargetPower;
|
||||
_window.OnEjectFuel += EjectFuel;
|
||||
_window.OnSwitchOutput += SwitchOutput;
|
||||
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -269,27 +269,6 @@ public sealed partial class PowerMonitoringWindow
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
var castChild = child as VScrollBar;
|
||||
|
||||
if (castChild != null)
|
||||
{
|
||||
vScrollBar = castChild;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AutoScrollToFocus()
|
||||
{
|
||||
if (!_autoScrollActive)
|
||||
@@ -299,15 +278,12 @@ public sealed partial class PowerMonitoringWindow
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
scroll.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,8 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<OfferingWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<OfferingWindow>();
|
||||
_window.Title = Loc.GetString("salvage-expedition-window-title");
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -22,9 +22,8 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<OfferingWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<OfferingWindow>();
|
||||
_window.Title = Loc.GetString("salvage-magnet-window-title");
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -21,10 +21,9 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<IFFConsoleWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<IFFConsoleWindow>();
|
||||
_window.ShowIFF += SendIFFMessage;
|
||||
_window.ShowVessel += SendVesselMessage;
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -20,12 +20,21 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
|
||||
private readonly HashSet<FadingSpriteComponent> _comps = new();
|
||||
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
private EntityQuery<SpriteFadeComponent> _fadeQuery;
|
||||
private EntityQuery<FadingSpriteComponent> _fadingQuery;
|
||||
|
||||
private const float TargetAlpha = 0.4f;
|
||||
private const float ChangeRate = 1f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
_fadeQuery = GetEntityQuery<SpriteFadeComponent>();
|
||||
_fadingQuery = GetEntityQuery<FadingSpriteComponent>();
|
||||
|
||||
SubscribeLocalEvent<FadingSpriteComponent, ComponentShutdown>(OnFadingShutdown);
|
||||
}
|
||||
|
||||
@@ -42,28 +51,26 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
var spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
var change = ChangeRate * frameTime;
|
||||
|
||||
if (TryComp(player, out TransformComponent? playerXform) &&
|
||||
_stateManager.CurrentState is GameplayState state &&
|
||||
spriteQuery.TryGetComponent(player, out var playerSprite))
|
||||
_spriteQuery.TryGetComponent(player, out var playerSprite))
|
||||
{
|
||||
var fadeQuery = GetEntityQuery<SpriteFadeComponent>();
|
||||
var mapPos = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: playerXform);
|
||||
|
||||
// Also want to handle large entities even if they may not be clickable.
|
||||
foreach (var ent in state.GetClickableEntities(mapPos))
|
||||
{
|
||||
if (ent == player ||
|
||||
!fadeQuery.HasComponent(ent) ||
|
||||
!spriteQuery.TryGetComponent(ent, out var sprite) ||
|
||||
!_fadeQuery.HasComponent(ent) ||
|
||||
!_spriteQuery.TryGetComponent(ent, out var sprite) ||
|
||||
sprite.DrawDepth < playerSprite.DrawDepth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryComp<FadingSpriteComponent>(ent, out var fading))
|
||||
if (!_fadingQuery.TryComp(ent, out var fading))
|
||||
{
|
||||
fading = AddComp<FadingSpriteComponent>(ent);
|
||||
fading.OriginalAlpha = sprite.Color.A;
|
||||
@@ -85,7 +92,7 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
if (_comps.Contains(comp))
|
||||
continue;
|
||||
|
||||
if (!spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
continue;
|
||||
|
||||
var newColor = Math.Min(sprite.Color.A + change, comp.OriginalAlpha);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Systems.Storage;
|
||||
using Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
using Content.Shared.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Storage;
|
||||
|
||||
@@ -11,6 +13,8 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private StorageWindow? _window;
|
||||
|
||||
public Vector2? Position => _window?.Position;
|
||||
|
||||
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
@@ -21,7 +25,7 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
|
||||
_window = IoCManager.Resolve<IUserInterfaceManager>()
|
||||
.GetUIController<StorageUIController>()
|
||||
.CreateStorageWindow(Owner);
|
||||
.CreateStorageWindow(this);
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out StorageComponent? storage))
|
||||
{
|
||||
@@ -50,10 +54,20 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
Reclaim();
|
||||
}
|
||||
|
||||
public void CloseWindow(Vector2 position)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
// Update its position before potentially saving.
|
||||
// Listen it makes sense okay.
|
||||
LayoutContainer.SetPosition(_window, position);
|
||||
_window?.Close();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
if (_window == null)
|
||||
@@ -70,11 +84,12 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
_window.Visible = true;
|
||||
}
|
||||
|
||||
public void ReOpen()
|
||||
public void Show(Vector2 position)
|
||||
{
|
||||
_window?.Orphan();
|
||||
_window = null;
|
||||
Open();
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
Show();
|
||||
LayoutContainer.SetPosition(_window, position);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
|
||||
private Dictionary<EntityUid, ItemStorageLocation> _oldStoredItems = new();
|
||||
|
||||
private List<(StorageBoundUserInterface Bui, bool Value)> _queuedBuis = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -72,7 +74,7 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
if (NestedStorage && player != null && ContainerSystem.TryGetContainingContainer((uid, null, null), out var container) &&
|
||||
UI.TryGetOpenUi<StorageBoundUserInterface>(container.Owner, StorageComponent.StorageUiKey.Key, out var containerBui))
|
||||
{
|
||||
containerBui.Hide();
|
||||
_queuedBuis.Add((containerBui, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +91,7 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
{
|
||||
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
|
||||
{
|
||||
storageBui.Hide();
|
||||
_queuedBuis.Add((storageBui, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
{
|
||||
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
|
||||
{
|
||||
storageBui.Show();
|
||||
_queuedBuis.Add((storageBui, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,4 +154,30 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This update loop exists just to synchronize with UISystem and avoid 1-tick delays.
|
||||
// If deferred opens / closes ever get removed you can dump this.
|
||||
foreach (var (bui, open) in _queuedBuis)
|
||||
{
|
||||
if (open)
|
||||
{
|
||||
bui.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
bui.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
_queuedBuis.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTrigger
|
||||
{
|
||||
comp.PrimingAnimation.AnimationTracks.Add(
|
||||
new AnimationTrackPlaySound() {
|
||||
KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(comp.PrimingSound), 0) }
|
||||
KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
|
||||
UIHelper = isAdmin ? new AdminAHelpUIHandler(ownerUserId) : new UserAHelpUIHandler(ownerUserId);
|
||||
UIHelper.DiscordRelayChanged(_discordRelayActive);
|
||||
|
||||
UIHelper.SendMessageAction = (userId, textMessage, playSound) => _bwoinkSystem?.Send(userId, textMessage, playSound);
|
||||
UIHelper.SendMessageAction = (userId, textMessage, playSound, adminOnly) => _bwoinkSystem?.Send(userId, textMessage, playSound, adminOnly);
|
||||
UIHelper.InputTextChanged += (channel, text) => _bwoinkSystem?.SendInputTextUpdated(channel, text.Length > 0);
|
||||
UIHelper.OnClose += () => { SetAHelpPressed(false); };
|
||||
UIHelper.OnOpen += () => { SetAHelpPressed(true); };
|
||||
@@ -324,7 +324,7 @@ public interface IAHelpUIHandler : IDisposable
|
||||
public void PeopleTypingUpdated(BwoinkPlayerTypingUpdated args);
|
||||
public event Action OnClose;
|
||||
public event Action OnOpen;
|
||||
public Action<NetUserId, string, bool>? SendMessageAction { get; set; }
|
||||
public Action<NetUserId, string, bool, bool>? SendMessageAction { get; set; }
|
||||
public event Action<NetUserId, string>? InputTextChanged;
|
||||
}
|
||||
public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
@@ -408,7 +408,7 @@ public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
|
||||
public event Action? OnClose;
|
||||
public event Action? OnOpen;
|
||||
public Action<NetUserId, string, bool>? SendMessageAction { get; set; }
|
||||
public Action<NetUserId, string, bool, bool>? SendMessageAction { get; set; }
|
||||
public event Action<NetUserId, string>? InputTextChanged;
|
||||
|
||||
public void Open(NetUserId channelId, bool relayActive)
|
||||
@@ -462,7 +462,7 @@ public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
if (_activePanelMap.TryGetValue(channelId, out var existingPanel))
|
||||
return existingPanel;
|
||||
|
||||
_activePanelMap[channelId] = existingPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(channelId, text, Window?.Bwoink.PlaySound.Pressed ?? true));
|
||||
_activePanelMap[channelId] = existingPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(channelId, text, Window?.Bwoink.PlaySound.Pressed ?? true, Window?.Bwoink.AdminOnly.Pressed ?? false));
|
||||
existingPanel.InputTextChanged += text => InputTextChanged?.Invoke(channelId, text);
|
||||
existingPanel.Visible = false;
|
||||
if (!Control!.BwoinkArea.Children.Contains(existingPanel))
|
||||
@@ -548,7 +548,7 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
|
||||
public event Action? OnClose;
|
||||
public event Action? OnOpen;
|
||||
public Action<NetUserId, string, bool>? SendMessageAction { get; set; }
|
||||
public Action<NetUserId, string, bool, bool>? SendMessageAction { get; set; }
|
||||
public event Action<NetUserId, string>? InputTextChanged;
|
||||
|
||||
public void Open(NetUserId channelId, bool relayActive)
|
||||
@@ -561,7 +561,7 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
{
|
||||
if (_window is { Disposed: false })
|
||||
return;
|
||||
_chatPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(_ownerId, text, true));
|
||||
_chatPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(_ownerId, text, true, false));
|
||||
_chatPanel.InputTextChanged += text => InputTextChanged?.Invoke(_ownerId, text);
|
||||
_chatPanel.RelayedToDiscordLabel.Visible = relayActive;
|
||||
_window = new DefaultWindow()
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -190,6 +191,26 @@ public sealed class StorageWindow : BaseWindow
|
||||
BuildGridRepresentation();
|
||||
}
|
||||
|
||||
private void CloseParent()
|
||||
{
|
||||
if (StorageEntity == null)
|
||||
return;
|
||||
|
||||
var containerSystem = _entity.System<SharedContainerSystem>();
|
||||
var uiSystem = _entity.System<UserInterfaceSystem>();
|
||||
|
||||
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
|
||||
_entity.TryGetComponent(container.Owner, out StorageComponent? storage) &&
|
||||
storage.Container.Contains(StorageEntity.Value) &&
|
||||
uiSystem
|
||||
.TryGetOpenUi<StorageBoundUserInterface>(container.Owner,
|
||||
StorageComponent.StorageUiKey.Key,
|
||||
out var parentBui))
|
||||
{
|
||||
parentBui.CloseWindow(Position);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildGridRepresentation()
|
||||
{
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || comp.Grid.Count == 0)
|
||||
@@ -212,7 +233,9 @@ public sealed class StorageWindow : BaseWindow
|
||||
};
|
||||
exitButton.OnPressed += _ =>
|
||||
{
|
||||
// Close ourselves and all parent BUIs.
|
||||
Close();
|
||||
CloseParent();
|
||||
};
|
||||
exitButton.OnKeyBindDown += args =>
|
||||
{
|
||||
@@ -220,6 +243,7 @@ public sealed class StorageWindow : BaseWindow
|
||||
if (!args.Handled && args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
||||
{
|
||||
Close();
|
||||
CloseParent();
|
||||
args.Handle();
|
||||
}
|
||||
};
|
||||
@@ -258,7 +282,8 @@ public sealed class StorageWindow : BaseWindow
|
||||
var containerSystem = _entity.System<SharedContainerSystem>();
|
||||
|
||||
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
|
||||
_entity.TryGetComponent(container.Owner, out StorageComponent? storage))
|
||||
_entity.TryGetComponent(container.Owner, out StorageComponent? storage) &&
|
||||
storage.Container.Contains(StorageEntity.Value))
|
||||
{
|
||||
Close();
|
||||
|
||||
@@ -267,7 +292,7 @@ public sealed class StorageWindow : BaseWindow
|
||||
StorageComponent.StorageUiKey.Key,
|
||||
out var parentBui))
|
||||
{
|
||||
parentBui.Show();
|
||||
parentBui.Show(Position);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -412,6 +437,8 @@ public sealed class StorageWindow : BaseWindow
|
||||
{
|
||||
if (storageComp.StoredItems.TryGetValue(ent, out var updated))
|
||||
{
|
||||
data.Control.Marked = IsMarked(ent);
|
||||
|
||||
if (data.Loc.Equals(updated))
|
||||
{
|
||||
DebugTools.Assert(data.Control.Location == updated);
|
||||
@@ -450,12 +477,7 @@ public sealed class StorageWindow : BaseWindow
|
||||
var gridPiece = new ItemGridPiece((ent, itemEntComponent), loc, _entity)
|
||||
{
|
||||
MinSize = size,
|
||||
Marked = _contained.IndexOf(ent) switch
|
||||
{
|
||||
0 => ItemGridPieceMarks.First,
|
||||
1 => ItemGridPieceMarks.Second,
|
||||
_ => null,
|
||||
}
|
||||
Marked = IsMarked(ent),
|
||||
};
|
||||
gridPiece.OnPiecePressed += OnPiecePressed;
|
||||
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
|
||||
@@ -467,6 +489,16 @@ public sealed class StorageWindow : BaseWindow
|
||||
}
|
||||
}
|
||||
|
||||
private ItemGridPieceMarks? IsMarked(EntityUid uid)
|
||||
{
|
||||
return _contained.IndexOf(uid) switch
|
||||
{
|
||||
0 => ItemGridPieceMarks.First,
|
||||
1 => ItemGridPieceMarks.Second,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
@@ -486,8 +518,9 @@ public sealed class StorageWindow : BaseWindow
|
||||
{
|
||||
if (StorageEntity != null && _entity.System<StorageSystem>().NestedStorage)
|
||||
{
|
||||
// If parent container nests us then show back button
|
||||
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
|
||||
_entity.HasComponent<StorageComponent>(container.Owner))
|
||||
_entity.TryGetComponent(container.Owner, out StorageComponent? storageComp) && storageComp.Container.Contains(StorageEntity.Value))
|
||||
{
|
||||
_backButton.Visible = true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -37,6 +38,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[UISystemDependency] private readonly StorageSystem _storage = default!;
|
||||
[UISystemDependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
|
||||
|
||||
@@ -59,39 +61,11 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UIManager.OnScreenChanged += OnScreenChange;
|
||||
|
||||
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
|
||||
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
|
||||
_configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
|
||||
}
|
||||
|
||||
private void OnScreenChange((UIScreen? Old, UIScreen? New) obj)
|
||||
{
|
||||
// Handle reconnects with hotbargui.
|
||||
|
||||
// Essentially HotbarGui / the screen gets loaded AFTER gamestates at the moment (because clientgameticker manually changes it via event)
|
||||
// and changing this may be a massive change.
|
||||
// So instead we'll just manually reload it for now.
|
||||
if (!StaticStorageUIEnabled ||
|
||||
obj.New == null ||
|
||||
!EntityManager.TryGetComponent(_player.LocalEntity, out UserInterfaceUserComponent? userComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// UISystemDependency not injected at this point so do it the old fashion way, I love ordering issues.
|
||||
var uiSystem = EntityManager.System<SharedUserInterfaceSystem>();
|
||||
|
||||
foreach (var bui in uiSystem.GetActorUis((_player.LocalEntity.Value, userComp)))
|
||||
{
|
||||
if (!uiSystem.TryGetOpenUi<StorageBoundUserInterface>(bui.Entity, StorageComponent.StorageUiKey.Key, out var storageBui))
|
||||
continue;
|
||||
|
||||
storageBui.ReOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStorageWindowTitle(bool obj)
|
||||
{
|
||||
WindowTitle = obj;
|
||||
@@ -107,7 +81,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
StaticStorageUIEnabled = obj;
|
||||
}
|
||||
|
||||
public StorageWindow CreateStorageWindow(EntityUid uid)
|
||||
public StorageWindow CreateStorageWindow(StorageBoundUserInterface sBui)
|
||||
{
|
||||
var window = new StorageWindow();
|
||||
window.MouseFilter = Control.MouseFilterMode.Pass;
|
||||
@@ -127,9 +101,25 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCenteredLeft();
|
||||
// Open at parent position if it's open.
|
||||
if (_ui.TryGetOpenUi<StorageBoundUserInterface>(EntityManager.GetComponent<TransformComponent>(sBui.Owner).ParentUid,
|
||||
StorageComponent.StorageUiKey.Key, out var bui) && bui.Position != null)
|
||||
{
|
||||
window.Open(bui.Position.Value);
|
||||
}
|
||||
// Open at the saved position if it exists.
|
||||
else if (_ui.TryGetPosition(sBui.Owner, StorageComponent.StorageUiKey.Key, out var pos))
|
||||
{
|
||||
window.Open(pos);
|
||||
}
|
||||
// Open at the default position.
|
||||
else
|
||||
{
|
||||
window.OpenCenteredLeft();
|
||||
}
|
||||
}
|
||||
|
||||
_ui.RegisterControl(sBui, window);
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace Content.Client.VendingMachines
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<VendingMachineMenu>();
|
||||
_menu.OpenCenteredLeft();
|
||||
_menu = this.CreateWindowCenteredLeft<VendingMachineMenu>();
|
||||
_menu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
Refresh();
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Components;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -89,16 +90,6 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
|
||||
// TODO using targeted actions while combat mode is enabled should NOT trigger attacks.
|
||||
|
||||
// TODO: Need to make alt-fire melee its own component I guess?
|
||||
// Melee and guns share a lot in the middle but share virtually nothing at the start and end so
|
||||
// it's kinda tricky.
|
||||
// I think as long as we make secondaries their own component it's probably fine
|
||||
// as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with.
|
||||
if (TryComp<GunComponent>(weaponUid, out var gun) && gun.UseKey)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
@@ -117,20 +108,37 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos);
|
||||
}
|
||||
|
||||
// If the gun has AltFireComponent, it can be used to attack.
|
||||
if (TryComp<GunComponent>(weaponUid, out var gun) && gun.UseKey)
|
||||
{
|
||||
if (!TryComp<AltFireMeleeComponent>(weaponUid, out var altFireComponent) || altDown != BoundKeyState.Down)
|
||||
return;
|
||||
|
||||
switch(altFireComponent.AttackType)
|
||||
{
|
||||
case AltFireAttackType.Light:
|
||||
ClientLightAttack(entity, mousePos, coordinates, weaponUid, weapon);
|
||||
break;
|
||||
|
||||
case AltFireAttackType.Heavy:
|
||||
ClientHeavyAttack(entity, coordinates, weaponUid, weapon);
|
||||
break;
|
||||
|
||||
case AltFireAttackType.Disarm:
|
||||
ClientDisarm(entity, mousePos, coordinates);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Heavy attack.
|
||||
if (altDown == BoundKeyState.Down)
|
||||
{
|
||||
// If it's an unarmed attack then do a disarm
|
||||
if (weapon.AltDisarm && weaponUid == entity)
|
||||
{
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
{
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
}
|
||||
|
||||
EntityManager.RaisePredictiveEvent(new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(coordinates)));
|
||||
ClientDisarm(entity, mousePos, coordinates);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,28 +148,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
|
||||
// Light attack
|
||||
if (useDown == BoundKeyState.Down)
|
||||
{
|
||||
var attackerPos = TransformSystem.GetMapCoordinates(entity);
|
||||
|
||||
if (mousePos.MapId != attackerPos.MapId ||
|
||||
(attackerPos.Position - mousePos.Position).Length() > weapon.Range)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
{
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
}
|
||||
|
||||
// Don't light-attack if interaction will be handling this instead
|
||||
if (Interaction.CombatModeCanHandInteract(entity, target))
|
||||
return;
|
||||
|
||||
RaisePredictiveEvent(new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
ClientLightAttack(entity, mousePos, coordinates, weaponUid, weapon);
|
||||
}
|
||||
|
||||
protected override bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session)
|
||||
@@ -236,6 +223,35 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
RaisePredictiveEvent(new HeavyAttackEvent(GetNetEntity(meleeUid), entities.GetRange(0, Math.Min(MaxTargets, entities.Count)), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void ClientDisarm(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates)
|
||||
{
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
|
||||
RaisePredictiveEvent(new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void ClientLightAttack(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates, EntityUid weaponUid, MeleeWeaponComponent meleeComponent)
|
||||
{
|
||||
var attackerPos = TransformSystem.GetMapCoordinates(attacker);
|
||||
|
||||
if (mousePos.MapId != attackerPos.MapId || (attackerPos.Position - mousePos.Position).Length() > meleeComponent.Range)
|
||||
return;
|
||||
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
|
||||
// Don't light-attack if interaction will be handling this instead
|
||||
if (Interaction.CombatModeCanHandInteract(attacker, target))
|
||||
return;
|
||||
|
||||
RaisePredictiveEvent(new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void OnMeleeLunge(MeleeLungeEvent ev)
|
||||
{
|
||||
var ent = GetEntity(ev.Entity);
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -73,11 +74,11 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldPos, out var gridUid, out _))
|
||||
{
|
||||
coords = EntityCoordinates.FromMap(gridUid, mouseWorldPos, TransformSystem);
|
||||
coords = TransformSystem.ToCoordinates(gridUid, mouseWorldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
coords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(mouseWorldPos.MapId), mouseWorldPos, TransformSystem);
|
||||
coords = TransformSystem.ToCoordinates(_mapSystem.GetMap(mouseWorldPos.MapId), mouseWorldPos);
|
||||
}
|
||||
|
||||
const float BufferDistance = 0.1f;
|
||||
|
||||
@@ -37,6 +37,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
[Dependency] private readonly InputSystem _inputSystem = default!;
|
||||
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
public const string HitscanProto = "HitscanEffect";
|
||||
@@ -102,6 +103,13 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
private void OnHitscan(HitscanEvent ev)
|
||||
{
|
||||
// ALL I WANT IS AN ANIMATED EFFECT
|
||||
|
||||
// TODO EFFECTS
|
||||
// This is very jank
|
||||
// because the effect consists of three unrelatd entities, the hitscan beam can be split appart.
|
||||
// E.g., if a grid rotates while part of the beam is parented to the grid, and part of it is parented to the map.
|
||||
// Ideally, there should only be one entity, with one sprite that has multiple layers
|
||||
// Or at the very least, have the other entities parented to the same entity to make sure they stick together.
|
||||
foreach (var a in ev.Sprites)
|
||||
{
|
||||
if (a.Sprite is not SpriteSpecifier.Rsi rsi)
|
||||
@@ -109,13 +117,17 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
var coords = GetCoordinates(a.coordinates);
|
||||
|
||||
if (Deleted(coords.EntityId))
|
||||
if (!TryComp(coords.EntityId, out TransformComponent? relativeXform))
|
||||
continue;
|
||||
|
||||
var ent = Spawn(HitscanProto, coords);
|
||||
var sprite = Comp<SpriteComponent>(ent);
|
||||
|
||||
var xform = Transform(ent);
|
||||
xform.LocalRotation = a.angle;
|
||||
var targetWorldRot = a.angle + _xform.GetWorldRotation(relativeXform);
|
||||
var delta = targetWorldRot - _xform.GetWorldRotation(xform);
|
||||
_xform.SetLocalRotationNoLerp(ent, xform.LocalRotation + delta, xform);
|
||||
|
||||
sprite[EffectLayers.Unshaded].AutoAnimated = false;
|
||||
sprite.LayerSetSprite(EffectLayers.Unshaded, rsi);
|
||||
sprite.LayerSetState(EffectLayers.Unshaded, rsi.RsiState);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -57,6 +58,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
// Work out tiles nearby to determine volume.
|
||||
if (TryComp<MapGridComponent>(entXform.GridUid, out var grid))
|
||||
{
|
||||
TryComp(entXform.GridUid, out RoofComponent? roofComp);
|
||||
var gridId = entXform.GridUid.Value;
|
||||
// FloodFill to the nearest tile and use that for audio.
|
||||
var seed = _mapSystem.GetTileRef(gridId, grid, entXform.Coordinates);
|
||||
@@ -71,7 +73,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
if (!visited.Add(node.GridIndices))
|
||||
continue;
|
||||
|
||||
if (!CanWeatherAffect(entXform.GridUid.Value, grid, node))
|
||||
if (!CanWeatherAffect(entXform.GridUid.Value, grid, node, roofComp))
|
||||
{
|
||||
// Add neighbors
|
||||
// TODO: Ideally we pick some deterministically random direction and use that
|
||||
|
||||
@@ -26,11 +26,7 @@ public sealed partial class TestPair
|
||||
instance.ProtoMan.LoadString(file, changed: changed);
|
||||
}
|
||||
|
||||
await instance.WaitPost(() =>
|
||||
{
|
||||
instance.ProtoMan.ResolveResults();
|
||||
instance.ProtoMan.ReloadPrototypes(changed);
|
||||
});
|
||||
await instance.WaitPost(() => instance.ProtoMan.ReloadPrototypes(changed));
|
||||
|
||||
foreach (var (kind, ids) in changed)
|
||||
{
|
||||
|
||||
84
Content.IntegrationTests/Tests/Doors/AirlockPryingTest.cs
Normal file
84
Content.IntegrationTests/Tests/Doors/AirlockPryingTest.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Shared.Doors.Components;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Doors;
|
||||
|
||||
public sealed class AirlockPryingTest : InteractionTest
|
||||
{
|
||||
[Test]
|
||||
public async Task PoweredClosedAirlock_Pry_DoesNotOpen()
|
||||
{
|
||||
await SpawnTarget(Airlock);
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
|
||||
await RunTicks(1);
|
||||
|
||||
Assert.That(TryComp<AirlockComponent>(out var airlockComp), "Airlock does not have AirlockComponent?");
|
||||
Assert.That(airlockComp.Powered, "Airlock should be powered for this test.");
|
||||
|
||||
Assert.That(TryComp<DoorComponent>(out var doorComp), "Airlock does not have DoorComponent?");
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Closed), "Airlock did not start closed.");
|
||||
|
||||
await InteractUsing(Pry);
|
||||
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Closed), "Powered airlock was pried open.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PoweredOpenAirlock_Pry_DoesNotClose()
|
||||
{
|
||||
await SpawnTarget(Airlock);
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
|
||||
await RunTicks(1);
|
||||
|
||||
Assert.That(TryComp<AirlockComponent>(out var airlockComp), "Airlock does not have AirlockComponent?");
|
||||
Assert.That(airlockComp.Powered, "Airlock should be powered for this test.");
|
||||
|
||||
var doorSys = SEntMan.System<DoorSystem>();
|
||||
await Server.WaitPost(() => doorSys.SetState(SEntMan.GetEntity(Target.Value), DoorState.Open));
|
||||
|
||||
Assert.That(TryComp<DoorComponent>(out var doorComp), "Airlock does not have DoorComponent?");
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Open), "Airlock did not start open.");
|
||||
|
||||
await InteractUsing(Pry);
|
||||
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Open), "Powered airlock was pried closed.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UnpoweredClosedAirlock_Pry_Opens()
|
||||
{
|
||||
await SpawnTarget(Airlock);
|
||||
|
||||
Assert.That(TryComp<AirlockComponent>(out var airlockComp), "Airlock does not have AirlockComponent?");
|
||||
Assert.That(airlockComp.Powered, Is.False, "Airlock should not be powered for this test.");
|
||||
|
||||
Assert.That(TryComp<DoorComponent>(out var doorComp), "Airlock does not have DoorComponent?");
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Closed), "Airlock did not start closed.");
|
||||
|
||||
await InteractUsing(Pry);
|
||||
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Opening), "Unpowered airlock failed to pry open.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UnpoweredOpenAirlock_Pry_Closes()
|
||||
{
|
||||
await SpawnTarget(Airlock);
|
||||
|
||||
Assert.That(TryComp<AirlockComponent>(out var airlockComp), "Airlock does not have AirlockComponent?");
|
||||
Assert.That(airlockComp.Powered, Is.False, "Airlock should not be powered for this test.");
|
||||
|
||||
var doorSys = SEntMan.System<DoorSystem>();
|
||||
await Server.WaitPost(() => doorSys.SetState(SEntMan.GetEntity(Target.Value), DoorState.Open));
|
||||
|
||||
Assert.That(TryComp<DoorComponent>(out var doorComp), "Airlock does not have DoorComponent?");
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Open), "Airlock did not start open.");
|
||||
|
||||
await InteractUsing(Pry);
|
||||
|
||||
Assert.That(doorComp.State, Is.EqualTo(DoorState.Closing), "Unpowered airlock failed to pry closed.");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ public abstract partial class InteractionTest
|
||||
protected const string Plating = "Plating";
|
||||
protected const string Lattice = "Lattice";
|
||||
|
||||
// Structures
|
||||
protected const string Airlock = "Airlock";
|
||||
|
||||
// Tools/steps
|
||||
protected const string Wrench = "Wrench";
|
||||
protected const string Screw = "Screwdriver";
|
||||
|
||||
70
Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs
Normal file
70
Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Weapons.Ranged.Components;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests all entity prototypes with the MagazineVisualsComponent.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class MagazineVisualsSpriteTest
|
||||
{
|
||||
[Test]
|
||||
public async Task MagazineVisualsSpritesExist()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var client = pair.Client;
|
||||
var protoMan = client.ResolveDependency<IPrototypeManager>();
|
||||
var componentFactory = client.ResolveDependency<IComponentFactory>();
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
foreach (var proto in protoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (proto.Abstract || pair.IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (!proto.TryGetComponent<MagazineVisualsComponent>(out var visuals, componentFactory))
|
||||
continue;
|
||||
|
||||
Assert.That(proto.TryGetComponent<SpriteComponent>(out var sprite, componentFactory),
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no SpriteComponent.");
|
||||
Assert.That(proto.HasComponent<AppearanceComponent>(componentFactory),
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no AppearanceComponent.");
|
||||
|
||||
var toTest = new List<(int, string)>();
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out var magLayerId))
|
||||
toTest.Add((magLayerId, ""));
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out var magUnshadedLayerId))
|
||||
toTest.Add((magUnshadedLayerId, "-unshaded"));
|
||||
|
||||
Assert.That(toTest, Is.Not.Empty,
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no Mag or MagUnshaded layer map.");
|
||||
|
||||
var start = visuals.ZeroVisible ? 0 : 1;
|
||||
foreach (var (id, midfix) in toTest)
|
||||
{
|
||||
Assert.That(sprite.TryGetLayer(id, out var layer));
|
||||
var rsi = layer.ActualRsi;
|
||||
for (var i = start; i < visuals.MagSteps; i++)
|
||||
{
|
||||
var state = $"{visuals.MagState}{midfix}-{i}";
|
||||
Assert.That(rsi.TryGetState(state, out _),
|
||||
@$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but {rsi.Path} doesn't have state {state}!");
|
||||
}
|
||||
|
||||
// MagSteps includes the 0th step, so sometimes people are off by one.
|
||||
var extraState = $"{visuals.MagState}{midfix}-{visuals.MagSteps}";
|
||||
Assert.That(rsi.TryGetState(extraState, out _), Is.False,
|
||||
@$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but more states exist!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -987,6 +987,8 @@ namespace Content.Server.Database
|
||||
BabyJail = 4,
|
||||
/// Results from rejected connections with external API checking tools
|
||||
IPChecks = 5,
|
||||
/// Results from rejected connections who are authenticated but have no modern hwid associated with them.
|
||||
NoHwid = 6
|
||||
}
|
||||
|
||||
public class ServerBanHit
|
||||
|
||||
@@ -136,7 +136,7 @@ public sealed partial class AdminVerbSystem
|
||||
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
|
||||
var board = Spawn("ChessBoard", xform.Coordinates);
|
||||
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
|
||||
xform.Coordinates = EntityCoordinates.FromMap(_mapManager, session.Position);
|
||||
xform.Coordinates = _transformSystem.ToCoordinates(session.Position);
|
||||
_transformSystem.SetWorldRotationNoLerp((args.Target, xform), Angle.Zero);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
|
||||
@@ -640,7 +640,7 @@ namespace Content.Server.Administration.Systems
|
||||
var personalChannel = senderSession.UserId == message.UserId;
|
||||
var senderAdmin = _adminManager.GetAdminData(senderSession);
|
||||
var senderAHelpAdmin = senderAdmin?.HasFlag(AdminFlags.Adminhelp) ?? false;
|
||||
var authorized = personalChannel || senderAHelpAdmin;
|
||||
var authorized = personalChannel && !message.AdminOnly || senderAHelpAdmin;
|
||||
if (!authorized)
|
||||
{
|
||||
// Unauthorized bwoink (log?)
|
||||
@@ -676,11 +676,11 @@ namespace Content.Server.Administration.Systems
|
||||
bwoinkText = $"{senderSession.Name}";
|
||||
}
|
||||
|
||||
bwoinkText = $"{(message.PlaySound ? "" : "(S) ")}{bwoinkText}: {escapedText}";
|
||||
bwoinkText = $"{(message.AdminOnly ? Loc.GetString("bwoink-message-admin-only") : !message.PlaySound ? Loc.GetString("bwoink-message-silent") : "")} {bwoinkText}: {escapedText}";
|
||||
|
||||
// If it's not an admin / admin chooses to keep the sound then play it.
|
||||
var playSound = !senderAHelpAdmin || message.PlaySound;
|
||||
var msg = new BwoinkTextMessage(message.UserId, senderSession.UserId, bwoinkText, playSound: playSound);
|
||||
// If it's not an admin / admin chooses to keep the sound and message is not an admin only message, then play it.
|
||||
var playSound = (!senderAHelpAdmin || message.PlaySound) && !message.AdminOnly;
|
||||
var msg = new BwoinkTextMessage(message.UserId, senderSession.UserId, bwoinkText, playSound: playSound, adminOnly: message.AdminOnly);
|
||||
|
||||
LogBwoink(msg);
|
||||
|
||||
@@ -700,7 +700,7 @@ namespace Content.Server.Administration.Systems
|
||||
}
|
||||
|
||||
// Notify player
|
||||
if (_playerManager.TryGetSessionById(message.UserId, out var session))
|
||||
if (_playerManager.TryGetSessionById(message.UserId, out var session) && !message.AdminOnly)
|
||||
{
|
||||
if (!admins.Contains(session.Channel))
|
||||
{
|
||||
|
||||
@@ -19,11 +19,12 @@ public sealed partial class AntagSelectionSystem
|
||||
/// Tries to get the next non-filled definition based on the current amount of selected minds and other factors.
|
||||
/// </summary>
|
||||
public bool TryGetNextAvailableDefinition(Entity<AntagSelectionComponent> ent,
|
||||
[NotNullWhen(true)] out AntagSelectionDefinition? definition)
|
||||
[NotNullWhen(true)] out AntagSelectionDefinition? definition,
|
||||
int? players = null)
|
||||
{
|
||||
definition = null;
|
||||
|
||||
var totalTargetCount = GetTargetAntagCount(ent);
|
||||
var totalTargetCount = GetTargetAntagCount(ent, players);
|
||||
var mindCount = ent.Comp.SelectedMinds.Count;
|
||||
if (mindCount >= totalTargetCount)
|
||||
return false;
|
||||
|
||||
@@ -144,7 +144,10 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
|
||||
DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn);
|
||||
|
||||
if (!TryGetNextAvailableDefinition((uid, antag), out var def))
|
||||
// do not count players in the lobby for the antag ratio
|
||||
var players = _playerManager.NetworkedSessions.Count(x => x.AttachedEntity != null);
|
||||
|
||||
if (!TryGetNextAvailableDefinition((uid, antag), out var def, players))
|
||||
continue;
|
||||
|
||||
if (TryMakeAntag((uid, antag), args.Player, def.Value))
|
||||
|
||||
@@ -76,14 +76,13 @@ public sealed class GasAnalyzerSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void OnUseInHand(Entity<GasAnalyzerComponent> entity, ref UseInHandEvent args)
|
||||
{
|
||||
// Not checking for Handled because ActivatableUISystem already marks it as such.
|
||||
|
||||
if (!entity.Comp.Enabled)
|
||||
{
|
||||
ActivateAnalyzer(entity, args.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisableAnalyzer(entity, args.User);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
_conHost.UnregisterCommand("playglobalsound");
|
||||
}
|
||||
|
||||
public void PlayAdminGlobal(Filter playerFilter, string filename, AudioParams? audioParams = null, bool replay = true)
|
||||
public void PlayAdminGlobal(Filter playerFilter, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null, bool replay = true)
|
||||
{
|
||||
var msg = new AdminSoundEvent(filename, audioParams);
|
||||
var msg = new AdminSoundEvent(specifier, audioParams);
|
||||
RaiseNetworkEvent(msg, playerFilter, recordReplay: replay);
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
return stationFilter;
|
||||
}
|
||||
|
||||
public void PlayGlobalOnStation(EntityUid source, string filename, AudioParams? audioParams = null)
|
||||
public void PlayGlobalOnStation(EntityUid source, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
var msg = new GameGlobalSoundEvent(filename, audioParams);
|
||||
var msg = new GameGlobalSoundEvent(specifier, audioParams);
|
||||
var filter = GetStationAndPvs(source);
|
||||
RaiseNetworkEvent(msg, filter);
|
||||
}
|
||||
@@ -52,13 +52,13 @@ public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
|
||||
public void DispatchStationEventMusic(EntityUid source, SoundSpecifier sound, StationEventMusicType type)
|
||||
{
|
||||
DispatchStationEventMusic(source, _audio.GetSound(sound), type);
|
||||
DispatchStationEventMusic(source, _audio.ResolveSound(sound), type);
|
||||
}
|
||||
|
||||
public void DispatchStationEventMusic(EntityUid source, string sound, StationEventMusicType type)
|
||||
public void DispatchStationEventMusic(EntityUid source, ResolvedSoundSpecifier specifier, StationEventMusicType type)
|
||||
{
|
||||
var audio = AudioParams.Default.WithVolume(-8);
|
||||
var msg = new StationEventMusicEvent(sound, type, audio);
|
||||
var msg = new StationEventMusicEvent(specifier, type, audio);
|
||||
|
||||
var filter = GetStationAndPvs(source);
|
||||
RaiseNetworkEvent(msg, filter);
|
||||
|
||||
@@ -369,7 +369,7 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
private void PlayDenySound(EntityUid uid, CargoOrderConsoleComponent component)
|
||||
{
|
||||
_audio.PlayPvs(_audio.GetSound(component.ErrorSound), uid);
|
||||
_audio.PlayPvs(_audio.ResolveSound(component.ErrorSound), uid);
|
||||
}
|
||||
|
||||
private static CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, CargoProductPrototype cargoProduct, int id)
|
||||
|
||||
@@ -92,7 +92,7 @@ public sealed partial class CargoSystem
|
||||
var currentOrder = comp.CurrentOrders.First();
|
||||
if (FulfillOrder(currentOrder, xform.Coordinates, comp.PrinterOutput))
|
||||
{
|
||||
_audio.PlayPvs(_audio.GetSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f));
|
||||
_audio.PlayPvs(_audio.ResolveSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f));
|
||||
|
||||
if (_station.GetOwningStation(uid) is { } station)
|
||||
UpdateOrders(station);
|
||||
|
||||
@@ -196,17 +196,30 @@ public partial class ChatSystem
|
||||
/// <returns></returns>
|
||||
private bool AllowedToUseEmote(EntityUid source, EmotePrototype emote)
|
||||
{
|
||||
if ((_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)))
|
||||
return false;
|
||||
// If emote is in AllowedEmotes, it will bypass whitelist and blacklist
|
||||
if (TryComp<SpeechComponent>(source, out var speech) &&
|
||||
speech.AllowedEmotes.Contains(emote.ID))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!emote.Available &&
|
||||
TryComp<SpeechComponent>(source, out var speech) &&
|
||||
!speech.AllowedEmotes.Contains(emote.ID))
|
||||
// Check the whitelist and blacklist
|
||||
if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) ||
|
||||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, source))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the emote is available for all
|
||||
if (!emote.Available)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto)
|
||||
{
|
||||
var ev = new EmoteEvent(proto);
|
||||
|
||||
@@ -328,7 +328,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
|
||||
if (playSound)
|
||||
{
|
||||
_audio.PlayGlobal(announcementSound == null ? DefaultAnnouncementSound : _audio.GetSound(announcementSound), Filter.Broadcast(), true, AudioParams.Default.WithVolume(-2f));
|
||||
_audio.PlayGlobal(announcementSound == null ? DefaultAnnouncementSound : _audio.ResolveSound(announcementSound), Filter.Broadcast(), true, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Global station announcement from {sender}: {message}");
|
||||
}
|
||||
|
||||
@@ -64,4 +64,9 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
var ev = new ReagentGuideRegistryChangedEvent(changeset);
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
public override void ReloadAllReagentPrototypes()
|
||||
{
|
||||
InitializeServerRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace Content.Server.Chemistry.TileReactions
|
||||
[DataDefinition]
|
||||
public sealed partial class SpillTileReaction : ITileReaction
|
||||
{
|
||||
[DataField("launchForwardsMultiplier")] private float _launchForwardsMultiplier = 1;
|
||||
[DataField("requiredSlipSpeed")] private float _requiredSlipSpeed = 6;
|
||||
[DataField("paralyzeTime")] private float _paralyzeTime = 1;
|
||||
[DataField("launchForwardsMultiplier")] public float LaunchForwardsMultiplier = 1;
|
||||
[DataField("requiredSlipSpeed")] public float RequiredSlipSpeed = 6;
|
||||
[DataField("paralyzeTime")] public float ParalyzeTime = 1;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SlipperyComponent.SuperSlippery"/>
|
||||
/// </summary>
|
||||
[DataField("superSlippery")] private bool _superSlippery;
|
||||
[DataField("superSlippery")] public bool SuperSlippery;
|
||||
|
||||
public FixedPoint2 TileReact(TileRef tile,
|
||||
ReagentPrototype reagent,
|
||||
@@ -39,13 +39,13 @@ namespace Content.Server.Chemistry.TileReactions
|
||||
.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out var puddleUid, false, false))
|
||||
{
|
||||
var slippery = entityManager.EnsureComponent<SlipperyComponent>(puddleUid);
|
||||
slippery.LaunchForwardsMultiplier = _launchForwardsMultiplier;
|
||||
slippery.ParalyzeTime = _paralyzeTime;
|
||||
slippery.SuperSlippery = _superSlippery;
|
||||
slippery.LaunchForwardsMultiplier = LaunchForwardsMultiplier;
|
||||
slippery.ParalyzeTime = ParalyzeTime;
|
||||
slippery.SuperSlippery = SuperSlippery;
|
||||
entityManager.Dirty(puddleUid, slippery);
|
||||
|
||||
var step = entityManager.EnsureComponent<StepTriggerComponent>(puddleUid);
|
||||
entityManager.EntitySysManager.GetEntitySystem<StepTriggerSystem>().SetRequiredTriggerSpeed(puddleUid, _requiredSlipSpeed, step);
|
||||
entityManager.EntitySysManager.GetEntitySystem<StepTriggerSystem>().SetRequiredTriggerSpeed(puddleUid, RequiredSlipSpeed, step);
|
||||
|
||||
var slow = entityManager.EnsureComponent<SpeedModifierContactsComponent>(puddleUid);
|
||||
var speedModifier = 1 - reagent.Viscosity;
|
||||
|
||||
@@ -222,6 +222,11 @@ namespace Content.Server.Connection
|
||||
|
||||
var modernHwid = e.UserData.ModernHWIds;
|
||||
|
||||
if (modernHwid.Length == 0 && e.AuthType == LoginType.LoggedIn && _cfg.GetCVar(CCVars.RequireModernHardwareId))
|
||||
{
|
||||
return (ConnectionDenyReason.NoHwid, Loc.GetString("hwid-required"), null);
|
||||
}
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
|
||||
if (bans.Count > 0)
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Content.Server.Damage.Systems
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage * _damageable.UniversalThrownDamageModifier, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CrystallEdge Melee upgrade end
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
@@ -66,12 +66,14 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDamageExamine(EntityUid uid, DamageOtherOnHitComponent component, ref DamageExamineEvent args)
|
||||
{
|
||||
var damage = component.Damage;
|
||||
//CP14 Sharpening damage apply
|
||||
var damage = component.Damage * _damageable.UniversalThrownDamageModifier;
|
||||
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
_damageExamine.AddDamageExamine(args.Message, damage, Loc.GetString("damage-throw"));
|
||||
//CP14 Sharpening damage apply end
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server.DetailExaminable
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class DetailExaminableComponent : Component
|
||||
{
|
||||
[DataField("content", required: true)] [ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Content = "";
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.DetailExaminable
|
||||
{
|
||||
public sealed class DetailExaminableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DetailExaminableComponent, GetVerbsEvent<ExamineVerb>>(OnGetExamineVerbs);
|
||||
}
|
||||
|
||||
private void OnGetExamineVerbs(EntityUid uid, DetailExaminableComponent component, GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
if (Identity.Name(args.Target, EntityManager) != MetaData(args.Target).EntityName)
|
||||
return;
|
||||
|
||||
var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid);
|
||||
|
||||
var verb = new ExamineVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
var markup = new FormattedMessage();
|
||||
markup.AddMarkupOrThrow(component.Content);
|
||||
_examineSystem.SendExamineTooltip(args.User, uid, markup, false, false);
|
||||
},
|
||||
Text = Loc.GetString("detail-examinable-verb-text"),
|
||||
Category = VerbCategory.Examine,
|
||||
Disabled = !detailsRange,
|
||||
Message = detailsRange ? null : Loc.GetString("detail-examinable-verb-disabled"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/examine.svg.192dpi.png"))
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,26 @@ namespace Content.Server.EntityEffects.Effects
|
||||
|
||||
var damageSpec = new DamageSpecifier(Damage);
|
||||
|
||||
var universalReagentDamageModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentDamageModifier;
|
||||
var universalReagentHealModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentHealModifier;
|
||||
|
||||
if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1)
|
||||
{
|
||||
foreach (var (type, val) in damageSpec.DamageDict)
|
||||
{
|
||||
if (val < 0f)
|
||||
{
|
||||
damageSpec.DamageDict[type] = val * universalReagentHealModifier;
|
||||
}
|
||||
if (val > 0f)
|
||||
{
|
||||
damageSpec.DamageDict[type] = val * universalReagentDamageModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
damageSpec = entSys.GetEntitySystem<DamageableSystem>().ApplyUniversalAllModifiers(damageSpec);
|
||||
|
||||
foreach (var group in prototype.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
if (!damageSpec.TryGetDamageInGroup(group, out var amount))
|
||||
@@ -114,17 +134,37 @@ namespace Content.Server.EntityEffects.Effects
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var scale = FixedPoint2.New(1);
|
||||
var damageSpec = new DamageSpecifier(Damage);
|
||||
|
||||
if (args is EntityEffectReagentArgs reagentArgs)
|
||||
{
|
||||
scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale;
|
||||
}
|
||||
|
||||
args.EntityManager.System<DamageableSystem>().TryChangeDamage(
|
||||
args.TargetEntity,
|
||||
Damage * scale,
|
||||
IgnoreResistances,
|
||||
interruptsDoAfters: false);
|
||||
var universalReagentDamageModifier = args.EntityManager.System<DamageableSystem>().UniversalReagentDamageModifier;
|
||||
var universalReagentHealModifier = args.EntityManager.System<DamageableSystem>().UniversalReagentHealModifier;
|
||||
|
||||
if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1)
|
||||
{
|
||||
foreach (var (type, val) in damageSpec.DamageDict)
|
||||
{
|
||||
if (val < 0f)
|
||||
{
|
||||
damageSpec.DamageDict[type] = val * universalReagentHealModifier;
|
||||
}
|
||||
if (val > 0f)
|
||||
{
|
||||
damageSpec.DamageDict[type] = val * universalReagentDamageModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.EntityManager.System<DamageableSystem>()
|
||||
.TryChangeDamage(
|
||||
args.TargetEntity,
|
||||
damageSpec * scale,
|
||||
IgnoreResistances,
|
||||
interruptsDoAfters: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +464,7 @@ public sealed partial class ExplosionSystem
|
||||
}
|
||||
|
||||
// TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
|
||||
_damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true);
|
||||
_damageableSystem.TryChangeDamage(entity, damage * _damageableSystem.UniversalExplosionDamageModifier, ignoreResistances: true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.GameTicking;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -38,6 +39,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
|
||||
@@ -345,7 +347,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
|
||||
|
||||
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
||||
var mapEntityCoords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(pos.MapId), pos, _transformSystem, EntityManager);
|
||||
var mapEntityCoords = _transformSystem.ToCoordinates(_mapSystem.GetMap(pos.MapId), pos);
|
||||
|
||||
// play sound.
|
||||
// for the normal audio, we want everyone in pvs range
|
||||
|
||||
@@ -141,7 +141,7 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
if (!_solutionContainerSystem.ResolveSolution((uid, manager), DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
|
||||
continue;
|
||||
|
||||
if (drainSolution.AvailableVolume <= 0)
|
||||
if (drainSolution.Volume <= 0 && !drain.AutoDrain)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
continue;
|
||||
@@ -158,7 +158,7 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
_puddles.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
|
||||
|
||||
if (_puddles.Count == 0)
|
||||
if (_puddles.Count == 0 && drainSolution.Volume <= 0)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chemistry.TileReactions;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Fluids.Components;
|
||||
using Content.Server.Spreader;
|
||||
@@ -387,23 +389,36 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
||||
private void UpdateSlip(EntityUid entityUid, PuddleComponent component, Solution solution)
|
||||
{
|
||||
var isSlippery = false;
|
||||
var isSuperSlippery = false;
|
||||
// The base sprite is currently at 0.3 so we require at least 2nd tier to be slippery or else it's too hard to see.
|
||||
var amountRequired = FixedPoint2.New(component.OverflowVolume.Float() * LowThreshold);
|
||||
var slipperyAmount = FixedPoint2.Zero;
|
||||
|
||||
// Utilize the defaults from their relevant systems... this sucks, and is a bandaid
|
||||
var launchForwardsMultiplier = SlipperyComponent.DefaultLaunchForwardsMultiplier;
|
||||
var paralyzeTime = SlipperyComponent.DefaultParalyzeTime;
|
||||
var requiredSlipSpeed = StepTriggerComponent.DefaultRequiredTriggeredSpeed;
|
||||
|
||||
foreach (var (reagent, quantity) in solution.Contents)
|
||||
{
|
||||
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
|
||||
|
||||
if (reagentProto.Slippery)
|
||||
{
|
||||
slipperyAmount += quantity;
|
||||
if (!reagentProto.Slippery)
|
||||
continue;
|
||||
slipperyAmount += quantity;
|
||||
|
||||
if (slipperyAmount > amountRequired)
|
||||
{
|
||||
isSlippery = true;
|
||||
break;
|
||||
}
|
||||
if (slipperyAmount <= amountRequired)
|
||||
continue;
|
||||
isSlippery = true;
|
||||
|
||||
foreach (var tileReaction in reagentProto.TileReactions)
|
||||
{
|
||||
if (tileReaction is not SpillTileReaction spillTileReaction)
|
||||
continue;
|
||||
isSuperSlippery = spillTileReaction.SuperSlippery;
|
||||
launchForwardsMultiplier = launchForwardsMultiplier < spillTileReaction.LaunchForwardsMultiplier ? spillTileReaction.LaunchForwardsMultiplier : launchForwardsMultiplier;
|
||||
requiredSlipSpeed = requiredSlipSpeed > spillTileReaction.RequiredSlipSpeed ? spillTileReaction.RequiredSlipSpeed : requiredSlipSpeed;
|
||||
paralyzeTime = paralyzeTime < spillTileReaction.ParalyzeTime ? spillTileReaction.ParalyzeTime : paralyzeTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,6 +428,14 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
||||
_stepTrigger.SetActive(entityUid, true, comp);
|
||||
var friction = EnsureComp<TileFrictionModifierComponent>(entityUid);
|
||||
_tile.SetModifier(entityUid, TileFrictionController.DefaultFriction * 0.5f, friction);
|
||||
|
||||
if (!TryComp<SlipperyComponent>(entityUid, out var slipperyComponent))
|
||||
return;
|
||||
slipperyComponent.SuperSlippery = isSuperSlippery;
|
||||
_stepTrigger.SetRequiredTriggerSpeed(entityUid, requiredSlipSpeed);
|
||||
slipperyComponent.LaunchForwardsMultiplier = launchForwardsMultiplier;
|
||||
slipperyComponent.ParalyzeTime = paralyzeTime;
|
||||
|
||||
}
|
||||
else if (TryComp<StepTriggerComponent>(entityUid, out var comp))
|
||||
{
|
||||
|
||||
@@ -585,7 +585,7 @@ namespace Content.Server.GameTicking
|
||||
|
||||
// This ordering mechanism isn't great (no ordering of minds) but functions
|
||||
var listOfPlayerInfoFinal = listOfPlayerInfo.OrderBy(pi => pi.PlayerOOCName).ToArray();
|
||||
var sound = RoundEndSoundCollection == null ? null : _audio.GetSound(new SoundCollectionSpecifier(RoundEndSoundCollection));
|
||||
var sound = RoundEndSoundCollection == null ? null : _audio.ResolveSound(new SoundCollectionSpecifier(RoundEndSoundCollection));
|
||||
|
||||
var roundEndMessageEvent = new RoundEndMessageEvent(
|
||||
gamemodeTitle,
|
||||
|
||||
@@ -361,6 +361,7 @@ namespace Content.Server.GameTicking
|
||||
if (DummyTicker)
|
||||
return;
|
||||
|
||||
var makeObserver = false;
|
||||
Entity<MindComponent?>? mind = player.GetMind();
|
||||
if (mind == null)
|
||||
{
|
||||
@@ -368,10 +369,13 @@ namespace Content.Server.GameTicking
|
||||
var (mindId, mindComp) = _mind.CreateMind(player.UserId, name);
|
||||
mind = (mindId, mindComp);
|
||||
_mind.SetUserId(mind.Value, player.UserId);
|
||||
_roles.MindAddRole(mind.Value, "MindRoleObserver");
|
||||
makeObserver = true;
|
||||
}
|
||||
|
||||
var ghost = _ghost.SpawnGhost(mind.Value);
|
||||
if (makeObserver)
|
||||
_roles.MindAddRole(mind.Value, "MindRoleObserver");
|
||||
|
||||
_adminLogger.Add(LogType.LateJoin,
|
||||
LogImpact.Low,
|
||||
$"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}.");
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for the SurvivorRuleSystem. Game rule that turns everyone into a survivor and gives them the objective to escape centcom alive.
|
||||
/// Started by Wizard Summon Guns/Magic spells.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SurvivorRuleSystem))]
|
||||
public sealed partial class SurvivorRuleComponent : Component;
|
||||
108
Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs
Normal file
108
Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Survivor.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class SurvivorRuleSystem : GameRuleSystem<SurvivorRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly TransformSystem _xform = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _eShuttle = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SurvivorRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
}
|
||||
|
||||
// TODO: Planned rework post wizard release when RandomGlobalSpawnSpell becomes a gamerule
|
||||
protected override void Started(EntityUid uid, SurvivorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var allAliveHumanMinds = _mind.GetAliveHumans();
|
||||
|
||||
foreach (var humanMind in allAliveHumanMinds)
|
||||
{
|
||||
if (!humanMind.Comp.OwnedEntity.HasValue)
|
||||
continue;
|
||||
|
||||
var mind = humanMind.Owner;
|
||||
var ent = humanMind.Comp.OwnedEntity.Value;
|
||||
|
||||
if (HasComp<SurvivorComponent>(mind) || _tag.HasTag(mind, "InvalidForSurvivorAntag"))
|
||||
continue;
|
||||
|
||||
EnsureComp<SurvivorComponent>(mind);
|
||||
_role.MindAddRole(mind, "MindRoleSurvivor");
|
||||
_antag.SendBriefing(ent, Loc.GetString("survivor-role-greeting"), Color.Olive, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetBriefing(Entity<SurvivorRoleComponent> ent, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(Loc.GetString("survivor-role-greeting"));
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
SurvivorRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
base.AppendRoundEndText(uid, component, gameRule, ref args);
|
||||
|
||||
// Using this instead of alive antagonists to make checking for shuttle & if the ent is alive easier
|
||||
var existingSurvivors = AllEntityQuery<SurvivorComponent, MindComponent>();
|
||||
|
||||
var deadSurvivors = 0;
|
||||
var aliveMarooned = 0;
|
||||
var aliveOnShuttle = 0;
|
||||
var eShuttle = _eShuttle.GetShuttle();
|
||||
|
||||
while (existingSurvivors.MoveNext(out _, out _, out var mindComp))
|
||||
{
|
||||
// If their brain is gone or they respawned/became a ghost role
|
||||
if (mindComp.CurrentEntity is null)
|
||||
{
|
||||
deadSurvivors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var survivor = mindComp.CurrentEntity.Value;
|
||||
|
||||
if (!_mobState.IsAlive(survivor))
|
||||
{
|
||||
deadSurvivors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eShuttle != null && eShuttle.Value.IsValid() && (Transform(eShuttle.Value).MapID == _xform.GetMapCoordinates(survivor).MapId))
|
||||
{
|
||||
aliveOnShuttle++;
|
||||
continue;
|
||||
}
|
||||
|
||||
aliveMarooned++;
|
||||
}
|
||||
|
||||
args.AddLine(Loc.GetString("survivor-round-end-dead-count", ("deadCount", deadSurvivors)));
|
||||
args.AddLine(Loc.GetString("survivor-round-end-alive-count", ("aliveCount", aliveMarooned)));
|
||||
args.AddLine(Loc.GetString("survivor-round-end-alive-on-shuttle-count", ("aliveCount", aliveOnShuttle)));
|
||||
|
||||
// Player manifest at EOR shows who's a survivor so no need for extra info here.
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -64,6 +65,7 @@ namespace Content.Server.Ghost
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
|
||||
private EntityQuery<GhostComponent> _ghostQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
@@ -403,8 +405,11 @@ namespace Content.Server.Ghost
|
||||
public void MakeVisible(bool visible)
|
||||
{
|
||||
var entityQuery = EntityQueryEnumerator<GhostComponent, VisibilityComponent>();
|
||||
while (entityQuery.MoveNext(out var uid, out _, out var vis))
|
||||
while (entityQuery.MoveNext(out var uid, out var _, out var vis))
|
||||
{
|
||||
if (!_tag.HasTag(uid, "AllowGhostShownByEvent"))
|
||||
continue;
|
||||
|
||||
if (visible)
|
||||
{
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -26,6 +25,7 @@ public sealed class RandomGiftSystem : EntitySystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private readonly List<string> _possibleGiftsSafe = new();
|
||||
private readonly List<string> _possibleGiftsUnsafe = new();
|
||||
@@ -63,11 +63,16 @@ public sealed class RandomGiftSystem : EntitySystem
|
||||
if (component.Wrapper is not null)
|
||||
Spawn(component.Wrapper, coords);
|
||||
|
||||
args.Handled = true;
|
||||
_audio.PlayPvs(component.Sound, args.User);
|
||||
Del(uid);
|
||||
|
||||
// Don't delete the entity in the event bus, so we queue it for deletion.
|
||||
// We need the free hand for the new item, so we send it to nullspace.
|
||||
_transform.DetachEntity(uid, Transform(uid));
|
||||
QueueDel(uid);
|
||||
|
||||
_hands.PickupOrDrop(args.User, handsEnt);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGiftMapInit(EntityUid uid, RandomGiftComponent component, MapInitEvent args)
|
||||
|
||||
@@ -115,13 +115,13 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (source != null)
|
||||
{
|
||||
// Close any AI request windows
|
||||
if (_stationAiSystem.TryGetStationAiCore(args.Actor, out var stationAiCore) && stationAiCore != null)
|
||||
if (_stationAiSystem.TryGetCore(args.Actor, out var stationAiCore))
|
||||
_userInterfaceSystem.CloseUi(receiver.Owner, HolopadUiKey.AiRequestWindow, args.Actor);
|
||||
|
||||
// Try to warn the AI if the source of the call is out of its range
|
||||
if (TryComp<TelephoneComponent>(stationAiCore, out var stationAiTelephone) &&
|
||||
TryComp<TelephoneComponent>(source, out var sourceTelephone) &&
|
||||
!_telephoneSystem.IsSourceInRangeOfReceiver((stationAiCore.Value.Owner, stationAiTelephone), (source.Value.Owner, sourceTelephone)))
|
||||
!_telephoneSystem.IsSourceInRangeOfReceiver((stationAiCore.Owner, stationAiTelephone), (source.Value.Owner, sourceTelephone)))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-reach-holopad"), receiver, args.Actor);
|
||||
return;
|
||||
@@ -150,11 +150,11 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
// If the user is an AI, end all calls originating from its
|
||||
// associated core to ensure that any broadcasts will end
|
||||
if (!TryComp<StationAiHeldComponent>(args.Actor, out var stationAiHeld) ||
|
||||
!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore))
|
||||
!_stationAiSystem.TryGetCore(args.Actor, out var stationAiCore))
|
||||
return;
|
||||
|
||||
if (TryComp<TelephoneComponent>(stationAiCore, out var telephone))
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value, telephone));
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore, telephone));
|
||||
}
|
||||
|
||||
private void OnHolopadActivateProjector(Entity<HolopadComponent> entity, ref HolopadActivateProjectorMessage args)
|
||||
@@ -176,17 +176,17 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
// Link the AI to the holopad they are broadcasting from
|
||||
LinkHolopadToUser(source, args.Actor);
|
||||
|
||||
if (!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore) ||
|
||||
stationAiCore.Value.Comp.RemoteEntity == null ||
|
||||
if (!_stationAiSystem.TryGetCore(args.Actor, out var stationAiCore) ||
|
||||
stationAiCore.Comp?.RemoteEntity == null ||
|
||||
!TryComp<HolopadComponent>(stationAiCore, out var stationAiCoreHolopad))
|
||||
return;
|
||||
|
||||
// Execute the broadcast, but have it originate from the AI core
|
||||
ExecuteBroadcast((stationAiCore.Value, stationAiCoreHolopad), args.Actor);
|
||||
ExecuteBroadcast((stationAiCore, stationAiCoreHolopad), args.Actor);
|
||||
|
||||
// Switch the AI's perspective from free roaming to the target holopad
|
||||
_xformSystem.SetCoordinates(stationAiCore.Value.Comp.RemoteEntity.Value, Transform(source).Coordinates);
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, false);
|
||||
_xformSystem.SetCoordinates(stationAiCore.Comp.RemoteEntity.Value, Transform(source).Coordinates);
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, false);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -220,10 +220,10 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
|
||||
reachableAiCores.Add((receiverUid, receiverTelephone));
|
||||
|
||||
if (!_stationAiSystem.TryGetInsertedAI((receiver, receiverStationAiCore), out var insertedAi))
|
||||
if (!_stationAiSystem.TryGetHeld((receiver, receiverStationAiCore), out var insertedAi))
|
||||
continue;
|
||||
|
||||
if (_userInterfaceSystem.TryOpenUi(receiverUid, HolopadUiKey.AiRequestWindow, insertedAi.Value.Owner))
|
||||
if (_userInterfaceSystem.TryOpenUi(receiverUid, HolopadUiKey.AiRequestWindow, insertedAi))
|
||||
LinkHolopadToUser(entity, args.Actor);
|
||||
}
|
||||
|
||||
@@ -274,8 +274,8 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
return;
|
||||
|
||||
// Auto-close the AI request window
|
||||
if (_stationAiSystem.TryGetInsertedAI((entity, stationAiCore), out var insertedAi))
|
||||
_userInterfaceSystem.CloseUi(entity.Owner, HolopadUiKey.AiRequestWindow, insertedAi.Value.Owner);
|
||||
if (_stationAiSystem.TryGetHeld((entity, stationAiCore), out var insertedAi))
|
||||
_userInterfaceSystem.CloseUi(entity.Owner, HolopadUiKey.AiRequestWindow, insertedAi);
|
||||
}
|
||||
|
||||
private void OnTelephoneMessageSent(Entity<HolopadComponent> holopad, ref TelephoneMessageSentEvent args)
|
||||
@@ -381,13 +381,13 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (!TryComp<StationAiHeldComponent>(entity, out var entityStationAiHeld))
|
||||
return;
|
||||
|
||||
if (!_stationAiSystem.TryGetStationAiCore((entity, entityStationAiHeld), out var stationAiCore))
|
||||
if (!_stationAiSystem.TryGetCore(entity, out var stationAiCore))
|
||||
return;
|
||||
|
||||
if (!TryComp<TelephoneComponent>(stationAiCore, out var stationAiCoreTelephone))
|
||||
return;
|
||||
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value, stationAiCoreTelephone));
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore, stationAiCoreTelephone));
|
||||
}
|
||||
|
||||
private void AddToggleProjectorVerb(Entity<HolopadComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
@@ -407,8 +407,8 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (!TryComp<StationAiHeldComponent>(user, out var userAiHeld))
|
||||
return;
|
||||
|
||||
if (!_stationAiSystem.TryGetStationAiCore((user, userAiHeld), out var stationAiCore) ||
|
||||
stationAiCore.Value.Comp.RemoteEntity == null)
|
||||
if (!_stationAiSystem.TryGetCore(user, out var stationAiCore) ||
|
||||
stationAiCore.Comp?.RemoteEntity == null)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
@@ -595,17 +595,17 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
{
|
||||
// Check if the associated holopad user is an AI
|
||||
if (TryComp<StationAiHeldComponent>(entity.Comp.User, out var stationAiHeld) &&
|
||||
_stationAiSystem.TryGetStationAiCore((entity.Comp.User.Value, stationAiHeld), out var stationAiCore))
|
||||
_stationAiSystem.TryGetCore(entity.Comp.User.Value, out var stationAiCore))
|
||||
{
|
||||
// Return the AI eye to free roaming
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, true);
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, true);
|
||||
|
||||
// If the AI core is still broadcasting, end its calls
|
||||
if (entity.Owner != stationAiCore.Value.Owner &&
|
||||
if (entity.Owner != stationAiCore.Owner &&
|
||||
TryComp<TelephoneComponent>(stationAiCore, out var stationAiCoreTelephone) &&
|
||||
_telephoneSystem.IsTelephoneEngaged((stationAiCore.Value.Owner, stationAiCoreTelephone)))
|
||||
_telephoneSystem.IsTelephoneEngaged((stationAiCore.Owner, stationAiCoreTelephone)))
|
||||
{
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value.Owner, stationAiCoreTelephone));
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore.Owner, stationAiCoreTelephone));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,8 +625,8 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (!TryComp<StationAiHeldComponent>(user, out var userAiHeld))
|
||||
return;
|
||||
|
||||
if (!_stationAiSystem.TryGetStationAiCore((user, userAiHeld), out var stationAiCore) ||
|
||||
stationAiCore.Value.Comp.RemoteEntity == null)
|
||||
if (!_stationAiSystem.TryGetCore(user, out var stationAiCore) ||
|
||||
stationAiCore.Comp?.RemoteEntity == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<TelephoneComponent>(stationAiCore, out var stationAiTelephone))
|
||||
@@ -635,7 +635,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (!TryComp<HolopadComponent>(stationAiCore, out var stationAiHolopad))
|
||||
return;
|
||||
|
||||
var source = new Entity<TelephoneComponent>(stationAiCore.Value, stationAiTelephone);
|
||||
var source = new Entity<TelephoneComponent>(stationAiCore, stationAiTelephone);
|
||||
|
||||
// Check if the AI is unable to activate the projector (unlikely this will ever pass; its just a safeguard)
|
||||
if (!_telephoneSystem.IsSourceInRangeOfReceiver(source, receiver))
|
||||
@@ -658,11 +658,11 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (!_telephoneSystem.IsSourceConnectedToReceiver(source, receiver))
|
||||
return;
|
||||
|
||||
LinkHolopadToUser((stationAiCore.Value, stationAiHolopad), user);
|
||||
LinkHolopadToUser((stationAiCore, stationAiHolopad), user);
|
||||
|
||||
// Switch the AI's perspective from free roaming to the target holopad
|
||||
_xformSystem.SetCoordinates(stationAiCore.Value.Comp.RemoteEntity.Value, Transform(entity).Coordinates);
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, false);
|
||||
_xformSystem.SetCoordinates(stationAiCore.Comp.RemoteEntity.Value, Transform(entity).Coordinates);
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, false);
|
||||
|
||||
// Open the holopad UI if it hasn't been opened yet
|
||||
if (TryComp<UserInterfaceComponent>(entity, out var entityUserInterfaceComponent))
|
||||
|
||||
@@ -23,7 +23,7 @@ using System.Numerics;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.DetailExaminable;
|
||||
using Content.Shared.DetailExaminable;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery) &&
|
||||
!TryComp(uid, out battery))
|
||||
{
|
||||
_audio.PlayPvs(_audio.GetSound(component.TurnOnFailSound), uid);
|
||||
_audio.PlayPvs(_audio.ResolveSound(component.TurnOnFailSound), uid);
|
||||
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-missing-message"), uid, user);
|
||||
return false;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
// Simple enough.
|
||||
if (component.Wattage > battery.CurrentCharge)
|
||||
{
|
||||
_audio.PlayPvs(_audio.GetSound(component.TurnOnFailSound), uid);
|
||||
_audio.PlayPvs(_audio.ResolveSound(component.TurnOnFailSound), uid);
|
||||
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-dead-message"), uid, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic.Events;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Magic;
|
||||
|
||||
public sealed class MagicSystem : SharedMagicSystem
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,4 +40,20 @@ public sealed class MagicSystem : SharedMagicSystem
|
||||
Spawn(ev.Effect, perfXForm.Coordinates);
|
||||
Spawn(ev.Effect, targetXForm.Coordinates);
|
||||
}
|
||||
|
||||
protected override void OnRandomGlobalSpawnSpell(RandomGlobalSpawnSpellEvent ev)
|
||||
{
|
||||
base.OnRandomGlobalSpawnSpell(ev);
|
||||
|
||||
if (!ev.MakeSurvivorAntagonist)
|
||||
return;
|
||||
|
||||
if (_mind.TryGetMind(ev.Performer, out var mind, out _) && !_tag.HasTag(mind, "InvalidForSurvivorAntag"))
|
||||
_tag.AddTag(mind, "InvalidForSurvivorAntag");
|
||||
|
||||
EntProtoId survivorRule = "Survivor";
|
||||
|
||||
if (!_gameTicker.IsGameRuleActive<SurvivorRuleComponent>())
|
||||
_gameTicker.StartGameRule(survivorRule);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Mapping
|
||||
@@ -35,6 +36,8 @@ namespace Content.Server.Mapping
|
||||
var opts = CompletionHelper.UserFilePath(args[1], res.UserData)
|
||||
.Concat(CompletionHelper.ContentFilePath(args[1], res));
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-mapping-path"));
|
||||
case 3:
|
||||
return CompletionResult.FromHintOptions(["false", "true"], Loc.GetString("cmd-mapping-hint-grid"));
|
||||
}
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
@@ -47,7 +50,7 @@ namespace Content.Server.Mapping
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 2)
|
||||
if (args.Length > 3)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
@@ -57,12 +60,20 @@ namespace Content.Server.Mapping
|
||||
shell.WriteLine(Loc.GetString("cmd-mapping-warning"));
|
||||
#endif
|
||||
|
||||
// For backwards compatibility, isGrid is optional and we allow mappers to try load grids without explicitly
|
||||
// specifying that they are loading a grid. Currently content is not allowed to override a map's MapId, so
|
||||
// without engine changes this needs to be done by brute force by just trying to load it as a map first.
|
||||
// This can result in errors being logged if the file is actually a grid, but the command should still work.
|
||||
// yipeeee
|
||||
bool? isGrid = args.Length < 3 ? null : bool.Parse(args[2]);
|
||||
|
||||
MapId mapId;
|
||||
string? toLoad = null;
|
||||
var mapSys = _entities.System<SharedMapSystem>();
|
||||
Entity<MapGridComponent>? grid = null;
|
||||
|
||||
// Get the map ID to use
|
||||
if (args.Length is 1 or 2)
|
||||
if (args.Length > 0)
|
||||
{
|
||||
if (!int.TryParse(args[0], out var intMapId))
|
||||
{
|
||||
@@ -79,7 +90,7 @@ namespace Content.Server.Mapping
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.MapExists(mapId))
|
||||
if (mapSys.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-mapping-exists", ("mapId", mapId)));
|
||||
return;
|
||||
@@ -93,12 +104,43 @@ namespace Content.Server.Mapping
|
||||
else
|
||||
{
|
||||
var path = new ResPath(args[1]);
|
||||
toLoad = path.FilenameWithoutExtension;
|
||||
var opts = new DeserializationOptions {StoreYamlUids = true};
|
||||
_entities.System<MapLoaderSystem>().TryLoadMapWithId(mapId, path, out _, out _, opts);
|
||||
var loader = _entities.System<MapLoaderSystem>();
|
||||
|
||||
if (isGrid == true)
|
||||
{
|
||||
mapSys.CreateMap(mapId, runMapInit: false);
|
||||
if (!loader.TryLoadGrid(mapId, path, out grid, opts))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-mapping-error"));
|
||||
mapSys.DeleteMap(mapId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!loader.TryLoadMapWithId(mapId, path, out _, out _, opts))
|
||||
{
|
||||
if (isGrid == false)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-mapping-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
// isGrid was not specified and loading it as a map failed, so we fall back to trying to load
|
||||
// the file as a grid
|
||||
shell.WriteLine(Loc.GetString("cmd-mapping-try-grid"));
|
||||
mapSys.CreateMap(mapId, runMapInit: false);
|
||||
if (!loader.TryLoadGrid(mapId, path, out grid, opts))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-mapping-error"));
|
||||
mapSys.DeleteMap(mapId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// was the map actually created or did it fail somehow?
|
||||
if (!_map.MapExists(mapId))
|
||||
if (!mapSys.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-mapping-error"));
|
||||
return;
|
||||
@@ -117,19 +159,25 @@ namespace Content.Server.Mapping
|
||||
}
|
||||
|
||||
// don't interrupt mapping with events or auto-shuttle
|
||||
shell.ExecuteCommand("sudo cvar events.enabled false");
|
||||
shell.ExecuteCommand("sudo cvar shuttle.auto_call_time 0");
|
||||
shell.ExecuteCommand("changecvar events.enabled false");
|
||||
shell.ExecuteCommand("changecvar shuttle.auto_call_time 0");
|
||||
|
||||
var auto = _entities.System<MappingSystem>();
|
||||
if (grid != null)
|
||||
auto.ToggleAutosave(grid.Value.Owner, toLoad ?? "NEWGRID");
|
||||
else
|
||||
auto.ToggleAutosave(mapId, toLoad ?? "NEWMAP");
|
||||
|
||||
if (_cfg.GetCVar(CCVars.AutosaveEnabled))
|
||||
shell.ExecuteCommand($"toggleautosave {mapId} {toLoad ?? "NEWMAP"}");
|
||||
shell.ExecuteCommand($"tp 0 0 {mapId}");
|
||||
shell.RemoteExecuteCommand("mappingclientsidesetup");
|
||||
_map.SetMapPaused(mapId, true);
|
||||
DebugTools.Assert(mapSys.IsPaused(mapId));
|
||||
|
||||
if (args.Length == 2)
|
||||
shell.WriteLine(Loc.GetString("cmd-mapping-success-load",("mapId",mapId),("path", args[1])));
|
||||
else
|
||||
if (args.Length != 2)
|
||||
shell.WriteLine(Loc.GetString("cmd-mapping-success", ("mapId", mapId)));
|
||||
else if (grid == null)
|
||||
shell.WriteLine(Loc.GetString("cmd-mapping-success-load", ("mapId", mapId), ("path", args[1])));
|
||||
else
|
||||
shell.WriteLine(Loc.GetString("cmd-mapping-success-load-grid", ("mapId", mapId), ("path", args[1])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ using System.IO;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class MappingSystem : EntitySystem
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resMan = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class MappingSystem : EntitySystem
|
||||
/// map id -> next autosave timespan & original filename.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Dictionary<MapId, (TimeSpan next, string fileName)> _currentlyAutosaving = new();
|
||||
private Dictionary<EntityUid, (TimeSpan next, string fileName)> _currentlyAutosaving = new();
|
||||
|
||||
private bool _autosaveEnabled;
|
||||
|
||||
@@ -60,25 +60,29 @@ public sealed class MappingSystem : EntitySystem
|
||||
if (!_autosaveEnabled)
|
||||
return;
|
||||
|
||||
foreach (var (map, (time, name))in _currentlyAutosaving.ToArray())
|
||||
foreach (var (uid, (time, name))in _currentlyAutosaving)
|
||||
{
|
||||
if (_timing.RealTime <= time)
|
||||
continue;
|
||||
|
||||
if (!_mapManager.MapExists(map) || _mapManager.IsMapInitialized(map))
|
||||
if (LifeStage(uid) >= EntityLifeStage.MapInitialized)
|
||||
{
|
||||
Log.Warning($"Can't autosave map {map}; it doesn't exist, or is initialized. Removing from autosave.");
|
||||
_currentlyAutosaving.Remove(map);
|
||||
return;
|
||||
Log.Warning($"Can't autosave entity {uid}; it doesn't exist, or is initialized. Removing from autosave.");
|
||||
_currentlyAutosaving.Remove(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentlyAutosaving[uid] = (CalculateNextTime(), name);
|
||||
var saveDir = Path.Combine(_cfg.GetCVar(CCVars.AutosaveDirectory), name);
|
||||
_resMan.UserData.CreateDir(new ResPath(saveDir).ToRootedPath());
|
||||
|
||||
var path = Path.Combine(saveDir, $"{DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss")}-AUTO.yml");
|
||||
_currentlyAutosaving[map] = (CalculateNextTime(), name);
|
||||
Log.Info($"Autosaving map {name} ({map}) to {path}. Next save in {ReadableTimeLeft(map)} seconds.");
|
||||
_loader.TrySaveMap(map, new ResPath(path));
|
||||
var path = new ResPath(Path.Combine(saveDir, $"{DateTime.Now:yyyy-M-dd_HH.mm.ss}-AUTO.yml"));
|
||||
Log.Info($"Autosaving map {name} ({uid}) to {path}. Next save in {ReadableTimeLeft(uid)} seconds.");
|
||||
|
||||
if (HasComp<MapComponent>(uid))
|
||||
_loader.TrySaveMap(uid, path);
|
||||
else
|
||||
_loader.TrySaveGrid(uid, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,34 +91,41 @@ public sealed class MappingSystem : EntitySystem
|
||||
return _timing.RealTime + TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.AutosaveInterval));
|
||||
}
|
||||
|
||||
private double ReadableTimeLeft(MapId map)
|
||||
private double ReadableTimeLeft(EntityUid uid)
|
||||
{
|
||||
return Math.Round(_currentlyAutosaving[map].next.TotalSeconds - _timing.RealTime.TotalSeconds);
|
||||
return Math.Round(_currentlyAutosaving[uid].next.TotalSeconds - _timing.RealTime.TotalSeconds);
|
||||
}
|
||||
|
||||
#region Public API
|
||||
|
||||
public void ToggleAutosave(MapId map, string? path=null)
|
||||
public void ToggleAutosave(MapId map, string? path = null)
|
||||
{
|
||||
if (_map.TryGetMap(map, out var uid))
|
||||
ToggleAutosave(uid.Value, path);
|
||||
}
|
||||
|
||||
public void ToggleAutosave(EntityUid uid, string? path=null)
|
||||
{
|
||||
if (!_autosaveEnabled)
|
||||
return;
|
||||
|
||||
if (path != null && _currentlyAutosaving.TryAdd(map, (CalculateNextTime(), Path.GetFileName(path))))
|
||||
{
|
||||
if (!_mapManager.MapExists(map) || _mapManager.IsMapInitialized(map))
|
||||
{
|
||||
Log.Warning("Tried to enable autosaving on non-existant or already initialized map!");
|
||||
_currentlyAutosaving.Remove(map);
|
||||
return;
|
||||
}
|
||||
if (_currentlyAutosaving.Remove(uid) || path == null)
|
||||
return;
|
||||
|
||||
Log.Info($"Started autosaving map {path} ({map}). Next save in {ReadableTimeLeft(map)} seconds.");
|
||||
}
|
||||
else
|
||||
if (LifeStage(uid) >= EntityLifeStage.MapInitialized)
|
||||
{
|
||||
_currentlyAutosaving.Remove(map);
|
||||
Log.Info($"Stopped autosaving on map {map}");
|
||||
Log.Error("Tried to enable autosaving on a post map-init entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<MapComponent>(uid) && !HasComp<MapGridComponent>(uid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(uid)} is neither a grid or map");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentlyAutosaving[uid] = (CalculateNextTime(), Path.GetFileName(path));
|
||||
Log.Info($"Started autosaving map {path} ({uid}). Next save in {ReadableTimeLeft(uid)} seconds.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -83,7 +83,7 @@ public sealed class HealingSystem : EntitySystem
|
||||
if (healing.ModifyBloodLevel != 0)
|
||||
_bloodstreamSystem.TryModifyBloodLevel(entity.Owner, healing.ModifyBloodLevel);
|
||||
|
||||
var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage, true, origin: args.Args.User);
|
||||
var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage * _damageable.UniversalTopicalsHealModifier, true, origin: args.Args.User);
|
||||
|
||||
if (healed == null && healing.BloodlossModifier != 0)
|
||||
return;
|
||||
|
||||
@@ -27,6 +27,9 @@ public sealed class MousetrapSystem : EntitySystem
|
||||
|
||||
private void OnUseInHand(EntityUid uid, MousetrapComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
component.IsActive = !component.IsActive;
|
||||
_popupSystem.PopupEntity(component.IsActive
|
||||
? Loc.GetString("mousetrap-on-activate")
|
||||
@@ -35,6 +38,8 @@ public sealed class MousetrapSystem : EntitySystem
|
||||
args.User);
|
||||
|
||||
UpdateVisuals(uid);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnStepTriggerAttempt(EntityUid uid, MousetrapComponent component, ref StepTriggerAttemptEvent args)
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
/// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx
|
||||
/// </summary>
|
||||
private float _nukeSongLength;
|
||||
private string _selectedNukeSong = String.Empty;
|
||||
private ResolvedSoundSpecifier _selectedNukeSong = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Time to leave between the nuke song and the nuke alarm playing.
|
||||
@@ -302,7 +302,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
|
||||
// Start playing the nuke event song so that it ends a couple seconds before the alert sound
|
||||
// should play
|
||||
if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !string.IsNullOrEmpty(_selectedNukeSong))
|
||||
if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !ResolvedSoundSpecifier.IsNullOrEmpty(_selectedNukeSong))
|
||||
{
|
||||
_sound.DispatchStationEventMusic(uid, _selectedNukeSong, StationEventMusicType.Nuke);
|
||||
nuke.PlayedNukeSong = true;
|
||||
@@ -311,7 +311,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
// play alert sound if time is running out
|
||||
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
|
||||
{
|
||||
_sound.PlayGlobalOnStation(uid, _audio.GetSound(nuke.AlertSound), new AudioParams{Volume = -5f});
|
||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(nuke.AlertSound), new AudioParams{Volume = -5f});
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
||||
nuke.PlayedAlertSound = true;
|
||||
UpdateAppearance(uid, nuke);
|
||||
@@ -469,7 +469,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
var posText = $"({x}, {y})";
|
||||
|
||||
// We are collapsing the randomness here, otherwise we would get separate random song picks for checking duration and when actually playing the song afterwards
|
||||
_selectedNukeSong = _audio.GetSound(component.ArmMusic);
|
||||
_selectedNukeSong = _audio.ResolveSound(component.ArmMusic);
|
||||
|
||||
// warn a crew
|
||||
var announcement = Loc.GetString("nuke-component-announcement-armed",
|
||||
@@ -478,7 +478,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, false, null, Color.Red);
|
||||
|
||||
_sound.PlayGlobalOnStation(uid, _audio.GetSound(component.ArmSound));
|
||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.ArmSound));
|
||||
_nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;
|
||||
|
||||
// turn on the spinny light
|
||||
@@ -519,7 +519,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
|
||||
|
||||
component.PlayedNukeSong = false;
|
||||
_sound.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound));
|
||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.DisarmSound));
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
||||
|
||||
// reset nuke remaining time to either itself or the minimum time, whichever is higher
|
||||
|
||||
@@ -41,7 +41,9 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
|
||||
protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie)
|
||||
{
|
||||
_audio.PlayPvs(_audio.GetSound(creamPie.Sound), uid, AudioParams.Default.WithVariation(0.125f));
|
||||
// The entity is deleted, so play the sound at its position rather than parenting
|
||||
var coordinates = Transform(uid).Coordinates;
|
||||
_audio.PlayPvs(_audio.ResolveSound(creamPie.Sound), coordinates, AudioParams.Default.WithVariation(0.125f));
|
||||
|
||||
if (EntityManager.TryGetComponent(uid, out FoodComponent? foodComp))
|
||||
{
|
||||
|
||||
@@ -37,6 +37,8 @@ public sealed class PAISystem : SharedPAISystem
|
||||
|
||||
private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args)
|
||||
{
|
||||
// Not checking for Handled because ToggleableGhostRoleSystem already marks it as such.
|
||||
|
||||
if (!TryComp<MindContainerComponent>(uid, out var mind) || !mind.HasMind)
|
||||
component.LastUser = args.User;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user