Merge pull request #1743 from space-syndicate/upstream-sync

Upstream sync
This commit is contained in:
Morb 2024-01-10 03:00:40 +03:00 • committed by GitHub
commit 71b9b49760
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1007 changed files with 603177 additions and 698018 deletions

View file

@ -14,7 +14,7 @@ jobs:
steps:
# - name: Install dependencies
# run: sudo apt-get install -y python3-paramiko
# run: sudo apt-get install -y python3-paramiko python3-lxml
- uses: actions/checkout@v3.6.0
with:

View file

@ -48,6 +48,7 @@ namespace Content.Benchmarks
var componentFactory = new Mock<IComponentFactory>();
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg);
componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index<DummyComponent>() });
IoCManager.RegisterInstance<IComponentFactory>(componentFactory.Object);

View file

@ -1,8 +1,8 @@
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
namespace Content.Client.Atmos.UI
{
@ -50,7 +50,7 @@ namespace Content.Client.Atmos.UI
private void OnFilterTransferRatePressed(string value)
{
float rate = float.TryParse(value, out var parsed) ? parsed : 0f;
var rate = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
SendMessage(new GasFilterChangeRateMessage(rate));
}

View file

@ -9,7 +9,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-filter-ui-filter-transfer-rate}"/>
<LineEdit Name="FilterTransferRateInput" MinSize="60 0" />
<LineEdit Name="FilterTransferRateInput" MinSize="70 0" />
<Button Name="SetFilterRate" Text="{Loc comp-gas-filter-ui-filter-set-rate}" Disabled="True"/>
</BoxContainer>

View file

@ -49,7 +49,7 @@ namespace Content.Client.Atmos.UI
public void SetTransferRate(float rate)
{
FilterTransferRateInput.Text = rate.ToString(CultureInfo.InvariantCulture);
FilterTransferRateInput.Text = rate.ToString(CultureInfo.CurrentCulture);
}
public void SetFilterStatus(bool enabled)

View file

@ -1,5 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@ -47,7 +48,7 @@ namespace Content.Client.Atmos.UI
private void OnMixerOutputPressurePressed(string value)
{
var pressure = float.TryParse(value, out var parsed) ? parsed : 0f;
var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (pressure > MaxPressure)
pressure = MaxPressure;
@ -57,7 +58,7 @@ namespace Content.Client.Atmos.UI
private void OnMixerSetPercentagePressed(string value)
{
// We don't need to send both nodes because it's just 100.0f - node
var node = float.TryParse(value, out var parsed) ? parsed : 1.0f;
var node = UserInputParser.TryFloat(value, out var parsed) ? parsed : 1.0f;
node = Math.Clamp(node, 0f, 100.0f);

View file

@ -10,7 +10,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-mixer-ui-mixer-output-pressure}"/>
<Control MinSize="5 0" />
<LineEdit Name="MixerPressureOutputInput" MinSize="60 0" />
<LineEdit Name="MixerPressureOutputInput" MinSize="70 0" />
<Control MinSize="5 0" />
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-mixer-ui-mixer-max}" />
<Control MinSize="5 0" />

View file

@ -45,7 +45,7 @@ namespace Content.Client.Atmos.UI
SetMaxPressureButton.OnPressed += _ =>
{
MixerPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.InvariantCulture);
MixerPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture);
SetOutputPressureButton.Disabled = false;
};
@ -69,16 +69,16 @@ namespace Content.Client.Atmos.UI
public void SetOutputPressure(float pressure)
{
MixerPressureOutputInput.Text = pressure.ToString(CultureInfo.InvariantCulture);
MixerPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture);
}
public void SetNodePercentages(float nodeOne)
{
nodeOne *= 100.0f;
MixerNodeOneInput.Text = nodeOne.ToString("0.##", CultureInfo.InvariantCulture);
MixerNodeOneInput.Text = nodeOne.ToString("0.##", CultureInfo.CurrentCulture);
float nodeTwo = 100.0f - nodeOne;
MixerNodeTwoInput.Text = nodeTwo.ToString("0.##", CultureInfo.InvariantCulture);
MixerNodeTwoInput.Text = nodeTwo.ToString("0.##", CultureInfo.CurrentCulture);
}
public void SetMixerStatus(bool enabled)

View file

@ -1,5 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@ -46,7 +47,7 @@ namespace Content.Client.Atmos.UI
private void OnPumpOutputPressurePressed(string value)
{
float pressure = float.TryParse(value, out var parsed) ? parsed : 0f;
var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (pressure > MaxPressure) pressure = MaxPressure;
SendMessage(new GasPressurePumpChangeOutputPressureMessage(pressure));

View file

@ -11,7 +11,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-output-pressure}"/>
<Control MinSize="5 0" />
<LineEdit Name="PumpPressureOutputInput" MinSize="60 0" />
<LineEdit Name="PumpPressureOutputInput" MinSize="70 0" />
<Control MinSize="5 0" />
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
<Control MinSize="5 0" />

View file

@ -39,14 +39,14 @@ namespace Content.Client.Atmos.UI
SetMaxPressureButton.OnPressed += _ =>
{
PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.InvariantCulture);
PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture);
SetOutputPressureButton.Disabled = false;
};
}
public void SetOutputPressure(float pressure)
{
PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.InvariantCulture);
PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture);
}
public void SetPumpStatus(bool enabled)

View file

@ -17,7 +17,7 @@ public sealed partial class GasThermomachineWindow : DefaultWindow
RobustXamlLoader.Load(this);
SpinboxHBox.AddChild(
TemperatureSpinbox = new FloatSpinBox(.1f, 2) { MaxWidth = 150, HorizontalExpand = true }
TemperatureSpinbox = new FloatSpinBox(.1f, 2) { MinWidth = 150, HorizontalExpand = true }
);
}

View file

@ -1,5 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@ -46,7 +47,7 @@ namespace Content.Client.Atmos.UI
private void OnPumpTransferRatePressed(string value)
{
var rate = float.TryParse(value, out var parsed) ? parsed : 0f;
var rate = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (rate > MaxTransferRate)
rate = MaxTransferRate;

View file

@ -11,7 +11,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-transfer-rate}"/>
<Control MinSize="5 0" />
<LineEdit Name="PumpTransferRateInput" MinSize="60 0" />
<LineEdit Name="PumpTransferRateInput" MinSize="70 0" />
<Control MinSize="5 0" />
<Button Name="SetMaxRateButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
<Control MinSize="5 0" />

View file

@ -39,14 +39,14 @@ namespace Content.Client.Atmos.UI
SetMaxRateButton.OnPressed += _ =>
{
PumpTransferRateInput.Text = Atmospherics.MaxTransferRate.ToString(CultureInfo.InvariantCulture);
PumpTransferRateInput.Text = Atmospherics.MaxTransferRate.ToString(CultureInfo.CurrentCulture);
SetTransferRateButton.Disabled = false;
};
}
public void SetTransferRate(float rate)
{
PumpTransferRateInput.Text = rate.ToString(CultureInfo.InvariantCulture);
PumpTransferRateInput.Text = rate.ToString(CultureInfo.CurrentCulture);
}
public void SetPumpStatus(bool enabled)

View file

@ -26,7 +26,6 @@ public sealed partial class ContentAudioSystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly RulesSystem _rules = default!;
@ -153,8 +152,7 @@ public sealed partial class ContentAudioSystem
// Update still runs in lobby so just ignore it.
if (_state.CurrentState is not GameplayState)
{
Audio.Stop(_ambientMusicStream);
_ambientMusicStream = null;
_ambientMusicStream = Audio.Stop(_ambientMusicStream);
_musicProto = null;
return;
}

View file

@ -119,7 +119,8 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
}
var volume = component.Volume - change * frameTime;
component.Volume = MathF.Max(MinVolume, volume);
volume = MathF.Max(MinVolume, volume);
_audio.SetVolume(stream, volume, component);
if (component.Volume.Equals(MinVolume))
{
@ -145,7 +146,8 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
}
var volume = component.Volume + change * frameTime;
component.Volume = MathF.Min(target, volume);
volume = MathF.Max(target, volume);
_audio.SetVolume(stream, volume, component);
if (component.Volume.Equals(target))
{

View file

@ -4,6 +4,7 @@ using Content.Shared.Weapons.Ranged.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Serialization;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
@ -26,6 +27,7 @@ public sealed class CombatModeIndicatorsOverlay : Overlay
private readonly HandsSystem _hands = default!;
private readonly Texture _gunSight;
private readonly Texture _gunBoltSight;
private readonly Texture _meleeSight;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
@ -46,6 +48,8 @@ public sealed class CombatModeIndicatorsOverlay : Overlay
var spriteSys = _entMan.EntitySysManager.GetEntitySystem<SpriteSystem>();
_gunSight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/crosshair_pointers.rsi"),
"gun_sight"));
_gunBoltSight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/crosshair_pointers.rsi"),
"gun_bolt_sight"));
_meleeSight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/crosshair_pointers.rsi"),
"melee_sight"));
}
@ -67,12 +71,16 @@ public sealed class CombatModeIndicatorsOverlay : Overlay
var handEntity = _hands.GetActiveHandEntity();
var isHandGunItem = _entMan.HasComponent<GunComponent>(handEntity);
var isGunBolted = true;
if (_entMan.TryGetComponent(handEntity, out ChamberMagazineAmmoProviderComponent? chamber))
isGunBolted = chamber.BoltClosed ?? true;
var mousePos = mouseScreenPosition.Position;
var uiScale = (args.ViewportControl as Control)?.UIScale ?? 1f;
var limitedScale = uiScale > 1.25f ? 1.25f : uiScale;
var sight = isHandGunItem ? _gunSight : _meleeSight;
var sight = isHandGunItem ? (isGunBolted ? _gunSight : _gunBoltSight) : _meleeSight;
DrawSight(sight, args.ScreenHandle, mousePos, limitedScale * Scale);
}

View file

@ -81,27 +81,30 @@ namespace Content.Client.Construction
private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
{
if (component.Prototype == null) return;
args.PushMarkup(Loc.GetString(
"construction-ghost-examine-message",
("name", component.Prototype.Name)));
if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
if (component.Prototype == null)
return;
var startNode = graph.Nodes[component.Prototype.StartNode];
if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) ||
!startNode.TryGetEdge(path[0].Name, out var edge))
using (args.PushGroup(nameof(ConstructionGhostComponent)))
{
return;
}
args.PushMarkup(Loc.GetString(
"construction-ghost-examine-message",
("name", component.Prototype.Name)));
foreach (ConstructionGraphStep step in edge.Steps)
{
args.Message.PushNewline();
step.DoExamine(args);
if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
return;
var startNode = graph.Nodes[component.Prototype.StartNode];
if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) ||
!startNode.TryGetEdge(path[0].Name, out var edge))
{
return;
}
foreach (var step in edge.Steps)
{
step.DoExamine(args);
}
}
}

View file

@ -8,20 +8,21 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical">
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" MinSize="128 128"/>
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
</BoxContainer>
<Control MinHeight="10"/>
<Button Name="PackButton" Text="{Loc 'flatpacker-ui-pack-button'}" MaxWidth="150" Margin="0 0 0 10"/>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<BoxContainer Orientation="Vertical" VerticalExpand="True" RectClipContent="True">
<Label Name="CostHeaderLabel" Text="{Loc 'flatpacker-ui-cost-label'}" HorizontalAlignment="Left"/>
<PanelContainer VerticalExpand="True"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<RichTextLabel Name="CostLabel" HorizontalAlignment="Center"/>
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
<RichTextLabel Name="CostLabel" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<RichTextLabel Name="InsertLabel" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View file

@ -27,6 +27,9 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
private readonly EntityUid _owner;
[ValidatePrototypeId<EntityPrototype>]
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
private EntityUid? _currentBoard = EntityUid.Invalid;
private EntityUid? _machinePreview;
@ -47,6 +50,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
MaterialStorageControl.SetOwner(uid);
InsertLabel.SetMarkup(Loc.GetString("flatpacker-ui-insert-board"));
}
protected override void FrameUpdate(FrameEventArgs args)
@ -63,15 +67,15 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
MachineBoardComponent? machineBoardComp = null;
if (flatpacker.Packing)
{
PackButton.Disabled = true;
}
else if (_currentBoard != null)
{
//todo double trycomp is kinda stinky.
Dictionary<string, int> cost;
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoardComp) &&
if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) &&
machineBoardComp.Prototype is not null)
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
else
@ -88,16 +92,17 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
_currentBoard = itemSlot.Item;
CostHeaderLabel.Visible = _currentBoard != null;
InsertLabel.Visible = _currentBoard == null;
if (_currentBoard is not null)
{
string? prototype = null;
Dictionary<string, int>? cost = null;
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoard))
if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
{
prototype = machineBoard.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoard));
prototype = machineBoardComp.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
}
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
{
@ -116,22 +121,23 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
}
else
{
_machinePreview = null;
MachineNameLabel.SetMessage(" ");
_machinePreview = _entityManager.Spawn(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(" ");
PackButton.Disabled = true;
}
MachineSprite.SetEntity(_machinePreview);
}
//todo beautify
private string GetCostString(Dictionary<string, int> costs)
{
var orderedCosts = costs.OrderBy(p => p.Value);
var orderedCosts = costs.OrderBy(p => p.Value).ToArray();
var msg = new FormattedMessage();
foreach (var (mat, amount) in orderedCosts)
for (var i = 0; i < orderedCosts.Length; i++)
{
var (mat, amount) = orderedCosts[i];
var matProto = _prototypeManager.Index<MaterialPrototype>(mat);
var sheetVolume = _materialStorage.GetSheetVolume(matProto);
@ -144,9 +150,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
("material", Loc.GetString(matProto.Name)));
msg.AddMarkup(text);
msg.PushNewline();
if (i != orderedCosts.Length - 1)
msg.PushNewline();
}
msg.Pop();
return msg.ToMarkup();
}

View file

@ -11,6 +11,7 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
/// <summary>
/// It's a little on the long side but given we use multiple colours denoting what happened it makes it easier to register.
@ -86,8 +87,13 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
continue;
}
var player = EnsureComp<AnimationPlayerComponent>(ent);
player.NetSyncEnabled = false;
if (!TryComp(ent, out AnimationPlayerComponent? player))
{
player = (AnimationPlayerComponent) _factory.GetComponent(typeof(AnimationPlayerComponent));
player.Owner = ent;
player.NetSyncEnabled = false;
AddComp(ent, player);
}
// Need to stop the existing animation first to ensure the sprite color is fixed.
// Otherwise we might lerp to a red colour instead.
@ -111,8 +117,14 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
if (animation == null)
continue;
var comp = EnsureComp<ColorFlashEffectComponent>(ent);
comp.NetSyncEnabled = false;
if (!TryComp(ent, out ColorFlashEffectComponent? comp))
{
comp = (ColorFlashEffectComponent) _factory.GetComponent(typeof(ColorFlashEffectComponent));
comp.Owner = ent;
comp.NetSyncEnabled = false;
AddComp(ent, comp);
}
comp.Color = sprite.Color;
_animation.Play((ent, player), animation, AnimationKey);
}

View file

@ -366,13 +366,14 @@ namespace Content.Client.Examine
var canSeeClearly = !HasComp<BlurryVisionComponent>(playerEnt);
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly);
if (IsClientSide(entity)
|| _client.RunLevel == ClientRunLevel.SinglePlayerGame) // i.e. a replay
{
message = GetExamineText(entity, playerEnt);
UpdateTooltipInfo(playerEnt.Value, entity, message);
}
else
// Always update tooltip info from client first.
// If we get it wrong, server will correct us later anyway.
// This will usually be correct (barring server-only components, which generally only adds, not replaces text)
message = GetExamineText(entity, playerEnt);
UpdateTooltipInfo(playerEnt.Value, entity, message);
if (!IsClientSide(entity))
{
// Ask server for extra examine info.
if (entity != _lastExaminedEntity)
@ -383,7 +384,6 @@ namespace Content.Client.Examine
}
RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value));
_lastExaminedEntity = entity;
}

View file

@ -35,7 +35,6 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public string? LobbySong { get; private set; }
[ViewVariables] public string? 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; }
@ -58,6 +57,7 @@ namespace Content.Client.GameTicking.Managers
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);
SubscribeNetworkEvent<TickerLobbyStatusEvent>(LobbyStatus);
SubscribeNetworkEvent<TickerLobbyInfoEvent>(LobbyInfo);
SubscribeNetworkEvent<TickerLobbyCountdownEvent>(LobbyCountdown);
@ -68,7 +68,6 @@ namespace Content.Client.GameTicking.Managers
});
SubscribeNetworkEvent<TickerLateJoinStatusEvent>(LateJoinStatus);
SubscribeNetworkEvent<TickerJobsAvailableEvent>(UpdateJobsAvailable);
SubscribeNetworkEvent<RoundRestartCleanupEvent>(RoundRestartCleanup);
_initialized = true;
}
@ -112,6 +111,11 @@ namespace Content.Client.GameTicking.Managers
_stateManager.RequestStateChange<LobbyState>();
}
private void ConnectionStatus(TickerConnectionStatusEvent message)
{
RoundStartTimeSpan = message.RoundStartTimeSpan;
}
private void LobbyStatus(TickerLobbyStatusEvent message)
{
StartTime = message.StartTime;
@ -147,7 +151,6 @@ namespace Content.Client.GameTicking.Managers
{
// Force an update in the event of this song being the same as the last.
SetLobbySong(message.LobbySong, true);
RestartSound = message.RestartSound;
// Don't open duplicate windows (mainly for replays).
if (_window?.RoundId == message.RoundId)
@ -156,22 +159,5 @@ namespace Content.Client.GameTicking.Managers
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
_window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager);
}
private void RoundRestartCleanup(RoundRestartCleanupEvent ev)
{
if (string.IsNullOrEmpty(RestartSound))
return;
if (!_configManager.GetCVar(CCVars.RestartSoundsEnabled))
{
RestartSound = null;
return;
}
_audio.PlayGlobal(RestartSound, Filter.Local(), false);
// Cleanup the sound, we only want it to play when the round restarts after it ends normally.
RestartSound = null;
}
}
}

View file

@ -1,9 +1,12 @@
using System;
using Robust.Client;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Client.Launcher
{
@ -13,6 +16,9 @@ namespace Content.Client.Launcher
[Dependency] private readonly IClientNetManager _clientNetManager = default!;
[Dependency] private readonly IGameController _gameController = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private LauncherConnectingGui? _control;
@ -51,7 +57,7 @@ namespace Content.Client.Launcher
protected override void Startup()
{
_control = new LauncherConnectingGui(this);
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg);
_userInterfaceManager.StateRoot.AddChild(_control);

View file

@ -54,4 +54,17 @@
</BoxContainer>
</BoxContainer>
</Control>
<!-- Bottom window for tips -->
<PanelContainer Name="LoginTips" StyleClasses="AngleRect" Margin="0 10" MaxWidth="600" VerticalExpand="True" VerticalAlignment="Bottom">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<controls:StripeBack>
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Label Name="LoginTipTitle" Text="Tip" StyleClasses="LabelHeading" Align="Center"/>
</BoxContainer>
</controls:StripeBack>
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" >
<RichTextLabel Name="LoginTip" VerticalExpand="True" />
</BoxContainer>
</BoxContainer>
</PanelContainer>
</Control>

View file

@ -1,12 +1,18 @@
using System.Linq;
using Content.Client.Stylesheets;
using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.Localization;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Client.Launcher
{
@ -15,11 +21,19 @@ namespace Content.Client.Launcher
{
private const float RedialWaitTimeSeconds = 15f;
private readonly LauncherConnecting _state;
private readonly IRobustRandom _random;
private readonly IPrototypeManager _prototype;
private readonly IConfigurationManager _cfg;
private float _redialWaitTime = RedialWaitTimeSeconds;
public LauncherConnectingGui(LauncherConnecting state)
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
IPrototypeManager prototype, IConfigurationManager config)
{
_state = state;
_random = random;
_prototype = prototype;
_cfg = config;
RobustXamlLoader.Load(this);
@ -27,6 +41,7 @@ namespace Content.Client.Launcher
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
ChangeLoginTip();
ReconnectButton.OnPressed += _ => _state.RetryConnect();
// Redial shouldn't fail, but if it does, try a reconnect (maybe we're being run from debug)
RedialButton.OnPressed += _ =>
@ -67,6 +82,29 @@ namespace Content.Client.Launcher
ReconnectButton.Visible = !redialFlag;
}
private void ChangeLoginTip()
{
var tipsDataset = _cfg.GetCVar(CCVars.LoginTipsDataset);
var loginTipsEnabled = _prototype.TryIndex<DatasetPrototype>(tipsDataset, out var tips);
LoginTips.Visible = loginTipsEnabled;
if (!loginTipsEnabled)
{
return;
}
var tipList = tips!.Values;
if (tipList.Count == 0)
return;
var randomIndex = _random.Next(tipList.Count);
var tip = tipList[randomIndex];
LoginTip.SetMessage(tip);
LoginTipTitle.Text = Loc.GetString("connecting-window-tip", ("numberTip", randomIndex));
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);

View file

@ -148,7 +148,8 @@ namespace Content.Client.Lobby.UI
foreach (var slot in slots)
{
var itemType = gear.GetGear(slot.Name, profile);
if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, true, true))
if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
entMan.DeleteEntity(unequippedItem.Value);
}

View file

@ -30,6 +30,7 @@ public sealed class MagicMirrorBoundUserInterface : BoundUserInterface
_window.OnFacialHairSlotAdded += delegate () { AddSlot(MagicMirrorCategory.FacialHair); };
_window.OnFacialHairSlotRemoved += args => RemoveSlot(MagicMirrorCategory.FacialHair, args);
_window.OnClose += Close;
_window.OpenCentered();
}
@ -53,17 +54,18 @@ public sealed class MagicMirrorBoundUserInterface : BoundUserInterface
SendMessage(new MagicMirrorAddSlotMessage(category));
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
protected override void UpdateState(BoundUserInterfaceState state)
{
base.ReceiveMessage(message);
base.UpdateState(state);
if (message is not MagicMirrorUiData data || _window == null)
if (state is not MagicMirrorUiState data || _window == null)
{
return;
}
_window.UpdateState(data);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

View file

@ -21,8 +21,6 @@ public sealed partial class MagicMirrorWindow : DefaultWindow
public Action<int>? OnFacialHairSlotRemoved;
public Action? OnFacialHairSlotAdded;
private bool _noHair;
public MagicMirrorWindow()
{
RobustXamlLoader.Load(this);
@ -38,15 +36,14 @@ public sealed partial class MagicMirrorWindow : DefaultWindow
FacialHairPicker.OnSlotAdd += delegate { OnFacialHairSlotAdded!(); };
}
public void UpdateState(MagicMirrorUiData state)
public void UpdateState(MagicMirrorUiState state)
{
HairPicker.UpdateData(state.Hair, state.Species, state.HairSlotTotal);
FacialHairPicker.UpdateData(state.FacialHair, state.Species, state.FacialHairSlotTotal);
if (!HairPicker.Visible && !FacialHairPicker.Visible && !_noHair)
if (!HairPicker.Visible && !FacialHairPicker.Visible)
{
AddChild(new Label { Text = Loc.GetString("magic-mirror-component-activate-user-has-no-hair") });
_noHair = true;
}
}
}

View file

@ -28,20 +28,24 @@
HorizontalExpand="True"
MinSize="50 50"
Margin="8">
<RichTextLabel Name="PdaOwnerLabel" Access="Public" />
<RichTextLabel Name="IdInfoLabel"
Access="Public"
HorizontalExpand="True" />
<PanelContainer>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StationNameLabel"
Access="Public"
HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>
<RichTextLabel Name="StationAlertLevelLabel" Access="Public" />
<RichTextLabel Name="StationTimeLabel" Access="Public" />
<RichTextLabel Name="StationAlertLevelInstructions" Access="Public" />
<ContainerButton Name="PdaOwnerButton">
<RichTextLabel Name="PdaOwnerLabel" Access="Public"/>
</ContainerButton>
<ContainerButton Name="IdInfoButton">
<RichTextLabel Name="IdInfoLabel" Access="Public"/>
</ContainerButton>
<ContainerButton Name="StationNameButton">
<RichTextLabel Name="StationNameLabel" Access="Public"/>
</ContainerButton>
<ContainerButton Name="StationAlertLevelButton">
<RichTextLabel Name="StationAlertLevelLabel" Access="Public"/>
</ContainerButton>
<ContainerButton Name="StationTimeButton">
<RichTextLabel Name="StationTimeLabel" Access="Public"/>
</ContainerButton>
<ContainerButton Name="StationAlertLevelInstructionsButton">
<RichTextLabel Name="StationAlertLevelInstructions" Access="Public"/>
</ContainerButton>
</BoxContainer>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
<BoxContainer Orientation="Vertical"

View file

@ -15,6 +15,7 @@ namespace Content.Client.PDA
[GenerateTypedNameReferences]
public sealed partial class PdaMenu : PdaWindow
{
[Dependency] private readonly IClipboardManager _clipboard = null!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly ClientGameTicker _gameTicker;
@ -24,6 +25,15 @@ namespace Content.Client.PDA
public const int SettingsView = 2;
public const int ProgramContentView = 3;
private string _pdaOwner = Loc.GetString("comp-pda-ui-unknown");
private string _owner = Loc.GetString("comp-pda-ui-unknown");
private string _jobTitle = Loc.GetString("comp-pda-ui-unassigned");
private string _stationName = Loc.GetString("comp-pda-ui-unknown");
private string _alertLevel = Loc.GetString("comp-pda-ui-unknown");
private string _instructions = Loc.GetString("comp-pda-ui-unknown");
private int _currentView;
public event Action<EntityUid>? OnProgramItemPressed;
@ -41,7 +51,7 @@ namespace Content.Client.PDA
FlashLightToggleButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/light.png"));
EjectPenButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/pencil.png"));
EjectIdButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/eject.png"));
EjectPaiButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/PAI.png"));
EjectPaiButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/pai.png"));
ProgramCloseButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/Nano/cross.svg.png"));
@ -84,6 +94,39 @@ namespace Content.Client.PDA
ToHomeScreen();
};
PdaOwnerButton.OnPressed += _ =>
{
_clipboard.SetText(_pdaOwner);
};
IdInfoButton.OnPressed += _ =>
{
_clipboard.SetText(_owner + ", " + _jobTitle);
};
StationNameButton.OnPressed += _ =>
{
_clipboard.SetText(_stationName);
};
StationAlertLevelButton.OnPressed += _ =>
{
_clipboard.SetText(_alertLevel);
};
StationTimeButton.OnPressed += _ =>
{
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
_clipboard.SetText((stationTime.ToString("hh\\:mm\\:ss")));
};
StationAlertLevelInstructionsButton.OnPressed += _ =>
{
_clipboard.SetText(_instructions);
};
HideAllViews();
ToHomeScreen();
@ -95,24 +138,29 @@ namespace Content.Client.PDA
if (state.PdaOwnerInfo.ActualOwnerName != null)
{
_pdaOwner = state.PdaOwnerInfo.ActualOwnerName;
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
("actualOwnerName", state.PdaOwnerInfo.ActualOwnerName)));
("actualOwnerName", _pdaOwner)));
}
if (state.PdaOwnerInfo.IdOwner != null || state.PdaOwnerInfo.JobTitle != null)
{
_owner = state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown");
_jobTitle = state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned");
IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
("owner", state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")),
("jobTitle", state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned"))));
("owner", _owner),
("jobTitle", _jobTitle)));
}
else
{
IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui-blank"));
}
_stationName = state.StationName ?? Loc.GetString("comp-pda-ui-unknown");
StationNameLabel.SetMarkup(Loc.GetString("comp-pda-ui-station",
("station", state.StationName ?? Loc.GetString("comp-pda-ui-unknown"))));
("station", _stationName)));
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
@ -122,16 +170,17 @@ namespace Content.Client.PDA
var alertLevel = state.PdaOwnerInfo.StationAlertLevel;
var alertColor = state.PdaOwnerInfo.StationAlertColor;
var alertLevelKey = alertLevel != null ? $"alert-level-{alertLevel}" : "alert-level-unknown";
_alertLevel = Loc.GetString(alertLevelKey);
StationAlertLevelLabel.SetMarkup(Loc.GetString(
"comp-pda-ui-station-alert-level",
("color", alertColor),
("level", Loc.GetString(alertLevelKey))
("level", _alertLevel)
));
_instructions = Loc.GetString($"{alertLevelKey}-instructions");
StationAlertLevelInstructions.SetMarkup(Loc.GetString(
"comp-pda-ui-station-alert-level-instructions",
("instructions", Loc.GetString($"{alertLevelKey}-instructions")))
("instructions", _instructions))
);
AddressLabel.Text = state.Address?.ToUpper() ?? " - ";

View file

@ -16,7 +16,6 @@ public sealed class NavMapBeaconBoundUserInterface : BoundUserInterface
protected override void Open()
{
base.Open();
_window = new NavMapBeaconWindow(Owner);
_window.OpenCentered();
_window.OnClose += Close;

View file

@ -0,0 +1,35 @@
using Robust.Client.GameObjects;
namespace Content.Client.Pinpointer.UI;
public sealed class UntrackedStationMapBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private StationMapWindow? _window;
public UntrackedStationMapBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window?.Close();
EntityUid? gridUid = null;
if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
{
gridUid = xform.GridUid;
}
_window = new StationMapWindow(gridUid, null);
_window.OpenCentered();
_window.OnClose += Close;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_window?.Dispose();
}
}

View file

@ -1,6 +1,5 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'salvage-expedition-window-title'}"
MinSize="800 360">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Name="ProgressionBox" Visible="False">

View file

@ -31,6 +31,7 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf
{
base.Open();
_window = new OfferingWindow();
_window.Title = Loc.GetString("salvage-expedition-window-title");
_window.OnClose += Close;
_window?.OpenCenteredLeft();
}

View file

@ -21,6 +21,7 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
{
base.Open();
_window = new OfferingWindow();
_window.Title = Loc.GetString("salvage-magnet-window-title");
_window.OnClose += Close;
_window.OpenCenteredLeft();
}

View file

@ -267,16 +267,14 @@ public sealed class StorageContainer : BaseWindow
var currentPosition = new Vector2i(x, y);
foreach (var item in storageComp.StoredItems)
foreach (var (itemEnt, itemPos) in storageComp.StoredItems)
{
if (item.Value.Position != currentPosition)
if (itemPos.Position != currentPosition)
continue;
var itemEnt = _entity.GetEntity(item.Key);
if (_entity.TryGetComponent<ItemComponent>(itemEnt, out var itemEntComponent))
{
var gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), item.Value, _entity)
var gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), itemPos, _entity)
{
MinSize = size,
Marked = itemEnt == lastEntity

View file

@ -69,7 +69,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
if (useDown != BoundKeyState.Down && altDown != BoundKeyState.Down)
if (weapon.AutoAttack || useDown != BoundKeyState.Down && altDown != BoundKeyState.Down)
{
if (weapon.Attacking)
{

View file

@ -31,6 +31,7 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly AnimationPlayerSystem _animPlayer = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[ValidatePrototypeId<EntityPrototype>]
public const string HitscanProto = "HitscanEffect";
@ -309,8 +310,14 @@ public sealed partial class GunSystem : SharedGunSystem
};
_animPlayer.Play(ent, anim, "muzzle-flash");
var light = EnsureComp<PointLightComponent>(uid);
light.NetSyncEnabled = false;
if (!TryComp(uid, out PointLightComponent? light))
{
light = (PointLightComponent) _factory.GetComponent(typeof(PointLightComponent));
light.Owner = uid;
light.NetSyncEnabled = false;
AddComp(uid, light);
}
Lights.SetEnabled(uid, true, light);
Lights.SetRadius(uid, 2f, light);
Lights.SetColor(uid, Color.FromHex("#cc8e2b"), light);

View file

@ -51,7 +51,7 @@ public sealed class ComputerConstruction : InteractionTest
Screw,
Pry,
Wrench,
Weld);
Screw);
// construction finished, entity no longer exists.
AssertDeleted();

View file

@ -56,34 +56,5 @@ public sealed class MachineConstruction : InteractionTest
await Interact(Bin1, Bin1, Bin1, Manipulator1, Glass, Screw);
AssertPrototype("Autolathe");
}
[Test]
public async Task UpgradeLathe()
{
// Partially deconstruct a protolathe.
await SpawnTarget(Protolathe);
var serverTarget = SEntMan.GetEntity(Target!.Value);
// Initially has all quality-1 parts.
foreach (var part in SConstruction.GetAllParts(serverTarget))
{
Assert.That(part.Rating, Is.EqualTo(1));
}
// Partially deconstruct lathe
await Interact(Screw, Pry, Pry);
AssertPrototype(MachineFrame);
// Reconstruct with better parts.
await Interact(ProtolatheBoard, Bin4, Bin4, Manipulator4, Manipulator4, Beaker, Beaker);
await Interact(Screw);
AssertPrototype(Protolathe);
// Query now returns higher quality parts.
foreach (var part in SConstruction.GetAllParts(SEntMan.GetEntity(Target!.Value)))
{
Assert.That(part.Rating, Is.EqualTo(4));
}
}
}

View file

@ -1,4 +1,3 @@
namespace Content.IntegrationTests.Tests.Interaction;
// This partial class contains various constant prototype IDs common to interaction tests.
@ -28,12 +27,8 @@ public abstract partial class InteractionTest
// Parts
protected const string Bin1 = "MatterBinStockPart";
protected const string Bin4 = "BluespaceMatterBinStockPart";
protected const string Cap1 = "CapacitorStockPart";
protected const string Cap4 = "QuadraticCapacitorStockPart";
protected const string Manipulator1 = "MicroManipulatorStockPart";
protected const string Manipulator4 = "FemtoManipulatorStockPart";
protected const string Battery1 = "PowerCellSmall";
protected const string Battery4 = "PowerCellHyper";
}

View file

@ -64,7 +64,6 @@ namespace Content.IntegrationTests.Tests
"CentComm",
"Box",
"Europa",
"Barratry",
"Saltern",
"Core",
"Marathon",

View file

@ -13,7 +13,7 @@ using Robust.Shared.Network;
namespace Content.Server.Administration;
public sealed class BanPanelEui : BaseEui, IPostInjectInit
public sealed class BanPanelEui : BaseEui
{
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly IEntityManager _entities = default!;
@ -23,7 +23,7 @@ public sealed class BanPanelEui : BaseEui, IPostInjectInit
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IAdminManager _admins = default!;
private ISawmill _sawmill = default!;
private readonly ISawmill _sawmill;
private NetUserId? PlayerId { get; set; }
private string PlayerName { get; set; } = string.Empty;
@ -35,6 +35,8 @@ public sealed class BanPanelEui : BaseEui, IPostInjectInit
public BanPanelEui()
{
IoCManager.InjectDependencies(this);
_sawmill = _log.GetSawmill("admin.bans_eui");
}
public override EuiStateBase GetNewState()
@ -185,9 +187,4 @@ public sealed class BanPanelEui : BaseEui, IPostInjectInit
StateDirty();
}
public void PostInject()
{
_sawmill = _log.GetSawmill("admin.bans_eui");
}
}

View file

@ -74,6 +74,17 @@ namespace Content.Server.Advertise
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Advertisements)), InGameICChatType.Speak, true);
}
public void SayThankYou(EntityUid uid, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements))
{
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.ThankYous), ("name", Name(uid))), InGameICChatType.Speak, true);
}
}
public void SetEnabled(EntityUid uid, bool enable, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))

View file

@ -11,5 +11,8 @@ namespace Content.Server.Advertisements
[DataField("advertisements")]
public List<string> Advertisements { get; private set; } = new();
[DataField("thankyous")]
public List<string> ThankYous { get; private set; } = new();
}
}

View file

@ -4,7 +4,6 @@ using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.Input;
using Robust.Shared.Player;
using Robust.Shared.Timing;
@ -35,7 +34,6 @@ namespace Content.Server.Afk
[UsedImplicitly]
public sealed class AfkManager : IAfkManager
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;

View file

@ -0,0 +1,21 @@
using Robust.Shared.Prototypes;
namespace Content.Server.Antag.Mimic;
/// <summary>
/// Replaces the relevant entities with mobs when the game rule is started.
/// </summary>
[RegisterComponent]
public sealed partial class MobReplacementRuleComponent : Component
{
// If you want more components use generics, using a whitelist would probably kill the server iterating every single entity.
[DataField]
public EntProtoId Proto = "MobMimic";
/// <summary>
/// Chance per-entity.
/// </summary>
[DataField]
public float Chance = 0.001f;
}

View file

@ -0,0 +1,37 @@
using Content.Server.Antag.Mimic;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Shared.VendingMachines;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Server.Antag;
public sealed class MobReplacementRuleSystem : GameRuleSystem<MobReplacementRuleComponent>
{
[Dependency] private readonly IRobustRandom _random = default!;
protected override void Started(EntityUid uid, MobReplacementRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = AllEntityQuery<VendingMachineComponent, TransformComponent>();
var spawns = new List<(EntityUid Entity, EntityCoordinates Coordinates)>();
while (query.MoveNext(out var vendingUid, out _, out var xform))
{
if (!_random.Prob(component.Chance))
continue;
spawns.Add((vendingUid, xform.Coordinates));
}
foreach (var entity in spawns)
{
var coordinates = entity.Coordinates;
Del(entity.Entity);
Spawn(component.Proto, coordinates);
}
}
}

View file

@ -13,4 +13,8 @@ public sealed partial class IgniteOnCollideComponent : Component
[ViewVariables(VVAccess.ReadWrite), DataField("fireStacks")]
public float FireStacks;
[ViewVariables(VVAccess.ReadWrite), DataField("fixtureId")]
public string FixtureId = "ignition";
}

View file

@ -13,6 +13,7 @@ using Content.Shared.Database;
using Content.Shared.Interaction;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Projectiles;
using Content.Shared.Rejuvenate;
using Content.Shared.Temperature;
using Content.Shared.Throwing;
@ -102,6 +103,12 @@ namespace Content.Server.Atmos.EntitySystems
if (!EntityManager.TryGetComponent(otherEnt, out FlammableComponent? flammable))
return;
//Only ignite when the colliding fixture is projectile or ignition.
if (args.OurFixtureId != component.FixtureId && args.OurFixtureId != SharedProjectileSystem.ProjectileFixture)
{
return;
}
flammable.FireStacks += component.FireStacks;
Ignite(otherEnt, uid, flammable);
component.Count--;

View file

@ -110,6 +110,7 @@ namespace Content.Server.Atmos.EntitySystems
private void OnExamined(EntityUid uid, GasTankComponent component, ExaminedEvent args)
{
using(args.PushGroup(nameof(GasTankComponent)));
if (args.IsInDetailsRange)
args.PushMarkup(Loc.GetString("comp-gas-tank-examine", ("pressure", Math.Round(component.Air?.Pressure ?? 0))));
if (component.IsConnected)

View file

@ -51,20 +51,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
return;
}
if (comp.Reacting)
using (args.PushGroup(nameof(GasRecyclerComponent)))
{
args.PushMarkup(Loc.GetString("gas-recycler-reacting"));
}
else
{
if (inlet.Air.Pressure < comp.MinPressure)
if (comp.Reacting)
{
args.PushMarkup(Loc.GetString("gas-recycler-low-pressure"));
args.PushMarkup(Loc.GetString("gas-recycler-reacting"));
}
if (inlet.Air.Temperature < comp.MinTemp)
else
{
args.PushMarkup(Loc.GetString("gas-recycler-low-temperature"));
if (inlet.Air.Pressure < comp.MinPressure)
{
args.PushMarkup(Loc.GetString("gas-recycler-low-pressure"));
}
if (inlet.Air.Temperature < comp.MinTemp)
{
args.PushMarkup(Loc.GetString("gas-recycler-low-temperature"));
}
}
}
}

View file

@ -1,7 +1,4 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Construction.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Atmos.Piping.Unary.Components
{
@ -15,24 +12,16 @@ namespace Content.Server.Atmos.Piping.Unary.Components
/// Current electrical power consumption, in watts. Increasing power increases the ability of the
/// thermomachine to heat or cool air.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float HeatCapacity = 10000;
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float HeatCapacity = 5000;
/// <summary>
/// Base heat capacity of the device. Actual heat capacity is calculated by taking this number and doubling
/// it for every matter bin quality tier above one.
/// </summary>
[DataField("baseHeatCapacity")]
public float BaseHeatCapacity = 5000;
[DataField("targetTemperature")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float TargetTemperature = Atmospherics.T20C;
/// <summary>
/// Tolerance for temperature setpoint hysteresis.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField, ViewVariables(VVAccess.ReadOnly)]
public float TemperatureTolerance = 2f;
/// <summary>
@ -40,7 +29,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
/// If true, add Sign(Cp)*TemperatureTolerance to the temperature setpoint.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public bool HysteresisState = false;
public bool HysteresisState;
/// <summary>
/// Coefficient of performance. Output power / input power.
@ -51,68 +40,29 @@ namespace Content.Server.Atmos.Piping.Unary.Components
public float Cp = 0.9f; // output power / input power, positive is heat
/// <summary>
/// Current minimum temperature, calculated from <see cref="InitialMinTemperature"/> and <see
/// cref="MinTemperatureDelta"/>.
/// Current minimum temperature
/// Ignored if heater.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float MinTemperature;
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float MinTemperature = 73.15f;
/// <summary>
/// Current maximum temperature, calculated from <see cref="InitialMaxTemperature"/> and <see
/// cref="MaxTemperatureDelta"/>.
/// Current maximum temperature
/// Ignored if freezer.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float MaxTemperature;
/// <summary>
/// Minimum temperature the device can reach with a 0 total capacitor quality. Usually the quality will be at
/// least 1.
/// </summary>
[DataField("baseMinTemperature")]
[ViewVariables(VVAccess.ReadWrite)]
public float BaseMinTemperature = 96.625f; // Selected so that tier-1 parts can reach 73.15k
/// <summary>
/// Maximum temperature the device can reach with a 0 total capacitor quality. Usually the quality will be at
/// least 1.
/// </summary>
[DataField("baseMaxTemperature")]
[ViewVariables(VVAccess.ReadWrite)]
public float BaseMaxTemperature = Atmospherics.T20C;
/// <summary>
/// Decrease in minimum temperature, per unit machine part quality.
/// </summary>
[DataField("minTemperatureDelta")]
[ViewVariables(VVAccess.ReadWrite)]
public float MinTemperatureDelta = 23.475f; // selected so that tier-4 parts can reach TCMB
/// <summary>
/// Change in maximum temperature, per unit machine part quality.
/// </summary>
[DataField("maxTemperatureDelta")]
[ViewVariables(VVAccess.ReadWrite)]
public float MaxTemperatureDelta = 300;
/// <summary>
/// The machine part that affects the heat capacity.
/// </summary>
[DataField("machinePartHeatCapacity", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartHeatCapacity = "MatterBin";
/// <summary>
/// The machine part that affects the temperature range.
/// </summary>
[DataField("machinePartTemperature", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartTemperature = "Capacitor";
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float MaxTemperature = 593.15f;
/// <summary>
/// Last amount of energy added/removed from the attached pipe network
/// </summary>
[DataField("lastEnergyDelta")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float LastEnergyDelta;
/// <summary>
/// An percentage of the energy change that is leaked into the surrounding environment rather than the inlet pipe.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float EnergyLeakPercentage;
}
}

View file

@ -2,7 +2,6 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.Construction;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
@ -15,6 +14,7 @@ using Content.Shared.Atmos.Piping.Unary.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Content.Server.Power.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.Examine;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
@ -28,17 +28,15 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasThermoMachineComponent, AtmosDeviceUpdateEvent>(OnThermoMachineUpdated);
SubscribeLocalEvent<GasThermoMachineComponent, RefreshPartsEvent>(OnGasThermoRefreshParts);
SubscribeLocalEvent<GasThermoMachineComponent, UpgradeExamineEvent>(OnGasThermoUpgradeExamine);
SubscribeLocalEvent<GasThermoMachineComponent, ExaminedEvent>(OnExamined);
// UI events
SubscribeLocalEvent<GasThermoMachineComponent, BeforeActivatableUIOpenEvent>(OnBeforeOpened);
SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineToggleMessage>(OnToggleMessage);
SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineChangeTemperatureMessage>(OnChangeTemperature);
@ -46,6 +44,11 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
SubscribeLocalEvent<GasThermoMachineComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
}
private void OnBeforeOpened(Entity<GasThermoMachineComponent> ent, ref BeforeActivatableUIOpenEvent args)
{
DirtyUI(ent, ent.Comp);
}
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, ref AtmosDeviceUpdateEvent args)
{
if (!(_power.IsPowered(uid) && TryComp<ApcPowerReceiverComponent>(uid, out var receiver))
@ -90,7 +93,13 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
thermoMachine.HysteresisState = false; // turn off
}
float dQActual = dQ * scale;
_atmosphereSystem.AddHeat(inlet.Air, dQActual);
float dQLeak = dQActual * thermoMachine.EnergyLeakPercentage;
float dQPipe = dQActual - dQLeak;
_atmosphereSystem.AddHeat(inlet.Air, dQPipe);
if (_atmosphereSystem.GetContainingMixture(uid) is { } containingMixture)
_atmosphereSystem.AddHeat(containingMixture, dQLeak);
receiver.Load = thermoMachine.HeatCapacity;// * scale; // we're not ready for dynamic load yet, see note above
}
@ -99,41 +108,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
return comp.Cp >= 0;
}
private void OnGasThermoRefreshParts(EntityUid uid, GasThermoMachineComponent thermoMachine, RefreshPartsEvent args)
{
var heatCapacityPartRating = args.PartRatings[thermoMachine.MachinePartHeatCapacity];
thermoMachine.HeatCapacity = thermoMachine.BaseHeatCapacity * MathF.Pow(heatCapacityPartRating, 2);
var temperatureRangePartRating = args.PartRatings[thermoMachine.MachinePartTemperature];
if (IsHeater(thermoMachine))
{
// 593.15K with stock parts.
thermoMachine.MaxTemperature = thermoMachine.BaseMaxTemperature + thermoMachine.MaxTemperatureDelta * temperatureRangePartRating;
thermoMachine.MinTemperature = Atmospherics.T20C;
}
else {
// 73.15K with stock parts.
thermoMachine.MinTemperature = MathF.Max(
thermoMachine.BaseMinTemperature - thermoMachine.MinTemperatureDelta * temperatureRangePartRating, Atmospherics.TCMB);
thermoMachine.MaxTemperature = Atmospherics.T20C;
}
DirtyUI(uid, thermoMachine);
}
private void OnGasThermoUpgradeExamine(EntityUid uid, GasThermoMachineComponent thermoMachine, UpgradeExamineEvent args)
{
if (IsHeater(thermoMachine))
{
args.AddPercentageUpgrade("gas-thermo-component-upgrade-heating", thermoMachine.MaxTemperature / (thermoMachine.BaseMaxTemperature + thermoMachine.MaxTemperatureDelta));
}
else
{
args.AddPercentageUpgrade("gas-thermo-component-upgrade-cooling", thermoMachine.MinTemperature / (thermoMachine.BaseMinTemperature - thermoMachine.MinTemperatureDelta));
}
args.AddPercentageUpgrade("gas-thermo-component-upgrade-heat-capacity", thermoMachine.HeatCapacity / thermoMachine.BaseHeatCapacity);
}
private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args)
{
_power.TogglePower(uid);

View file

@ -101,7 +101,7 @@ public sealed class RottingSystem : EntitySystem
var ev = new IsRottingEvent();
RaiseLocalEvent(uid, ref ev);
return ev.Handled;
return !ev.Handled;
}
public bool IsRotten(EntityUid uid, RottingComponent? rotting = null)
@ -154,7 +154,7 @@ public sealed class RottingSystem : EntitySystem
{
if (args.Handled)
return;
args.Handled = component.CurrentTemperature > Atmospherics.T0C + 0.85f;
args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f;
}
public override void Update(float frameTime)

View file

@ -37,7 +37,6 @@ public sealed class BodySystem : SharedBodySystem
SubscribeLocalEvent<BodyComponent, MoveInputEvent>(OnRelayMoveInput);
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
SubscribeLocalEvent<BodyComponent, BeingMicrowavedEvent>(OnBeingMicrowaved);
}
private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args)
@ -65,19 +64,6 @@ public sealed class BodySystem : SharedBodySystem
}
}
private void OnBeingMicrowaved(EntityUid uid, BodyComponent component, BeingMicrowavedEvent args)
{
if (args.Handled)
return;
// Don't microwave animals, kids
SharedTransform.AttachToGridOrMap(uid);
_appearance.SetData(args.Microwave, MicrowaveVisualState.Bloody, true);
GibBody(uid, false, component);
args.Handled = true;
}
protected override void AddPart(
EntityUid bodyUid,
EntityUid partUid,

View file

@ -171,7 +171,7 @@ namespace Content.Server.Body.Systems
// still remove reagents
if (EntityManager.TryGetComponent<MobStateComponent>(solutionEntityUid.Value, out var state))
{
if (_mobStateSystem.IsDead(solutionEntityUid.Value, state))
if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
continue;
}

View file

@ -90,10 +90,13 @@ public sealed partial class BotanySystem : EntitySystem
if (!TryGetSeed(component, out var seed))
return;
var name = Loc.GetString(seed.DisplayName);
args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name)));
args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", seed.Yield)));
args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", seed.Potency)));
using (args.PushGroup(nameof(SeedComponent)))
{
var name = Loc.GetString(seed.DisplayName);
args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name)));
args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", seed.Yield)));
args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", seed.Potency)));
}
}
#region SeedPrototype prototype stuff

View file

@ -77,59 +77,62 @@ public sealed class PlantHolderSystem : EntitySystem
var (_, component) = entity;
if (component.Seed == null)
using (args.PushGroup(nameof(PlantHolderComponent)))
{
args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message"));
}
else if (!component.Dead)
{
var displayName = Loc.GetString(component.Seed.DisplayName);
args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message",
if (component.Seed == null)
{
args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message"));
}
else if (!component.Dead)
{
var displayName = Loc.GetString(component.Seed.DisplayName);
args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message",
("seedName", displayName),
("toBeForm", displayName.EndsWith('s') ? "are" : "is")));
if (component.Health <= component.Seed.Endurance / 2)
if (component.Health <= component.Seed.Endurance / 2)
{
args.PushMarkup(Loc.GetString(
"plant-holder-component-something-already-growing-low-health-message",
("healthState",
Loc.GetString(component.Age > component.Seed.Lifespan
? "plant-holder-component-plant-old-adjective"
: "plant-holder-component-plant-unhealthy-adjective"))));
}
}
else
{
args.PushMarkup(Loc.GetString(
"plant-holder-component-something-already-growing-low-health-message",
("healthState",
Loc.GetString(component.Age > component.Seed.Lifespan
? "plant-holder-component-plant-old-adjective"
: "plant-holder-component-plant-unhealthy-adjective"))));
args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message"));
}
}
else
{
args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message"));
}
if (component.WeedLevel >= 5)
args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message"));
if (component.WeedLevel >= 5)
args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message"));
if (component.PestLevel >= 5)
args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message"));
if (component.PestLevel >= 5)
args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message"));
args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message",
("waterLevel", (int) component.WaterLevel)));
args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message",
("nutritionLevel", (int) component.NutritionLevel)));
args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message",
("waterLevel", (int) component.WaterLevel)));
args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message",
("nutritionLevel", (int) component.NutritionLevel)));
if (component.DrawWarnings)
{
if (component.Toxins > 40f)
args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning"));
if (component.DrawWarnings)
{
if (component.Toxins > 40f)
args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning"));
if (component.ImproperLight)
args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning"));
if (component.ImproperLight)
args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning"));
if (component.ImproperHeat)
args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning"));
if (component.ImproperHeat)
args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning"));
if (component.ImproperPressure)
args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning"));
if (component.ImproperPressure)
args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning"));
if (component.MissingGas > 0)
args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning"));
if (component.MissingGas > 0)
args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning"));
}
}
}

View file

@ -0,0 +1,29 @@
using Content.Server.Power.Components;
using Content.Shared.Atmos.Rotting;
using Content.Shared.Buckle.Components;
namespace Content.Server.Buckle.Systems;
public sealed class AntiRotOnBuckleSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BuckleComponent, IsRottingEvent>(OnIsRotting);
SubscribeLocalEvent<AntiRotOnBuckleComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnIsRotting(EntityUid uid, BuckleComponent buckle, ref IsRottingEvent args)
{
if (args.Handled)
return;
args.Handled = buckle is { Buckled: true, BuckledTo: not null } &&
TryComp<AntiRotOnBuckleComponent>(buckle.BuckledTo.Value, out var antiRot) &&
antiRot.Enabled;
}
private void OnPowerChanged(EntityUid uid, AntiRotOnBuckleComponent component, ref PowerChangedEvent args)
{
component.Enabled = !component.RequiresPower || args.Powered;
}
}

View file

@ -15,6 +15,7 @@ public sealed class PriceGunSystem : EntitySystem
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly PricingSystem _pricingSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly CargoSystem _bountySystem = default!;
/// <inheritdoc/>
public override void Initialize()
@ -55,9 +56,17 @@ public sealed class PriceGunSystem : EntitySystem
if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
return;
var price = _pricingSystem.GetPrice(args.Target.Value);
// Check if we're scanning a bounty crate
if (_bountySystem.IsBountyComplete(args.Target.Value, (EntityUid?) null, out _))
{
_popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), args.User, args.User);
}
else // Otherwise appraise the price
{
double price = _pricingSystem.GetPrice(args.Target.Value);
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(args.Target.Value, EntityManager)), ("price", $"{price:F2}")), args.User, args.User);
}
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(args.Target.Value, EntityManager)), ("price", $"{price:F2}")), args.User, args.User);
_useDelay.TryResetDelay((uid, useDelay));
args.Handled = true;
}

View file

@ -13,6 +13,7 @@ namespace Content.Server.Communications
/// Remaining cooldown between making announcements.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float AnnouncementCooldownRemaining;
/// <summary>
@ -37,6 +38,13 @@ namespace Content.Server.Communications
[DataField]
public int Delay = 90;
/// <summary>
/// Time in seconds of announcement cooldown when a new console is created on a per-console basis
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public int InitialDelay = 30;
/// <summary>
/// Can call or recall the shuttle
/// </summary>

View file

@ -52,6 +52,9 @@ namespace Content.Server.Communications
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleAnnounceMessage>(OnAnnounceMessage);
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleCallEmergencyShuttleMessage>(OnCallShuttleMessage);
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleRecallEmergencyShuttleMessage>(OnRecallShuttleMessage);
// On console init, set cooldown
SubscribeLocalEvent<CommunicationsConsoleComponent, MapInitEvent>(OnCommunicationsConsoleMapInit);
}
public override void Update(float frameTime)
@ -79,6 +82,11 @@ namespace Content.Server.Communications
base.Update(frameTime);
}
public void OnCommunicationsConsoleMapInit(EntityUid uid, CommunicationsConsoleComponent comp, MapInitEvent args)
{
comp.AnnouncementCooldownRemaining = comp.InitialDelay;
}
/// <summary>
/// Update the UI of every comms console.
/// </summary>

View file

@ -46,7 +46,7 @@ namespace Content.Server.Construction.Conditions
if (entityManager.EntitySysManager.GetEntitySystem<MachineFrameSystem>().IsComplete(machineFrame))
return false;
args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label") + "\n");
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label"));
foreach (var (part, required) in machineFrame.Requirements)
{
var amount = required - machineFrame.Progress[part];
@ -54,10 +54,9 @@ namespace Content.Server.Construction.Conditions
if(amount == 0)
continue;
args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
("amount", amount),
("elementName", Loc.GetString(part)))
+ "\n");
("elementName", Loc.GetString(part))));
}
foreach (var (material, required) in machineFrame.MaterialRequirements)
@ -67,10 +66,9 @@ namespace Content.Server.Construction.Conditions
if(amount == 0)
continue;
args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
("amount", amount),
("elementName", Loc.GetString(material)))
+ "\n");
("elementName", Loc.GetString(material))));
}
foreach (var (compName, info) in machineFrame.ComponentRequirements)
@ -80,10 +78,9 @@ namespace Content.Server.Construction.Conditions
if(amount == 0)
continue;
args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
("amount", info.Amount),
("elementName", Loc.GetString(info.ExamineName)))
+ "\n");
("elementName", Loc.GetString(info.ExamineName))));
}
foreach (var (tagName, info) in machineFrame.TagRequirements)
@ -93,10 +90,10 @@ namespace Content.Server.Construction.Conditions
if(amount == 0)
continue;
args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
("amount", info.Amount),
("elementName", Loc.GetString(info.ExamineName)))
+ "\n");
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
("amount", info.Amount),
("elementName", Loc.GetString(info.ExamineName)))
+ "\n");
}
return true;

View file

@ -67,46 +67,50 @@ namespace Content.Server.Construction
private void HandleConstructionExamined(EntityUid uid, ConstructionComponent component, ExaminedEvent args)
{
if (GetTargetNode(uid, component) is {} target)
using (args.PushGroup(nameof(ConstructionComponent)))
{
if (target.Name == component.DeconstructionNode)
if (GetTargetNode(uid, component) is {} target)
{
args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n");
if (target.Name == component.DeconstructionNode)
{
args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n");
}
else
{
args.PushMarkup(Loc.GetString(
"construction-component-to-create-header",
("targetName", target.Name)) + "\n");
}
}
else
if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge)
{
args.PushMarkup(Loc.GetString(
"construction-component-to-create-header",
("targetName", target.Name)) + "\n");
var preventStepExamine = false;
foreach (var condition in targetEdge.Conditions)
{
preventStepExamine |= condition.DoExamine(args);
}
if (!preventStepExamine)
targetEdge.Steps[0].DoExamine(args);
return;
}
if (GetCurrentEdge(uid, component) is {} edge)
{
var preventStepExamine = false;
foreach (var condition in edge.Conditions)
{
preventStepExamine |= condition.DoExamine(args);
}
if (!preventStepExamine && component.StepIndex < edge.Steps.Count)
edge.Steps[component.StepIndex].DoExamine(args);
}
}
if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge)
{
var preventStepExamine = false;
foreach (var condition in targetEdge.Conditions)
{
preventStepExamine |= condition.DoExamine(args);
}
if (!preventStepExamine)
targetEdge.Steps[0].DoExamine(args);
return;
}
if (GetCurrentEdge(uid, component) is {} edge)
{
var preventStepExamine = false;
foreach (var condition in edge.Conditions)
{
preventStepExamine |= condition.DoExamine(args);
}
if (!preventStepExamine && component.StepIndex < edge.Steps.Count)
edge.Steps[component.StepIndex].DoExamine(args);
}
}

View file

@ -5,7 +5,6 @@ using Content.Shared.Construction;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.Timing;
using YamlDotNet.Serialization.NodeTypeResolvers;
namespace Content.Server.Construction;
@ -37,7 +36,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
Dictionary<string, int>? cost = null;
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
if (TryComp<ComputerBoardComponent>(machineBoard, out var computerBoardComponent))
if (HasComp<ComputerBoardComponent>(machineBoard))
cost = GetFlatpackCreationCost(ent);
if (cost is null)
@ -78,7 +77,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
Dictionary<string, int>? cost = null;
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
if (TryComp<ComputerBoardComponent>(machineBoard, out var computerBoardComponent))
if (HasComp<ComputerBoardComponent>(machineBoard))
cost = GetFlatpackCreationCost(ent);
if (cost is null)
@ -89,6 +88,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
var flatpack = Spawn(comp.BaseFlatpackPrototype, Transform(ent).Coordinates);
SetupFlatpack(flatpack, machineBoard);
Del(machineBoard);
}
public override void Update(float frameTime)

View file

@ -3,6 +3,7 @@ using Robust.Shared.Prototypes;
using Content.Shared.Construction;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps;
using Robust.Server.GameObjects;
namespace Content.Server.GuideGenerator;
@ -65,7 +66,7 @@ public sealed class HeatableRecipeEntry
{
var nodeStep = (TemperatureConstructionGraphStep)nodeStepRaw;
graphID = nodeEdge.Target; // required to check when we need to leave second loop; this is the best solution, because nodeEdge.Target is marked as required datafield and cannot be null
EntityManager em = new();
ServerEntityManager em = new();
MinTemp = nodeStep.MinTemperature.HasValue ? nodeStep.MinTemperature.Value : 0;
Result = nodeStep.MinTemperature.HasValue ? constructionProto.Nodes[nodeEdge.Target].Entity.GetId(null, null, new GraphNodeEntityArgs(em)) : null;
break;

View file

@ -3,6 +3,7 @@ using Robust.Shared.Prototypes;
using Content.Shared.Construction;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps;
using Robust.Server.GameObjects;
namespace Content.Server.GuideGenerator;
@ -68,7 +69,7 @@ public sealed class ToolRecipeEntry // because of https://github.com/space-wizar
{
var nodeStep = (ToolConstructionGraphStep)nodeStepRaw;
graphID = nodeEdge.Target; // required to check when we need to leave second loop; this is the best solution, because nodeEdge.Target is marked as required datafield and cannot be null
EntityManager em = new();
ServerEntityManager em = new();
Tool = nodeStep.Tool;
Result = constructionProto.Nodes[nodeEdge.Target].Entity.GetId(null, null, new GraphNodeEntityArgs(em));
break;

View file

@ -10,7 +10,6 @@ using Content.Shared.Effects;
using Content.Shared.Mobs.Components;
using Content.Shared.Throwing;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
namespace Content.Server.Damage.Systems
@ -39,7 +38,11 @@ namespace Content.Server.Damage.Systems
if (dmg != null && HasComp<MobStateComponent>(args.Target))
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.Total:damage} damage from collision");
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
if (dmg is { Empty: false })
{
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
}
_guns.PlayImpactSound(args.Target, dmg, null, false);
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
{

View file

@ -66,26 +66,29 @@ public sealed class DefusableSystem : SharedDefusableSystem
if (!args.IsInDetailsRange)
return;
if (!comp.Usable)
using (args.PushGroup(nameof(DefusableComponent)))
{
args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
}
else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
{
if (comp.DisplayTime)
if (!comp.Usable)
{
args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
("time", MathF.Floor(activeComp.TimeRemaining))));
args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
}
else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
{
if (comp.DisplayTime)
{
args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
("time", MathF.Floor(activeComp.TimeRemaining))));
}
else
{
args.PushMarkup(Loc.GetString("defusable-examine-live-display-off", ("name", uid)));
}
}
else
{
args.PushMarkup(Loc.GetString("defusable-examine-live-display-off", ("name", uid)));
args.PushMarkup(Loc.GetString("defusable-examine-inactive", ("name", uid)));
}
}
else
{
args.PushMarkup(Loc.GetString("defusable-examine-inactive", ("name", uid)));
}
args.PushMarkup(Loc.GetString("defusable-examine-bolts", ("down", comp.Bolted)));
}

View file

@ -1,5 +1,6 @@
using Content.Shared.DeviceLinking;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.DeviceLinking.Components;
@ -7,36 +8,40 @@ namespace Content.Server.DeviceLinking.Components;
[RegisterComponent]
public sealed partial class SignalTimerComponent : Component
{
[DataField("delay"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public double Delay = 5;
/// <summary>
/// This shows the Label: text box in the UI.
/// </summary>
[DataField("canEditLabel"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool CanEditLabel = true;
/// <summary>
/// The label, used for TextScreen visuals currently.
/// </summary>
[DataField("label"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public string Label = string.Empty;
/// <summary>
/// The port that gets signaled when the timer triggers.
/// </summary>
[DataField("triggerPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string TriggerPort = "Timer";
[DataField, ViewVariables(VVAccess.ReadWrite)]
public ProtoId<SourcePortPrototype> TriggerPort = "Timer";
/// <summary>
/// The port that gets signaled when the timer starts.
/// </summary>
[DataField("startPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string StartPort = "Start";
[DataField, ViewVariables(VVAccess.ReadWrite)]
public ProtoId<SourcePortPrototype> StartPort = "Start";
[DataField, ViewVariables(VVAccess.ReadWrite)]
public ProtoId<SinkPortPrototype> Trigger = "Trigger";
/// <summary>
/// If not null, this timer will play this sound when done.
/// </summary>
[DataField("doneSound"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier? DoneSound;
}

View file

@ -1,4 +1,5 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events;
using Content.Server.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.MachineLinking;
@ -19,6 +20,11 @@ public sealed class SignalTimerSystem : EntitySystem
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
/// <summary>
/// Per-tick timer cache.
/// </summary>
private List<Entity<SignalTimerComponent>> _timers = new();
public override void Initialize()
{
base.Initialize();
@ -29,11 +35,13 @@ public sealed class SignalTimerSystem : EntitySystem
SubscribeLocalEvent<SignalTimerComponent, SignalTimerTextChangedMessage>(OnTextChangedMessage);
SubscribeLocalEvent<SignalTimerComponent, SignalTimerDelayChangedMessage>(OnDelayChangedMessage);
SubscribeLocalEvent<SignalTimerComponent, SignalTimerStartMessage>(OnTimerStartMessage);
SubscribeLocalEvent<SignalTimerComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args)
{
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
_signalSystem.EnsureSinkPorts(uid, component.Trigger);
}
private void OnAfterActivatableUIOpen(EntityUid uid, SignalTimerComponent component, AfterActivatableUIOpenEvent args)
@ -58,11 +66,13 @@ public sealed class SignalTimerSystem : EntitySystem
public void Trigger(EntityUid uid, SignalTimerComponent signalTimer)
{
RemComp<ActiveSignalTimerComponent>(uid);
if (TryComp<AppearanceComponent>(uid, out var appearance))
{
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, new string?[] { signalTimer.Label }, appearance);
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, new[] { signalTimer.Label }, appearance);
}
_audio.PlayPvs(signalTimer.DoneSound, uid);
_signalSystem.InvokePort(uid, signalTimer.TriggerPort);
if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui))
@ -80,16 +90,29 @@ public sealed class SignalTimerSystem : EntitySystem
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateTimer();
}
private void UpdateTimer()
{
_timers.Clear();
var query = EntityQueryEnumerator<ActiveSignalTimerComponent, SignalTimerComponent>();
while (query.MoveNext(out var uid, out var active, out var timer))
{
if (active.TriggerTime > _gameTiming.CurTime)
continue;
Trigger(uid, timer);
_timers.Add((uid, timer));
}
if (timer.DoneSound != null)
_audio.PlayPvs(timer.DoneSound, uid);
foreach (var timer in _timers)
{
// Exploded or the likes.
if (!Exists(timer.Owner))
continue;
Trigger(timer.Owner, timer.Comp);
}
}
@ -144,7 +167,19 @@ public sealed class SignalTimerSystem : EntitySystem
{
if (!IsMessageValid(uid, args))
return;
OnStartTimer(uid, component);
}
private void OnSignalReceived(EntityUid uid, SignalTimerComponent component, ref SignalReceivedEvent args)
{
if (args.Port == component.Trigger)
{
OnStartTimer(uid, component);
}
}
public void OnStartTimer(EntityUid uid, SignalTimerComponent component)
{
TryComp<AppearanceComponent>(uid, out var appearance);
var timer = EnsureComp<ActiveSignalTimerComponent>(uid);
timer.TriggerTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.Delay);

View file

@ -65,10 +65,7 @@ namespace Content.Server.Entry
factory.DoAutoRegistrations();
factory.IgnoreMissingComponents("Visuals");
foreach (var ignoreName in IgnoredComponents.List)
{
factory.RegisterIgnore(ignoreName);
}
factory.RegisterIgnore(IgnoredComponents.List);
prototypes.RegisterIgnore("parallax");
prototypes.RegisterIgnore("guideEntry");

View file

@ -14,12 +14,10 @@ using Content.Shared.Database;
using Content.Shared.Explosion;
using Content.Shared.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.Projectiles;
using Content.Shared.Throwing;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
@ -252,7 +250,9 @@ public sealed partial class ExplosionSystem : EntitySystem
var pos = Transform(uid);
QueueExplosion(pos.MapPosition, typeId, totalIntensity, slope, maxTileIntensity, tileBreakScale, maxTileBreak, canCreateVacuum, addLog: false);
var coordinates = _transformSystem.GetMapCoordinates(pos);
QueueExplosion(coordinates, typeId, totalIntensity, slope, maxTileIntensity, tileBreakScale, maxTileBreak, canCreateVacuum, addLog: false);
if (!addLog)
return;
@ -260,15 +260,15 @@ public sealed partial class ExplosionSystem : EntitySystem
if (user == null)
{
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(uid):entity} exploded ({typeId}) at {pos.Coordinates:coordinates} with intensity {totalIntensity} slope {slope}");
$"{ToPrettyString(uid):entity} exploded ({typeId}) at {coordinates:coordinates} with intensity {totalIntensity} slope {slope}");
}
else
{
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user.Value):user} caused {ToPrettyString(uid):entity} to explode ({typeId}) at {pos.Coordinates:coordinates} with intensity {totalIntensity} slope {slope}");
$"{ToPrettyString(user.Value):user} caused {ToPrettyString(uid):entity} to explode ({typeId}) at {coordinates:coordinates} with intensity {totalIntensity} slope {slope}");
var alertMinExplosionIntensity = _cfg.GetCVar(CCVars.AdminAlertExplosionMinIntensity);
if (alertMinExplosionIntensity > -1 && totalIntensity >= alertMinExplosionIntensity)
_chat.SendAdminAlert(user.Value, $"caused {ToPrettyString(uid)} to explode ({typeId}:{totalIntensity}) at {pos.Coordinates:coordinates}");
_chat.SendAdminAlert(user.Value, $"caused {ToPrettyString(uid)} to explode ({typeId}:{totalIntensity}) at {coordinates:coordinates}");
}
}

View file

@ -216,7 +216,7 @@ public sealed class DrainSystem : SharedDrainSystem
var text = drainSolution.AvailableVolume != 0
? Loc.GetString("drain-component-examine-volume", ("volume", drainSolution.AvailableVolume))
: Loc.GetString("drain-component-examine-hint-full");
args.Message.AddMarkup($"\n\n{text}");
args.PushMarkup(text);
}
private void OnInteract(Entity<DrainComponent> entity, ref AfterInteractUsingEvent args)

View file

@ -43,10 +43,13 @@ public sealed partial class PuddleSystem
private void OnExamined(Entity<SpillableComponent> entity, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("spill-examine-is-spillable"));
using (args.PushGroup(nameof(SpillableComponent)))
{
args.PushMarkup(Loc.GetString("spill-examine-is-spillable"));
if (HasComp<MeleeWeaponComponent>(entity))
args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon"));
if (HasComp<MeleeWeaponComponent>(entity))
args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon"));
}
}
private void OnOverflow(Entity<SpillableComponent> entity, ref SolutionContainerOverflowEvent args)

View file

@ -361,23 +361,27 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
private void HandlePuddleExamined(Entity<PuddleComponent> entity, ref ExaminedEvent args)
{
if (TryComp<StepTriggerComponent>(entity, out var slippery) && slippery.Active)
using (args.PushGroup(nameof(PuddleComponent)))
{
args.PushMarkup(Loc.GetString("puddle-component-examine-is-slipper-text"));
}
if (TryComp<StepTriggerComponent>(entity, out var slippery) && slippery.Active)
{
args.PushMarkup(Loc.GetString("puddle-component-examine-is-slipper-text"));
}
if (HasComp<EvaporationComponent>(entity) &&
_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
{
if (CanFullyEvaporate(solution))
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating"));
else if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero)
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-partial"));
if (HasComp<EvaporationComponent>(entity) &&
_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName,
ref entity.Comp.Solution, out var solution))
{
if (CanFullyEvaporate(solution))
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating"));
else if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero)
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-partial"));
else
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
}
else
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
}
else
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
}
private void OnAnchorChanged(Entity<PuddleComponent> entity, ref AnchorStateChangedEvent args)

View file

@ -18,15 +18,6 @@ namespace Content.Server.GameTicking
[ViewVariables]
public bool DisallowLateJoin { get; private set; } = false;
[ViewVariables]
public bool StationOffset { get; private set; } = false;
[ViewVariables]
public bool StationRotation { get; private set; } = false;
[ViewVariables]
public float MaxStationOffset { get; private set; } = 0f;
[ViewVariables]
public string? ServerName { get; private set; }
@ -57,9 +48,6 @@ namespace Content.Server.GameTicking
_configurationManager.OnValueChanged(CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true);
_configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins,
value => { DisallowLateJoin = value; UpdateLateJoinStatus(); }, true);
_configurationManager.OnValueChanged(CCVars.StationOffset, value => StationOffset = value, true);
_configurationManager.OnValueChanged(CCVars.StationRotation, value => StationRotation = value, true);
_configurationManager.OnValueChanged(CCVars.MaxStationOffset, value => MaxStationOffset = value, true);
_configurationManager.OnValueChanged(CCVars.AdminLogsServerName, value =>
{
// TODO why tf is the server name on admin logs

View file

@ -86,10 +86,15 @@ namespace Content.Server.GameTicking
("desc", desc));
}
private TickerConnectionStatusEvent GetConnectionStatusMsg()
{
return new TickerConnectionStatusEvent(RoundStartTimeSpan);
}
private TickerLobbyStatusEvent GetStatusMsg(ICommonSession session)
{
_playerGameStatuses.TryGetValue(session.UserId, out var status);
return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbySong, LobbyBackground,status == PlayerGameStatus.ReadyToPlay, _roundStartTime, RoundPreloadTime, RoundStartTimeSpan, Paused);
return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbySong, LobbyBackground, status == PlayerGameStatus.ReadyToPlay, _roundStartTime, RoundPreloadTime, RoundStartTimeSpan, Paused);
}
private void SendStatusToAll()

View file

@ -70,6 +70,8 @@ namespace Content.Server.GameTicking
? Loc.GetString("player-first-join-message", ("name", args.Session.Name))
: Loc.GetString("player-join-message", ("name", args.Session.Name)));
RaiseNetworkEvent(GetConnectionStatusMsg(), session.Channel);
if (LobbyEnabled && _roundStartCountdownHasNotStartedYetDueToNoPlayers)
{
_roundStartCountdownHasNotStartedYetDueToNoPlayers = false;

View file

@ -388,8 +388,7 @@ namespace Content.Server.GameTicking
var listOfPlayerInfoFinal = listOfPlayerInfo.OrderBy(pi => pi.PlayerOOCName).ToArray();
RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, RoundId,
listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, LobbySong,
new SoundCollectionSpecifier("RoundEnd").GetSound()));
listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, LobbySong));
RaiseLocalEvent(new RoundEndedEvent(RoundId, roundDuration)); // Corvax
}
@ -525,8 +524,11 @@ namespace Content.Server.GameTicking
_playerGameStatuses.Clear();
foreach (var session in _playerManager.Sessions)
{
_playerGameStatuses[session.UserId] = LobbyEnabled ? PlayerGameStatus.NotReadyToPlay : PlayerGameStatus.ReadyToPlay;
_playerGameStatuses[session.UserId] = LobbyEnabled ? PlayerGameStatus.NotReadyToPlay : PlayerGameStatus.ReadyToPlay;
}
// Put a bangin' donk on it.
_audio.PlayGlobal(_audio.GetSound(new SoundCollectionSpecifier("RoundEnd")), Filter.Broadcast(), true);
}
public bool DelayStart(TimeSpan time)
@ -540,7 +542,7 @@ namespace Content.Server.GameTicking
RaiseNetworkEvent(new TickerLobbyCountdownEvent(_roundStartTime, Paused));
_chatManager.DispatchServerAnnouncement(Loc.GetString("game-ticker-delay-start", ("seconds",time.TotalSeconds)));
_chatManager.DispatchServerAnnouncement(Loc.GetString("game-ticker-delay-start", ("seconds", time.TotalSeconds)));
return true;
}

View file

@ -36,6 +36,8 @@ namespace Content.Server.GameTicking
private void GetStatusResponse(JsonNode jObject)
{
var preset = CurrentPreset ?? Preset;
// This method is raised from another thread, so this better be thread safe!
lock (_statusShellLock)
{
@ -52,6 +54,8 @@ namespace Content.Server.GameTicking
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
jObject["run_level"] = (int) _runLevel;
if (preset != null)
jObject["preset"] = Loc.GetString(preset.ModeTitle);
if (_runLevel >= GameRunLevel.InRound)
{
jObject["round_start_time"] = _roundStartDateTime.ToString("o");

View file

@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Content.Server.Administration.Commands;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Communications;
@ -28,6 +29,7 @@ using Content.Server.Station.Systems;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
@ -53,6 +55,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Configuration;
namespace Content.Server.GameTicking.Rules;
@ -81,6 +84,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly WarDeclaratorSystem _warDeclarator = default!;
@ -715,6 +720,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{
nukeOp = _random.PickAndTake(medPrefList);
everyone.Remove(nukeOp);
prefList.Remove(nukeOp);
Logger.InfoS("preset", "Insufficient preferred nukeop commanders, picking an agent");
}
@ -963,6 +969,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
_mind.SetUserId(newMind, session.UserId);
_roles.MindAddRole(newMind, new NukeopsRoleComponent { PrototypeId = spawnDetails.Role });
// Automatically de-admin players who are being made nukeops
if (_cfg.GetCVar(CCVars.AdminDeadminOnJoin) && _adminManager.IsAdmin(session))
_adminManager.DeAdmin(session);
_mind.TransferTo(newMind, mob);
}
else if (addSpawnPoints)

View file

@ -2,8 +2,10 @@ using Content.Server.GameTicking.Presets;
using Content.Server.GameTicking.Rules.Components;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.CCVar;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Configuration;
namespace Content.Server.GameTicking.Rules;
@ -11,6 +13,7 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
protected override void Started(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
@ -32,7 +35,8 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
{
// TODO: This doesn't consider what can't start due to minimum player count,
// but currently there's no way to know anyway as they use cvars.
var preset = _prototypeManager.Index<WeightedRandomPrototype>("Secret").Pick(_random);
var presetString = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
var preset = _prototypeManager.Index<WeightedRandomPrototype>(presetString).Pick(_random);
Logger.InfoS("gamepreset", $"Selected {preset} for secret.");
var rules = _prototypeManager.Index<GamePresetPrototype>(preset).Rules;

View file

@ -55,7 +55,7 @@ public sealed partial class GatewayGeneratorComponent : Component
/// </summary>
public List<ProtoId<BiomeMarkerLayerPrototype>> LootLayers = new()
{
"OreTin",
"OreIron",
"OreQuartz",
"OreGold",
"OreSilver",

View file

@ -59,7 +59,7 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
{
if (!args.IsInDetailsRange)
return;
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
args.PushMarkup(Loc.GetString(component.ExamineTextMindPresent));

View file

@ -44,8 +44,7 @@ public sealed class RandomGiftSystem : EntitySystem
return;
var name = _prototype.Index<EntityPrototype>(component.SelectedEntity).Name;
args.Message.PushNewline();
args.Message.AddText(Loc.GetString("gift-packin-contains", ("name", name)));
args.PushText(Loc.GetString("gift-packin-contains", ("name", name)));
}
private void OnUseInHand(EntityUid uid, RandomGiftComponent component, UseInHandEvent args)

View file

@ -23,8 +23,7 @@ namespace Content.Server.Holiday
public override void Initialize()
{
_configManager.OnValueChanged(CCVars.HolidaysEnabled, OnHolidaysEnableChange, true);
_configManager.OnValueChanged(CCVars.HolidaysEnabled, OnHolidaysEnableChange);
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
SubscribeLocalEvent<HolidayVisualsComponent, ComponentInit>(OnVisualsInit);
}

View file

@ -25,11 +25,14 @@ public sealed class HolosignSystem : EntitySystem
var charges = UsesRemaining(component, battery);
var maxCharges = MaxUses(component, battery);
args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges)));
if (charges > 0 && charges == maxCharges)
using (args.PushGroup(nameof(HolosignProjectorComponent)))
{
args.PushMarkup(Loc.GetString("limited-charges-max-charges"));
args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges)));
if (charges > 0 && charges == maxCharges)
{
args.PushMarkup(Loc.GetString("limited-charges-max-charges"));
}
}
}

View file

@ -48,7 +48,7 @@ namespace Content.Server.Interaction
private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
{
if (ev.Player.AttachedEntity is not { } user)
if (ev.Player.AttachedEntity is not { } user || ev.Result == BoundUserInterfaceRangeResult.Fail)
return;
if (InRangeUnobstructed(user, ev.Target, ev.UserInterface.InteractionRange))

View file

@ -16,6 +16,10 @@ namespace Content.Server.Kitchen.Components
public string MachinePartCookTimeMultiplier = "Capacitor";
[DataField("cookTimeScalingConstant")]
public float CookTimeScalingConstant = 0.5f;
[DataField("baseHeatMultiplier"), ViewVariables(VVAccess.ReadWrite)]
public float BaseHeatMultiplier = 100;
[DataField("objectHeatMultiplier"), ViewVariables(VVAccess.ReadWrite)]
public float ObjectHeatMultiplier = 100;
[DataField("failureResult", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string BadRecipeEntityId = "FoodBadRecipe";
@ -66,6 +70,9 @@ namespace Content.Server.Kitchen.Components
public int CurrentCookTimeButtonIndex;
public Container Storage = default!;
[DataField, ViewVariables(VVAccess.ReadWrite)]
public int Capacity = 10;
}
public sealed class BeingMicrowavedEvent : HandledEntityEventArgs

View file

@ -31,6 +31,7 @@ using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using System.Linq;
using Content.Shared.Access.Components;
namespace Content.Server.Kitchen.EntitySystems
{
@ -104,11 +105,11 @@ namespace Content.Server.Kitchen.EntitySystems
/// <param name="time">The time on the microwave, in seconds.</param>
private void AddTemperature(MicrowaveComponent component, float time)
{
var heatToAdd = time * 100;
var heatToAdd = time * component.BaseHeatMultiplier;
foreach (var entity in component.Storage.ContainedEntities)
{
if (TryComp<TemperatureComponent>(entity, out var tempComp))
_temperature.ChangeHeat(entity, heatToAdd, false, tempComp);
_temperature.ChangeHeat(entity, heatToAdd * component.ObjectHeatMultiplier, false, tempComp);
if (!TryComp<SolutionContainerManagerComponent>(entity, out var solutions))
continue;
@ -260,6 +261,12 @@ namespace Content.Server.Kitchen.EntitySystems
return;
}
if (ent.Comp.Storage.Count >= ent.Comp.Capacity)
{
_popupSystem.PopupEntity(Loc.GetString("microwave-component-interact-full"), ent, args.User);
return;
}
args.Handled = true;
_handsSystem.TryDropIntoContainer(args.User, args.Used, ent.Comp.Storage);
UpdateUserInterfaceState(ent, ent.Comp);
@ -475,10 +482,13 @@ namespace Content.Server.Kitchen.EntitySystems
//check if there's still cook time left
active.CookTimeRemaining -= frameTime;
if (active.CookTimeRemaining > 0)
{
AddTemperature(microwave, frameTime);
continue;
}
//this means the microwave has finished cooking.
AddTemperature(microwave, active.TotalTime);
AddTemperature(microwave, Math.Max(frameTime + active.CookTimeRemaining, 0)); //Though there's still a little bit more heat to pump out
if (active.PortionedRecipe.Item1 != null)
{

View file

@ -100,25 +100,28 @@ namespace Content.Server.Labels
if (comp.LabelSlot.Item is not {Valid: true} item)
return;
if (!args.IsInDetailsRange)
using (args.PushGroup(nameof(PaperLabelComponent)))
{
args.PushMarkup(Loc.GetString("comp-paper-label-has-label-cant-read"));
return;
if (!args.IsInDetailsRange)
{
args.PushMarkup(Loc.GetString("comp-paper-label-has-label-cant-read"));
return;
}
if (!EntityManager.TryGetComponent(item, out PaperComponent? paper))
// Assuming yaml has the correct entity whitelist, this should not happen.
return;
if (string.IsNullOrWhiteSpace(paper.Content))
{
args.PushMarkup(Loc.GetString("comp-paper-label-has-label-blank"));
return;
}
args.PushMarkup(Loc.GetString("comp-paper-label-has-label"));
var text = paper.Content;
args.PushMarkup(text.TrimEnd());
}
if (!EntityManager.TryGetComponent(item, out PaperComponent? paper))
// Assuming yaml has the correct entity whitelist, this should not happen.
return;
if (string.IsNullOrWhiteSpace(paper.Content))
{
args.PushMarkup(Loc.GetString("comp-paper-label-has-label-blank"));
return;
}
args.PushMarkup(Loc.GetString("comp-paper-label-has-label"));
var text = paper.Content;
args.PushMarkup(text.TrimEnd());
}
private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args)

View file

@ -47,28 +47,31 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
private void OnEmergencyExamine(EntityUid uid, EmergencyLightComponent component, ExaminedEvent args)
{
args.PushMarkup(
Loc.GetString("emergency-light-component-on-examine",
("batteryStateText",
Loc.GetString(component.BatteryStateText[component.State]))));
using (args.PushGroup(nameof(EmergencyLightComponent)))
{
args.PushMarkup(
Loc.GetString("emergency-light-component-on-examine",
("batteryStateText",
Loc.GetString(component.BatteryStateText[component.State]))));
// Show alert level on the light itself.
if (!TryComp<AlertLevelComponent>(_station.GetOwningStation(uid), out var alerts))
return;
// Show alert level on the light itself.
if (!TryComp<AlertLevelComponent>(_station.GetOwningStation(uid), out var alerts))
return;
if (alerts.AlertLevels == null)
return;
if (alerts.AlertLevels == null)
return;
var name = alerts.CurrentLevel;
var name = alerts.CurrentLevel;
var color = Color.White;
if (alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details))
color = details.Color;
var color = Color.White;
if (alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details))
color = details.Color;
args.PushMarkup(
Loc.GetString("emergency-light-component-on-examine-alert",
("color", color.ToHex()),
("level", name)));
args.PushMarkup(
Loc.GetString("emergency-light-component-on-examine-alert",
("color", color.ToHex()),
("level", name)));
}
}
private void OnEmergencyLightEvent(EntityUid uid, EmergencyLightComponent component, EmergencyLightEvent args)

View file

@ -33,23 +33,27 @@ public sealed class LightReplacerSystem : EntitySystem
private void OnExamined(EntityUid uid, LightReplacerComponent component, ExaminedEvent args)
{
if (!component.InsertedBulbs.ContainedEntities.Any())
using (args.PushGroup(nameof(LightReplacerComponent)))
{
args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights"));
return;
}
args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights"));
var groups = new Dictionary<string, int>();
var metaQuery = GetEntityQuery<MetaDataComponent>();
foreach (var bulb in component.InsertedBulbs.ContainedEntities)
{
var metaData = metaQuery.GetComponent(bulb);
groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1;
}
if (!component.InsertedBulbs.ContainedEntities.Any())
{
args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights"));
return;
}
foreach (var (name, amount) in groups)
{
args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name)));
args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights"));
var groups = new Dictionary<string, int>();
var metaQuery = GetEntityQuery<MetaDataComponent>();
foreach (var bulb in component.InsertedBulbs.ContainedEntities)
{
var metaData = metaQuery.GetComponent(bulb);
groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1;
}
foreach (var (name, amount) in groups)
{
args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name)));
}
}
}

View file

@ -3,23 +3,45 @@ using Robust.Shared.Audio;
namespace Content.Server.MagicMirror;
/// <summary>
/// Allows humanoids to change their appearance mid-round.
/// </summary>
[RegisterComponent]
public sealed partial class MagicMirrorComponent : Component
{
public Entity<HumanoidAppearanceComponent>? Target;
/// <summary>
/// Magic mirror target, used for validating UI messages.
/// </summary>
[DataField]
public EntityUid? Target;
/// <summary>
/// doafter time required to add a new slot
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float AddSlotTime = 5f;
public TimeSpan AddSlotTime = TimeSpan.FromSeconds(5);
/// <summary>
/// doafter time required to remove a existing slot
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float RemoveSlotTime = 2f;
public TimeSpan RemoveSlotTime = TimeSpan.FromSeconds(2);
/// <summary>
/// doafter time required to change slot
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float SelectSlotTime = 3f;
public TimeSpan SelectSlotTime = TimeSpan.FromSeconds(3);
/// <summary>
/// doafter time required to recolor slot
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float ChangeSlotTime = 1f;
public TimeSpan ChangeSlotTime = TimeSpan.FromSeconds(1);
/// <summary>
/// Sound emitted when slots are changed
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ChangeHairSound = new SoundPathSpecifier("/Audio/Items/scissors.ogg");
}

View file

@ -13,20 +13,23 @@ using Robust.Shared.Player;
namespace Content.Server.MagicMirror;
/// <summary>
/// Allows humanoids to change their appearance mid-round.
/// </summary>
public sealed class MagicMirrorSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly MarkingManager _markings = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MagicMirrorComponent, ActivatableUIOpenAttemptEvent>(OnOpenUIAttempt);
SubscribeLocalEvent<MagicMirrorComponent, AfterActivatableUIOpenEvent>(AfterUIOpen);
SubscribeLocalEvent<MagicMirrorComponent, BoundUIClosedEvent>(OnUIClosed);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorSelectMessage>(OnMagicMirrorSelect);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorChangeColorMessage>(OnTryMagicMirrorChangeColor);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorAddSlotMessage>(OnTryMagicMirrorAddSlot);
@ -34,21 +37,34 @@ public sealed class MagicMirrorSystem : EntitySystem
SubscribeLocalEvent<MagicMirrorComponent, AfterInteractEvent>(OnMagicMirrorInteract);
SubscribeLocalEvent<MagicMirrorComponent, SelectDoAfterEvent>(OnSelectSlotDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, ChangeColorDoAfterEvent>(OnChangeColorDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, RemoveSlotDoAfterEvent>(OnRemoveSlotDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, AddSlotDoAfterEvent>(OnAddSlotDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorSelectDoAfterEvent>(OnSelectSlotDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorChangeColorDoAfterEvent>(OnChangeColorDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorRemoveSlotDoAfterEvent>(OnRemoveSlotDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorAddSlotDoAfterEvent>(OnAddSlotDoAfter);
SubscribeLocalEvent<MagicMirrorComponent, BoundUserInterfaceCheckRangeEvent>(OnMirrorRangeCheck);
}
private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args)
{
if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value))
{
args.Result = BoundUserInterfaceRangeResult.Fail;
}
}
private void OnMagicMirrorInteract(Entity<MagicMirrorComponent> mirror, ref AfterInteractEvent args)
{
if (!TryComp<ActorComponent>(args.User, out var actor)) return;
if (TryComp<HumanoidAppearanceComponent>(args.Target, out var humanoid))
{
mirror.Comp.Target = new Entity<HumanoidAppearanceComponent>(args.Target.Value, humanoid);
UpdateInterface(mirror.Owner, mirror.Comp.Target.Value.Owner, actor.PlayerSession);
Log.Debug($"Target {mirror.Comp.Target}!");
};
if (!args.CanReach || args.Target == null)
return;
if (!TryComp<ActorComponent>(args.User, out var actor))
return;
if (!_uiSystem.TryOpen(mirror.Owner, MagicMirrorUiKey.Key, actor.PlayerSession))
return;
UpdateInterface(mirror.Owner, args.Target.Value, mirror.Comp);
}
private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, ActivatableUIOpenAttemptEvent args)
@ -59,12 +75,19 @@ public sealed class MagicMirrorSystem : EntitySystem
private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, MagicMirrorSelectMessage message)
{
if (component.Target == null) return;
if (message.Session.AttachedEntity == null) return;
if (component.Target is not { } target || message.Session.AttachedEntity is not { } user)
return;
var doAfter = new SelectDoAfterEvent(message);
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Session.AttachedEntity.Value, component.SelectSlotTime, doAfter, uid, target: component.Target.Value.Owner, used: uid)
var doAfter = new MagicMirrorSelectDoAfterEvent()
{
Category = message.Category,
Slot = message.Slot,
Marking = message.Marking,
};
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.SelectSlotTime, doAfter, uid, target: target, used: uid)
{
DistanceThreshold = SharedInteractionSystem.InteractionRange,
BreakOnTargetMove = true,
BreakOnDamage = true,
BreakOnHandChange = false,
@ -75,14 +98,18 @@ public sealed class MagicMirrorSystem : EntitySystem
_audio.PlayPvs(component.ChangeHairSound, uid);
}
private void OnSelectSlotDoAfter(EntityUid uid, MagicMirrorComponent component, SelectDoAfterEvent args)
{
if (args.Handled || args.Args.Target == null || args.Cancelled)
return;
if (component.Target == null) return;
var category = MarkingCategories.Hair;
switch (args.Message.Category)
private void OnSelectSlotDoAfter(EntityUid uid, MagicMirrorComponent component, MagicMirrorSelectDoAfterEvent args)
{
if (args.Handled || args.Target == null || args.Cancelled)
return;
if (component.Target != args.Target)
return;
MarkingCategories category;
switch (args.Category)
{
case MagicMirrorCategory.Hair:
category = MarkingCategories.Hair;
@ -94,18 +121,24 @@ public sealed class MagicMirrorSystem : EntitySystem
return;
}
_humanoid.SetMarkingId(component.Target.Value.Owner, category, args.Message.Slot, args.Message.Marking);
_humanoid.SetMarkingId(component.Target.Value, category, args.Slot, args.Marking);
UpdateInterface(uid, component.Target.Value.Owner, args.Message.Session);
UpdateInterface(uid, component.Target.Value, component);
}
private void OnTryMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent component, MagicMirrorChangeColorMessage message)
{
if (component.Target == null) return;
if (message.Session.AttachedEntity == null) return;
if (component.Target is not { } target || message.Session.AttachedEntity is not { } user)
return;
var doAfter = new ChangeColorDoAfterEvent(message);
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Session.AttachedEntity.Value, component.ChangeSlotTime, doAfter, uid, target: component.Target.Value.Owner, used: uid)
var doAfter = new MagicMirrorChangeColorDoAfterEvent()
{
Category = message.Category,
Slot = message.Slot,
Colors = message.Colors,
};
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.ChangeSlotTime, doAfter, uid, target: target, used: uid)
{
BreakOnTargetMove = true,
BreakOnDamage = true,
@ -115,14 +148,16 @@ public sealed class MagicMirrorSystem : EntitySystem
NeedHand = true
});
}
private void OnChangeColorDoAfter(EntityUid uid, MagicMirrorComponent component, ChangeColorDoAfterEvent args)
private void OnChangeColorDoAfter(EntityUid uid, MagicMirrorComponent component, MagicMirrorChangeColorDoAfterEvent args)
{
if (args.Handled || args.Args.Target == null || args.Cancelled)
if (args.Handled || args.Target == null || args.Cancelled)
return;
if (component.Target == null) return;
var category = MarkingCategories.Hair;
switch (args.Message.Category)
if (component.Target != args.Target)
return;
MarkingCategories category;
switch (args.Category)
{
case MagicMirrorCategory.Hair:
category = MarkingCategories.Hair;
@ -134,20 +169,27 @@ public sealed class MagicMirrorSystem : EntitySystem
return;
}
_humanoid.SetMarkingColor(component.Target.Value.Owner, category, args.Message.Slot, args.Message.Colors);
_humanoid.SetMarkingColor(component.Target.Value, category, args.Slot, args.Colors);
// using this makes the UI feel like total ass
// que
// UpdateInterface(uid, component.Target, message.Session);
}
private void OnTryMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent component, MagicMirrorRemoveSlotMessage message)
{
if (component.Target == null) return;
if (message.Session.AttachedEntity == null) return;
if (component.Target is not { } target || message.Session.AttachedEntity is not { } user)
return;
var doAfter = new RemoveSlotDoAfterEvent(message);
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Session.AttachedEntity.Value, component.RemoveSlotTime, doAfter, uid, target: component.Target.Value.Owner, used: uid)
var doAfter = new MagicMirrorRemoveSlotDoAfterEvent()
{
Category = message.Category,
Slot = message.Slot,
};
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.RemoveSlotTime, doAfter, uid, target: target, used: uid)
{
DistanceThreshold = SharedInteractionSystem.InteractionRange,
BreakOnTargetMove = true,
BreakOnDamage = true,
BreakOnHandChange = false,
@ -158,15 +200,18 @@ public sealed class MagicMirrorSystem : EntitySystem
_audio.PlayPvs(component.ChangeHairSound, uid);
}
private void OnRemoveSlotDoAfter(EntityUid uid, MagicMirrorComponent component, RemoveSlotDoAfterEvent args)
private void OnRemoveSlotDoAfter(EntityUid uid, MagicMirrorComponent component, MagicMirrorRemoveSlotDoAfterEvent args)
{
if (args.Handled || args.Args.Target == null || args.Cancelled)
if (args.Handled || args.Target == null || args.Cancelled)
return;
if (component.Target == null) return;
if (component.Target != args.Target)
return;
var category = MarkingCategories.Hair;
switch (args.Message.Category)
MarkingCategories category;
switch (args.Category)
{
case MagicMirrorCategory.Hair:
category = MarkingCategories.Hair;
@ -178,18 +223,25 @@ public sealed class MagicMirrorSystem : EntitySystem
return;
}
_humanoid.RemoveMarking(component.Target.Value.Owner, category, args.Message.Slot);
_humanoid.RemoveMarking(component.Target.Value, category, args.Slot);
UpdateInterface(uid, component.Target.Value.Owner, args.Message.Session);
UpdateInterface(uid, component.Target.Value, component);
}
private void OnTryMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent component, MagicMirrorAddSlotMessage message)
{
if (component.Target == null) return;
if (message.Session.AttachedEntity == null) return;
if (component.Target == null)
return;
var doAfter = new AddSlotDoAfterEvent(message);
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Session.AttachedEntity.Value, component.AddSlotTime, doAfter, uid, target: component.Target.Value.Owner, used: uid)
if (message.Session.AttachedEntity == null)
return;
var doAfter = new MagicMirrorAddSlotDoAfterEvent()
{
Category = message.Category,
};
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Session.AttachedEntity.Value, component.AddSlotTime, doAfter, uid, target: component.Target.Value, used: uid)
{
BreakOnTargetMove = true,
BreakOnDamage = true,
@ -198,17 +250,17 @@ public sealed class MagicMirrorSystem : EntitySystem
BreakOnWeightlessMove = false,
NeedHand = true
});
_audio.PlayPvs(component.ChangeHairSound, uid);
}
private void OnAddSlotDoAfter(EntityUid uid, MagicMirrorComponent component, AddSlotDoAfterEvent args)
private void OnAddSlotDoAfter(EntityUid uid, MagicMirrorComponent component, MagicMirrorAddSlotDoAfterEvent args)
{
if (args.Handled || args.Args.Target == null || args.Cancelled)
if (args.Handled || args.Target == null || args.Cancelled || !TryComp(component.Target, out HumanoidAppearanceComponent? humanoid))
return;
if (component.Target == null) return;
MarkingCategories category;
var category = MarkingCategories.Hair;
switch (args.Message.Category)
switch (args.Category)
{
case MagicMirrorCategory.Hair:
category = MarkingCategories.Hair;
@ -220,21 +272,21 @@ public sealed class MagicMirrorSystem : EntitySystem
return;
}
var marking = _markings.MarkingsByCategoryAndSpecies(category, component.Target.Value.Comp.Species).Keys.FirstOrDefault();
var marking = _markings.MarkingsByCategoryAndSpecies(category, humanoid.Species).Keys.FirstOrDefault();
if (string.IsNullOrEmpty(marking))
{
return;
}
_humanoid.AddMarking(component.Target.Value.Owner, marking, Color.Black);
_humanoid.AddMarking(component.Target.Value, marking, Color.Black);
UpdateInterface(uid, component.Target.Value, component);
UpdateInterface(uid, component.Target.Value.Owner, args.Message.Session);
}
private void UpdateInterface(EntityUid uid, EntityUid playerUid, ICommonSession session)
private void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMirrorComponent component)
{
if (!TryComp<HumanoidAppearanceComponent>(playerUid, out var humanoid)) return;
if (session is not { } player) return;
if (!TryComp<HumanoidAppearanceComponent>(targetUid, out var humanoid))
return;
var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings)
? new List<Marking>(hairMarkings)
@ -244,21 +296,19 @@ public sealed class MagicMirrorSystem : EntitySystem
? new List<Marking>(facialHairMarkings)
: new();
var msg = new MagicMirrorUiData(
var state = new MagicMirrorUiState(
humanoid.Species,
hair,
humanoid.MarkingSet.PointsLeft(MarkingCategories.Hair) + hair.Count,
facialHair,
humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count);
_uiSystem.TrySendUiMessage(uid, MagicMirrorUiKey.Key, msg, player);
component.Target = targetUid;
_uiSystem.TrySetUiState(mirrorUid, MagicMirrorUiKey.Key, state);
}
private void AfterUIOpen(EntityUid uid, MagicMirrorComponent component, AfterActivatableUIOpenEvent args)
private void OnUIClosed(Entity<MagicMirrorComponent> ent, ref BoundUIClosedEvent args)
{
if (!TryComp<HumanoidAppearanceComponent>(args.User, out var humanoid)) return;
component.Target = new Entity<HumanoidAppearanceComponent>(args.User, humanoid);
UpdateInterface(uid, args.User, args.Session);
ent.Comp.Target = null;
}
}

View file

@ -208,10 +208,13 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
var container = _itemSlotsSystem.GetItemOrNull(entity.Owner, entity.Comp.SolutionContainerName);
if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out _, out var containerSolution))
{
args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));
if (containerSolution.Volume == 0)
using (args.PushGroup(nameof(CryoPodComponent)))
{
args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker"));
args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));
if (containerSolution.Volume == 0)
{
args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker"));
}
}
}
}

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