Merge remote-tracking branch 'upstream/master' into ed-12-07-2024-upstream
# Conflicts: # Resources/Prototypes/Maps/cluster.yml # Resources/Prototypes/Maps/europa.yml
This commit is contained in:
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
@@ -5,8 +5,8 @@
|
||||
"Changes: Map":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'Resources/Maps/*.yml'
|
||||
- 'Resources/Prototypes/Maps/*.yml'
|
||||
- 'Resources/Maps/**/*.yml'
|
||||
- 'Resources/Prototypes/Maps/**/*.yml'
|
||||
|
||||
"Changes: UI":
|
||||
- changed-files:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Popup xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="#18181B"/>
|
||||
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#18181B" BackgroundColor="#25252a"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Vertical" Margin="4 4 4 4">
|
||||
<Label Name="PlayerNameLabel"/>
|
||||
<Label Name="IdLabel"/>
|
||||
<Label Name="TypeLabel"/>
|
||||
|
||||
@@ -2,21 +2,36 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Client.Chat.TypingIndicator;
|
||||
|
||||
public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingIndicatorComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex<TypingIndicatorPrototype>(component.Prototype, out var proto))
|
||||
var currentTypingIndicator = component.TypingIndicatorPrototype;
|
||||
|
||||
var evt = new BeforeShowTypingIndicatorEvent();
|
||||
|
||||
if (TryComp<InventoryComponent>(uid, out var inventoryComp))
|
||||
_inventory.RelayEvent((uid, inventoryComp), ref evt);
|
||||
|
||||
var overrideIndicator = evt.GetMostRecentIndicator();
|
||||
|
||||
if (overrideIndicator != null)
|
||||
currentTypingIndicator = overrideIndicator.Value;
|
||||
|
||||
if (!_prototypeManager.TryIndex(currentTypingIndicator, out var proto))
|
||||
{
|
||||
Log.Error($"Unknown typing indicator id: {component.Prototype}");
|
||||
Log.Error($"Unknown typing indicator id: {component.TypingIndicatorPrototype}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ namespace Content.Client.Communications.UI
|
||||
}
|
||||
|
||||
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
|
||||
CountdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s");
|
||||
var infoText = Loc.GetString($"comms-console-menu-time-remaining",
|
||||
("time", Owner.Countdown.ToString()));
|
||||
CountdownLabel.SetMessage(infoText);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class AdminFaxEui : BaseEui
|
||||
_window.OnClose += () => SendMessage(new AdminFaxEuiMsg.Close());
|
||||
_window.OnFollowFax += entity => SendMessage(new AdminFaxEuiMsg.Follow(entity));
|
||||
_window.OnMessageSend += args => SendMessage(new AdminFaxEuiMsg.Send(args.entity, args.title,
|
||||
args.stampedBy, args.message, args.stampSprite, args.stampColor));
|
||||
args.stampedBy, args.message, args.stampSprite, args.stampColor, args.locked));
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc admin-fax-stamp-color}" />
|
||||
<ColorSelectorSliders Margin="12 0 0 0" Name="StampColorSelector" Color="#BB3232"/>
|
||||
<Control MinHeight="10" />
|
||||
<Button Name="SendButton" Text="{Loc admin-fax-send}"></Button>
|
||||
<CheckBox Name="LockPageCheckbox" Text="{Loc admin-fax-lock-page}" ToolTip="{Loc admin-fax-lock-page-tooltip}"/>
|
||||
<Button Name="SendButton" Text="{Loc admin-fax-send}" Margin="0 10 0 0" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -14,7 +14,7 @@ public sealed partial class AdminFaxWindow : DefaultWindow
|
||||
{
|
||||
private const string StampsRsiPath = "/Textures/Objects/Misc/bureaucracy.rsi";
|
||||
|
||||
public Action<(NetEntity entity, string title, string stampedBy, string message, string stampSprite, Color stampColor)>? OnMessageSend;
|
||||
public Action<(NetEntity entity, string title, string stampedBy, string message, string stampSprite, Color stampColor, bool locked)>? OnMessageSend;
|
||||
public Action<NetEntity>? OnFollowFax;
|
||||
|
||||
[Dependency] private readonly IResourceCache _resCache = default!;
|
||||
@@ -98,6 +98,7 @@ public sealed partial class AdminFaxWindow : DefaultWindow
|
||||
|
||||
var from = FromEdit.Text;
|
||||
var stampColor = StampColorSelector.Color;
|
||||
OnMessageSend?.Invoke((faxEntity.Value, title, from, message, stamp, stampColor));
|
||||
var locked = LockPageCheckbox.Pressed;
|
||||
OnMessageSend?.Invoke((faxEntity.Value, title, from, message, stamp, stampColor, locked));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
|
||||
namespace Content.Shared.Item;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class ItemToggleSystem : SharedItemToggleSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -46,6 +46,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
|
||||
private CharacterSetupGui? _characterSetup;
|
||||
private HumanoidProfileEditor? _profileEditor;
|
||||
private CharacterSetupGuiSavePanel? _savePanel;
|
||||
|
||||
/// <summary>
|
||||
/// This is the characher preview panel in the chat. This should only update if their character updates.
|
||||
@@ -214,6 +215,46 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
ReloadCharacterSetup();
|
||||
}
|
||||
|
||||
private void CloseProfileEditor()
|
||||
{
|
||||
if (_profileEditor == null)
|
||||
return;
|
||||
|
||||
_profileEditor.SetProfile(null, null);
|
||||
_profileEditor.Visible = false;
|
||||
|
||||
if (_stateManager.CurrentState is LobbyState lobbyGui)
|
||||
{
|
||||
lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSavePanel()
|
||||
{
|
||||
if (_savePanel is { IsOpen: true })
|
||||
return;
|
||||
|
||||
_savePanel = new CharacterSetupGuiSavePanel();
|
||||
|
||||
_savePanel.SaveButton.OnPressed += _ =>
|
||||
{
|
||||
SaveProfile();
|
||||
|
||||
_savePanel.Close();
|
||||
|
||||
CloseProfileEditor();
|
||||
};
|
||||
|
||||
_savePanel.NoSaveButton.OnPressed += _ =>
|
||||
{
|
||||
_savePanel.Close();
|
||||
|
||||
CloseProfileEditor();
|
||||
};
|
||||
|
||||
_savePanel.OpenCentered();
|
||||
}
|
||||
|
||||
private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui()
|
||||
{
|
||||
if (_characterSetup != null && _profileEditor != null)
|
||||
@@ -240,14 +281,16 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
|
||||
_characterSetup.CloseButton.OnPressed += _ =>
|
||||
{
|
||||
// Reset sliders etc.
|
||||
_profileEditor.SetProfile(null, null);
|
||||
_profileEditor.Visible = false;
|
||||
|
||||
if (_stateManager.CurrentState is LobbyState lobbyGui)
|
||||
// Open the save panel if we have unsaved changes.
|
||||
if (_profileEditor.Profile != null && _profileEditor.IsDirty)
|
||||
{
|
||||
lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
|
||||
OpenSavePanel();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset sliders etc.
|
||||
CloseProfileEditor();
|
||||
};
|
||||
|
||||
_profileEditor.Save += SaveProfile;
|
||||
|
||||
10
Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml
Normal file
10
Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'character-setup-gui-save-panel-title'}"
|
||||
Resizable="False">
|
||||
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="4" MinSize="200 40">
|
||||
<Button Name="SaveButton" Access="Public" Text="{Loc 'character-setup-gui-save-panel-save'}" StyleClasses="ButtonBig"/>
|
||||
<Button Name="NoSaveButton" Access="Public" Text="{Loc 'character-setup-gui-save-panel-nosave'}" StyleClasses="ButtonBig"/>
|
||||
<Button Name="CancelButton" Access="Public" Text="{Loc 'character-setup-gui-save-panel-cancel'}" StyleClasses="ButtonBig"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
21
Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml.cs
Normal file
21
Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CharacterSetupGuiSavePanel : DefaultWindow
|
||||
{
|
||||
public CharacterSetupGuiSavePanel()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
CancelButton.OnPressed += _ =>
|
||||
{
|
||||
Close();
|
||||
};
|
||||
|
||||
CloseButton.Visible = false;
|
||||
}
|
||||
}
|
||||
@@ -1207,7 +1207,7 @@ namespace Content.Client.Lobby.UI
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private bool IsDirty
|
||||
public bool IsDirty
|
||||
{
|
||||
get => _isDirty;
|
||||
set
|
||||
|
||||
5
Content.Client/Ninja/Systems/ItemCreatorSystem.cs
Normal file
5
Content.Client/Ninja/Systems/ItemCreatorSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Ninja.Systems;
|
||||
|
||||
namespace Content.Client.Ninja.Systems;
|
||||
|
||||
public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
|
||||
@@ -2,9 +2,4 @@ using Content.Shared.Ninja.Systems;
|
||||
|
||||
namespace Content.Client.Ninja.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing special, only exists to provide a client implementation.
|
||||
/// </summary>
|
||||
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
|
||||
{
|
||||
}
|
||||
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
|
||||
namespace Content.Client.Ninja.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Disables cloak prediction since client has no knowledge of battery power.
|
||||
/// Cloak will still be enabled after server tells it.
|
||||
/// </summary>
|
||||
public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NinjaSuitComponent, AttemptStealthEvent>(OnAttemptStealth);
|
||||
}
|
||||
|
||||
private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;
|
||||
|
||||
@@ -2,11 +2,4 @@ using Content.Shared.Ninja.Systems;
|
||||
|
||||
namespace Content.Client.Ninja.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Currently does nothing special clientside.
|
||||
/// All functionality is in shared and server.
|
||||
/// Only exists to prevent crashing.
|
||||
/// </summary>
|
||||
public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
{
|
||||
}
|
||||
public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem;
|
||||
|
||||
5
Content.Client/Ninja/Systems/SpiderChargeSystem.cs
Normal file
5
Content.Client/Ninja/Systems/SpiderChargeSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Ninja.Systems;
|
||||
|
||||
namespace Content.Client.Ninja.Systems;
|
||||
|
||||
public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
|
||||
23
Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs
Normal file
23
Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Content.Client.Radio.Ui;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Radio.EntitySystems;
|
||||
|
||||
public sealed class RadioDeviceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<IntercomComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
|
||||
}
|
||||
|
||||
private void OnAfterHandleState(Entity<IntercomComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_ui.TryGetOpenUi<IntercomBoundUserInterface>(ent.Owner, IntercomUiKey.Key, out var bui))
|
||||
bui.Update(ent);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Radio.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Radio.Ui;
|
||||
|
||||
@@ -19,7 +19,9 @@ public sealed class IntercomBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new();
|
||||
var comp = EntMan.GetComponent<IntercomComponent>(Owner);
|
||||
|
||||
_menu = new((Owner, comp));
|
||||
|
||||
_menu.OnMicPressed += enabled =>
|
||||
{
|
||||
@@ -46,13 +48,8 @@ public sealed class IntercomBoundUserInterface : BoundUserInterface
|
||||
_menu?.Close();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public void Update(Entity<IntercomComponent> ent)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not IntercomBoundUIState msg)
|
||||
return;
|
||||
|
||||
_menu?.Update(msg);
|
||||
_menu?.Update(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Radio.Ui;
|
||||
|
||||
@@ -17,38 +18,54 @@ public sealed partial class IntercomMenu : FancyWindow
|
||||
|
||||
private readonly List<string> _channels = new();
|
||||
|
||||
public IntercomMenu()
|
||||
public IntercomMenu(Entity<IntercomComponent> entity)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
MicButton.OnPressed += args => OnMicPressed?.Invoke(args.Button.Pressed);
|
||||
SpeakerButton.OnPressed += args => OnSpeakerPressed?.Invoke(args.Button.Pressed);
|
||||
|
||||
Update(entity);
|
||||
}
|
||||
|
||||
public void Update(IntercomBoundUIState state)
|
||||
public void Update(Entity<IntercomComponent> entity)
|
||||
{
|
||||
MicButton.Pressed = state.MicEnabled;
|
||||
SpeakerButton.Pressed = state.SpeakerEnabled;
|
||||
MicButton.Pressed = entity.Comp.MicrophoneEnabled;
|
||||
SpeakerButton.Pressed = entity.Comp.SpeakerEnabled;
|
||||
|
||||
MicButton.Disabled = entity.Comp.SupportedChannels.Count == 0;
|
||||
SpeakerButton.Disabled = entity.Comp.SupportedChannels.Count == 0;
|
||||
ChannelOptions.Disabled = entity.Comp.SupportedChannels.Count == 0;
|
||||
|
||||
ChannelOptions.Clear();
|
||||
_channels.Clear();
|
||||
for (var i = 0; i < state.AvailableChannels.Count; i++)
|
||||
for (var i = 0; i < entity.Comp.SupportedChannels.Count; i++)
|
||||
{
|
||||
var channel = state.AvailableChannels[i];
|
||||
if (!_prototype.TryIndex<RadioChannelPrototype>(channel, out var prototype))
|
||||
var channel = entity.Comp.SupportedChannels[i];
|
||||
if (!_prototype.TryIndex(channel, out var prototype))
|
||||
continue;
|
||||
|
||||
_channels.Add(channel);
|
||||
ChannelOptions.AddItem(Loc.GetString(prototype.Name), i);
|
||||
|
||||
if (channel == state.SelectedChannel)
|
||||
if (channel == entity.Comp.CurrentChannel)
|
||||
ChannelOptions.Select(i);
|
||||
}
|
||||
|
||||
if (entity.Comp.SupportedChannels.Count == 0)
|
||||
{
|
||||
ChannelOptions.AddItem(Loc.GetString("intercom-options-none"), 0);
|
||||
ChannelOptions.Select(0);
|
||||
}
|
||||
|
||||
ChannelOptions.OnItemSelected += args =>
|
||||
{
|
||||
if (!_channels.TryGetValue(args.Id, out var proto))
|
||||
return;
|
||||
|
||||
ChannelOptions.SelectId(args.Id);
|
||||
OnChannelSelected?.Invoke(_channels[args.Id]);
|
||||
OnChannelSelected?.Invoke(proto);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ public sealed partial class MapScreen : BoxContainer
|
||||
ourMap = shuttleXform.MapID;
|
||||
}
|
||||
|
||||
while (mapComps.MoveNext(out var mapComp, out var mapXform, out var mapMetadata))
|
||||
while (mapComps.MoveNext(out var mapUid, out var mapComp, out var mapXform, out var mapMetadata))
|
||||
{
|
||||
if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value))
|
||||
{
|
||||
@@ -311,7 +311,7 @@ public sealed partial class MapScreen : BoxContainer
|
||||
};
|
||||
|
||||
_mapHeadings.Add(mapComp.MapId, gridContents);
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapComp.MapId))
|
||||
foreach (var grid in _mapManager.GetAllGrids(mapComp.MapId))
|
||||
{
|
||||
_entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
|
||||
|
||||
@@ -327,8 +327,10 @@ public sealed partial class MapScreen : BoxContainer
|
||||
{
|
||||
AddMapObject(mapComp.MapId, gridObj);
|
||||
}
|
||||
else if (!_shuttles.IsBeaconMap(_mapManager.GetMapEntityId(mapComp.MapId)) && (iffComp == null ||
|
||||
(iffComp.Flags & IFFFlags.Hide) == 0x0))
|
||||
// If we can show it then add it to pending.
|
||||
else if (!_shuttles.IsBeaconMap(mapUid) && (iffComp == null ||
|
||||
(iffComp.Flags & IFFFlags.Hide) == 0x0) &&
|
||||
!gridObj.HideButton)
|
||||
{
|
||||
_pendingMapObjects.Add((mapComp.MapId, gridObj));
|
||||
}
|
||||
@@ -336,11 +338,17 @@ public sealed partial class MapScreen : BoxContainer
|
||||
|
||||
foreach (var (beacon, _) in _shuttles.GetExclusions(mapComp.MapId, _exclusions))
|
||||
{
|
||||
if (beacon.HideButton)
|
||||
continue;
|
||||
|
||||
_pendingMapObjects.Add((mapComp.MapId, beacon));
|
||||
}
|
||||
|
||||
foreach (var (beacon, _) in _shuttles.GetBeacons(mapComp.MapId, _beacons))
|
||||
{
|
||||
if (beacon.HideButton)
|
||||
continue;
|
||||
|
||||
_pendingMapObjects.Add((mapComp.MapId, beacon));
|
||||
}
|
||||
|
||||
@@ -425,9 +433,6 @@ public sealed partial class MapScreen : BoxContainer
|
||||
var existing = _mapObjects.GetOrNew(mapId);
|
||||
existing.Add(mapObj);
|
||||
|
||||
if (mapObj.HideButton)
|
||||
return;
|
||||
|
||||
var gridContents = _mapHeadings[mapId];
|
||||
|
||||
var gridButton = new Button()
|
||||
|
||||
@@ -59,7 +59,6 @@ public sealed class ThrownItemVisualizerSystem : EntitySystem
|
||||
if (length <= TimeSpan.Zero)
|
||||
return null;
|
||||
|
||||
length += TimeSpan.FromSeconds(ThrowingSystem.FlyTime);
|
||||
var scale = ent.Comp2.Scale;
|
||||
var lenFloat = (float) length.TotalSeconds;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:windows="clr-namespace:Content.Client.UserInterface.Systems.Actions.Windows"
|
||||
Name="ActionsList"
|
||||
HorizontalExpand="True"
|
||||
Title="Actions"
|
||||
Title="{Loc ui-actionmenu-title}"
|
||||
VerticalExpand="True"
|
||||
Resizable="True"
|
||||
MinHeight="300"
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed partial class ActionsWindow : DefaultWindow
|
||||
|
||||
foreach (var filter in Enum.GetValues<Filters>())
|
||||
{
|
||||
FilterButton.AddItem(filter.ToString(), filter);
|
||||
FilterButton.AddItem(Loc.GetString($"ui-actionmenu-{filter.ToString().ToLower()}"), filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,9 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
if (clientInv == null)
|
||||
{
|
||||
_inventoryHotbar?.ClearButtons();
|
||||
if (_inventoryButton != null)
|
||||
_inventoryButton.Visible = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -409,6 +412,8 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
{
|
||||
slotGroup.ClearButtons();
|
||||
}
|
||||
|
||||
UpdateInventoryHotbar(null);
|
||||
}
|
||||
|
||||
private void SpriteUpdated(SlotSpriteUpdate update)
|
||||
|
||||
53
Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs
Normal file
53
Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Atmos;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class GridJoinTest
|
||||
{
|
||||
private const string CanisterProtoId = "AirCanister";
|
||||
|
||||
[Test]
|
||||
public async Task TestGridJoinAtmosphere()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var protoMan = server.ProtoMan;
|
||||
var atmosSystem = entMan.System<AtmosphereSystem>();
|
||||
var atmosDeviceSystem = entMan.System<AtmosDeviceSystem>();
|
||||
var transformSystem = entMan.System<SharedTransformSystem>();
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Spawn an atmos device on the grid
|
||||
var canister = entMan.Spawn(CanisterProtoId);
|
||||
transformSystem.SetCoordinates(canister, testMap.GridCoords);
|
||||
var deviceComp = entMan.GetComponent<AtmosDeviceComponent>(canister);
|
||||
var canisterEnt = (canister, deviceComp);
|
||||
|
||||
// Make sure the canister is tracked as an off-grid device
|
||||
Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt));
|
||||
|
||||
// Add an atmosphere to the grid
|
||||
entMan.AddComponent<GridAtmosphereComponent>(testMap.Grid);
|
||||
|
||||
// Force AtmosDeviceSystem to update off-grid devices
|
||||
// This means the canister is now considered on-grid,
|
||||
// but it's still tracked as off-grid!
|
||||
Assert.DoesNotThrow(() => atmosDeviceSystem.Update(atmosSystem.AtmosTime));
|
||||
|
||||
// Make sure that the canister is now properly tracked as on-grid
|
||||
Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt), Is.False);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -11,6 +13,19 @@ namespace Content.IntegrationTests.Tests.Hands;
|
||||
[TestFixture]
|
||||
public sealed class HandTests
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: TestPickUpThenDropInContainerTestBox
|
||||
name: box
|
||||
components:
|
||||
- type: EntityStorage
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
entity_storage: !type:Container
|
||||
";
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task TestPickupDrop()
|
||||
{
|
||||
@@ -57,4 +72,69 @@ public sealed class HandTests
|
||||
await server.WaitPost(() => mapMan.DeleteMap(data.MapId));
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestPickUpThenDropInContainer()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true,
|
||||
DummyTicker = false
|
||||
});
|
||||
var server = pair.Server;
|
||||
var map = await pair.CreateTestMap();
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var sys = entMan.System<SharedHandsSystem>();
|
||||
var tSys = entMan.System<TransformSystem>();
|
||||
var containerSystem = server.System<SharedContainerSystem>();
|
||||
|
||||
EntityUid item = default;
|
||||
EntityUid box = default;
|
||||
EntityUid player = default;
|
||||
HandsComponent hands = default!;
|
||||
|
||||
// spawn the elusive box and crowbar at the coordinates
|
||||
await server.WaitPost(() => box = server.EntMan.SpawnEntity("TestPickUpThenDropInContainerTestBox", map.GridCoords));
|
||||
await server.WaitPost(() => item = server.EntMan.SpawnEntity("Crowbar", map.GridCoords));
|
||||
// place the player at the exact same coordinates and have them grab the crowbar
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
tSys.PlaceNextTo(player, item);
|
||||
hands = entMan.GetComponent<HandsComponent>(player);
|
||||
sys.TryPickup(player, item, hands.ActiveHand!);
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
|
||||
|
||||
// Open then close the box to place the player, who is holding the crowbar, inside of it
|
||||
var storage = server.System<EntityStorageSystem>();
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
storage.OpenStorage(box);
|
||||
storage.CloseStorage(box);
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
Assert.That(containerSystem.IsEntityInContainer(player), Is.True);
|
||||
|
||||
// Dropping the item while the player is inside the box should cause the item
|
||||
// to also be inside the same container the player is in now,
|
||||
// with the item not being in the player's hands
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
sys.TryDrop(player, item, null!);
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
var xform = entMan.GetComponent<TransformComponent>(player);
|
||||
var itemXform = entMan.GetComponent<TransformComponent>(item);
|
||||
Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item));
|
||||
Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform)));
|
||||
|
||||
await server.WaitPost(() => mapMan.DeleteMap(map.MapId));
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public abstract partial class InteractionTest
|
||||
// turn on welders
|
||||
if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
|
||||
{
|
||||
Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle));
|
||||
Assert.That(ItemToggleSys.TryActivate((item, itemToggle), user: playerEnt));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ public abstract partial class InteractionTest
|
||||
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
|
||||
protected SharedDoAfterSystem DoAfterSys = default!;
|
||||
protected ToolSystem ToolSys = default!;
|
||||
protected SharedItemToggleSystem ItemToggleSys = default!;
|
||||
protected ItemToggleSystem ItemToggleSys = default!;
|
||||
protected InteractionTestSystem STestSystem = default!;
|
||||
protected SharedTransformSystem Transform = default!;
|
||||
protected SharedMapSystem MapSystem = default!;
|
||||
@@ -165,7 +165,7 @@ public abstract partial class InteractionTest
|
||||
HandSys = SEntMan.System<HandsSystem>();
|
||||
InteractSys = SEntMan.System<SharedInteractionSystem>();
|
||||
ToolSys = SEntMan.System<ToolSystem>();
|
||||
ItemToggleSys = SEntMan.System<SharedItemToggleSystem>();
|
||||
ItemToggleSys = SEntMan.System<ItemToggleSystem>();
|
||||
DoAfterSys = SEntMan.System<SharedDoAfterSystem>();
|
||||
Transform = SEntMan.System<SharedTransformSystem>();
|
||||
MapSystem = SEntMan.System<SharedMapSystem>();
|
||||
|
||||
@@ -134,22 +134,11 @@ public sealed class AmeNodeGroup : BaseNodeGroup
|
||||
// The AME is being overloaded.
|
||||
// Note about these maths: I would assume the general idea here is to make larger engines less safe to overload.
|
||||
// In other words, yes, those are supposed to be CoreCount, not safeFuelLimit.
|
||||
var instability = 0;
|
||||
var overloadVsSizeResult = fuel - CoreCount;
|
||||
|
||||
// fuel > safeFuelLimit: Slow damage. Can safely run at this level for burst periods if the engine is small and someone is keeping an eye on it.
|
||||
if (_random.Prob(0.5f))
|
||||
instability = 1;
|
||||
// overloadVsSizeResult > 5:
|
||||
if (overloadVsSizeResult > 5)
|
||||
instability = 3;
|
||||
// overloadVsSizeResult > 10: This will explode in at most 20 injections.
|
||||
if (overloadVsSizeResult > 10)
|
||||
instability = 5;
|
||||
|
||||
// Apply calculated instability
|
||||
if (instability == 0)
|
||||
return powerOutput;
|
||||
var instability = overloadVsSizeResult / CoreCount;
|
||||
var fuzz = _random.Next(-1, 2); // -1 to 1
|
||||
instability += fuzz; // fuzz the values a tiny bit.
|
||||
|
||||
overloading = true;
|
||||
var integrityCheck = 100;
|
||||
@@ -179,10 +168,12 @@ public sealed class AmeNodeGroup : BaseNodeGroup
|
||||
/// </summary>
|
||||
public float CalculatePower(int fuel, int cores)
|
||||
{
|
||||
// Fuel is squared so more fuel vastly increases power and efficiency
|
||||
// We divide by the number of cores so a larger AME is less efficient at the same fuel settings
|
||||
// this results in all AMEs having the same efficiency at the same fuel-per-core setting
|
||||
return 20000f * fuel * fuel / cores;
|
||||
// Balanced around a single core AME with injection level 2 producing 120KW.
|
||||
// Overclocking yields diminishing returns until it evens out at around 360KW.
|
||||
|
||||
// The adjustment for cores make it so that a 1 core AME at 2 injections is better than a 2 core AME at 2 injections.
|
||||
// However, for the relative amounts for each (1 core at 2 and 2 core at 4), more cores has more output.
|
||||
return 200000f * MathF.Log10(fuel * fuel) * MathF.Pow(0.75f, cores - 1);
|
||||
}
|
||||
|
||||
public int GetTotalStability()
|
||||
|
||||
@@ -274,9 +274,9 @@ public sealed class AmeControllerSystem : EntitySystem
|
||||
At the time of editing, players regularly "overclock" the AME and those cases require no admin attention.
|
||||
|
||||
// Admin alert
|
||||
var safeLimit = 0;
|
||||
var safeLimit = int.MaxValue;
|
||||
if (TryGetAMENodeGroup(uid, out var group))
|
||||
safeLimit = group.CoreCount * 2;
|
||||
safeLimit = group.CoreCount * 4;
|
||||
|
||||
if (oldValue <= safeLimit && value > safeLimit)
|
||||
{
|
||||
@@ -291,10 +291,20 @@ public sealed class AmeControllerSystem : EntitySystem
|
||||
*/
|
||||
}
|
||||
|
||||
public void AdjustInjectionAmount(EntityUid uid, int delta, int min = 0, int max = int.MaxValue, EntityUid? user = null, AmeControllerComponent? controller = null)
|
||||
public void AdjustInjectionAmount(EntityUid uid, int delta, EntityUid? user = null, AmeControllerComponent? controller = null)
|
||||
{
|
||||
if (Resolve(uid, ref controller))
|
||||
SetInjectionAmount(uid, MathHelper.Clamp(controller.InjectionAmount + delta, min, max), user, controller);
|
||||
if (!Resolve(uid, ref controller))
|
||||
return;
|
||||
|
||||
var max = GetMaxInjectionAmount((uid, controller));
|
||||
SetInjectionAmount(uid, MathHelper.Clamp(controller.InjectionAmount + delta, 0, max), user, controller);
|
||||
}
|
||||
|
||||
public int GetMaxInjectionAmount(Entity<AmeControllerComponent> ent)
|
||||
{
|
||||
if (!TryGetAMENodeGroup(ent, out var group))
|
||||
return 0;
|
||||
return group.CoreCount * 8;
|
||||
}
|
||||
|
||||
private void UpdateDisplay(EntityUid uid, int stability, AmeControllerComponent? controller = null, AppearanceComponent? appearance = null)
|
||||
|
||||
@@ -132,10 +132,19 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
var ev = new AtmosDeviceUpdateEvent(_atmosphereSystem.AtmosTime, null, null);
|
||||
foreach (var device in _joinedDevices)
|
||||
{
|
||||
DebugTools.Assert(!HasComp<GridAtmosphereComponent>(Transform(device).GridUid));
|
||||
var deviceGrid = Transform(device).GridUid;
|
||||
if (HasComp<GridAtmosphereComponent>(deviceGrid))
|
||||
{
|
||||
RejoinAtmosphere(device);
|
||||
}
|
||||
RaiseLocalEvent(device, ref ev);
|
||||
device.Comp.LastProcess = time;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsJoinedOffGrid(Entity<AtmosDeviceComponent> device)
|
||||
{
|
||||
return _joinedDevices.Contains(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
// Toggle off if they're on
|
||||
if (AreInternalsWorking(internals))
|
||||
{
|
||||
if (force || user == uid)
|
||||
if (force)
|
||||
{
|
||||
DisconnectTank(internals);
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Content.Server.Charges.Components;
|
||||
/// Something with limited charges that can be recharged automatically.
|
||||
/// Requires LimitedChargesComponent to function.
|
||||
/// </summary>
|
||||
// TODO: no reason this cant be predicted and server system deleted
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
[Access(typeof(ChargesSystem))]
|
||||
public sealed partial class AutoRechargeComponent : Component
|
||||
|
||||
@@ -37,15 +37,17 @@ public sealed class ChargesSystem : SharedChargesSystem
|
||||
args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining)));
|
||||
}
|
||||
|
||||
public override void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
|
||||
public override void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
if (!Query.Resolve(uid, ref comp, false))
|
||||
return;
|
||||
|
||||
var startRecharge = comp.Charges == comp.MaxCharges;
|
||||
base.UseCharge(uid, comp);
|
||||
// start the recharge time after first use at full charge
|
||||
if (startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
|
||||
base.AddCharges(uid, change, comp);
|
||||
|
||||
// if a charge was just used from full, start the recharge timer
|
||||
// TODO: probably make this an event instead of having le server system that just does this
|
||||
if (change < 0 && startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
|
||||
recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class AnnounceOnSpawnSystem : EntitySystem
|
||||
private void OnInit(EntityUid uid, AnnounceOnSpawnComponent comp, MapInitEvent args)
|
||||
{
|
||||
var message = Loc.GetString(comp.Message);
|
||||
var sender = comp.Sender != null ? Loc.GetString(comp.Sender) : "Central Command";
|
||||
var sender = comp.Sender != null ? Loc.GetString(comp.Sender) : Loc.GetString("chat-manager-sender-announcement");
|
||||
_chat.DispatchGlobalAnnouncement(message, sender, playSound: true, comp.Sound, comp.Color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,12 +319,14 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
/// <param name="colorOverride">Optional color for the announcement message</param>
|
||||
public void DispatchGlobalAnnouncement(
|
||||
string message,
|
||||
string sender = "Central Command",
|
||||
string? sender = null,
|
||||
bool playSound = true,
|
||||
SoundSpecifier? announcementSound = null,
|
||||
Color? colorOverride = null
|
||||
)
|
||||
{
|
||||
sender ??= Loc.GetString("chat-manager-sender-announcement");
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
|
||||
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
|
||||
if (playSound)
|
||||
@@ -345,11 +347,13 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
public void DispatchStationAnnouncement(
|
||||
EntityUid source,
|
||||
string message,
|
||||
string sender = "Central Command",
|
||||
string? sender = null,
|
||||
bool playDefaultSound = true,
|
||||
SoundSpecifier? announcementSound = null,
|
||||
Color? colorOverride = null)
|
||||
{
|
||||
sender ??= Loc.GetString("chat-manager-sender-announcement");
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
|
||||
var station = _stationSystem.GetOwningStation(source);
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
|
||||
@@ -10,34 +13,68 @@ public sealed partial class ReactionMixerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainers = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ReactionMixerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<ReactionMixerComponent, ShakeEvent>(OnShake);
|
||||
SubscribeLocalEvent<ReactionMixerComponent, ReactionMixDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(Entity<ReactionMixerComponent> entity, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.Target.HasValue || !args.CanReach)
|
||||
if (!args.Target.HasValue || !args.CanReach || !entity.Comp.MixOnInteract)
|
||||
return;
|
||||
|
||||
var mixAttemptEvent = new MixingAttemptEvent(entity);
|
||||
RaiseLocalEvent(entity, ref mixAttemptEvent);
|
||||
if (mixAttemptEvent.Cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetMixableSolution(args.Target.Value, out var solution, out _))
|
||||
if (!MixAttempt(entity, args.Target.Value, out var solution))
|
||||
return;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User);
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.TimeToMix, new ReactionMixDoAfterEvent(), entity, args.Target.Value, entity);
|
||||
|
||||
_solutionContainers.UpdateChemicals(solution.Value, true, entity.Comp);
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs);
|
||||
}
|
||||
|
||||
var afterMixingEvent = new AfterMixingEvent(entity, args.Target.Value);
|
||||
private void OnDoAfter(Entity<ReactionMixerComponent> entity, ref ReactionMixDoAfterEvent args)
|
||||
{
|
||||
//Do again to get the solution again
|
||||
if (!MixAttempt(entity, args.Target!.Value, out var solution))
|
||||
return;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target!.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User);
|
||||
|
||||
_solutionContainers.UpdateChemicals(solution!.Value, true, entity.Comp);
|
||||
|
||||
var afterMixingEvent = new AfterMixingEvent(entity, args.Target!.Value);
|
||||
RaiseLocalEvent(entity, afterMixingEvent);
|
||||
}
|
||||
|
||||
private void OnShake(Entity<ReactionMixerComponent> entity, ref ShakeEvent args)
|
||||
{
|
||||
if (!MixAttempt(entity, entity, out var solution))
|
||||
return;
|
||||
|
||||
_solutionContainers.UpdateChemicals(solution!.Value, true, entity.Comp);
|
||||
|
||||
var afterMixingEvent = new AfterMixingEvent(entity, entity);
|
||||
RaiseLocalEvent(entity, afterMixingEvent);
|
||||
}
|
||||
|
||||
private bool MixAttempt(EntityUid ent, EntityUid target, out Entity<SolutionComponent>? solution)
|
||||
{
|
||||
solution = null;
|
||||
var mixAttemptEvent = new MixingAttemptEvent(ent);
|
||||
RaiseLocalEvent(ent, ref mixAttemptEvent);
|
||||
if (mixAttemptEvent.Cancelled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetMixableSolution(target, out solution, out _))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,11 @@ namespace Content.Server.Connection
|
||||
// Wait some time to lookup data
|
||||
var record = await _dbManager.GetPlayerRecordByUserId(userId);
|
||||
|
||||
var isAccountAgeInvalid = record == null || record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
|
||||
// No player record = new account or the DB is having a skill issue
|
||||
if (record == null)
|
||||
return (false, "");
|
||||
|
||||
var isAccountAgeInvalid = record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
|
||||
if (isAccountAgeInvalid && showReason)
|
||||
{
|
||||
var locAccountReason = reason != string.Empty
|
||||
|
||||
@@ -131,6 +131,10 @@ namespace Content.Server.Database
|
||||
|
||||
if (exemptFlags is { } exempt)
|
||||
{
|
||||
// Any flag to bypass BlacklistedRange bans.
|
||||
if (exempt != ServerBanExemptFlags.None)
|
||||
exempt |= ServerBanExemptFlags.BlacklistedRange;
|
||||
|
||||
query = query.Where(b => (b.ExemptFlags & exempt) == 0);
|
||||
}
|
||||
|
||||
@@ -144,15 +148,12 @@ namespace Content.Server.Database
|
||||
ServerBanExemptFlags? exemptFlags,
|
||||
bool newPlayer)
|
||||
{
|
||||
// Any flag to bypass BlacklistedRange bans.
|
||||
var exemptFromBlacklistedRange = exemptFlags != null && exemptFlags.Value != ServerBanExemptFlags.None;
|
||||
|
||||
if (!exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)
|
||||
&& address != null
|
||||
&& ban.Address is not null
|
||||
&& address.IsInSubnet(ban.Address.ToTuple().Value)
|
||||
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) ||
|
||||
newPlayer && !exemptFromBlacklistedRange))
|
||||
newPlayer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,33 +11,63 @@ public sealed partial class ElectrifiedComponent : Component
|
||||
[DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on collide
|
||||
/// </summary>
|
||||
[DataField("onBump")]
|
||||
public bool OnBump = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on attack
|
||||
/// </summary>
|
||||
[DataField("onAttacked")]
|
||||
public bool OnAttacked = true;
|
||||
|
||||
/// <summary>
|
||||
/// When true - disables power if a window is present in the same tile
|
||||
/// </summary>
|
||||
[DataField("noWindowInTile")]
|
||||
public bool NoWindowInTile = false;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on interact with empty hand
|
||||
/// </summary>
|
||||
[DataField("onHandInteract")]
|
||||
public bool OnHandInteract = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on interact while holding an object in their hand
|
||||
/// </summary>
|
||||
[DataField("onInteractUsing")]
|
||||
public bool OnInteractUsing = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the entity requires power to function
|
||||
/// </summary>
|
||||
[DataField("requirePower")]
|
||||
public bool RequirePower = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the entity uses APC power
|
||||
/// </summary>
|
||||
[DataField("usesApcPower")]
|
||||
public bool UsesApcPower = false;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the high voltage node.
|
||||
/// </summary>
|
||||
[DataField("highVoltageNode")]
|
||||
public string? HighVoltageNode;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the medium voltage node.
|
||||
/// </summary>
|
||||
[DataField("mediumVoltageNode")]
|
||||
public string? MediumVoltageNode;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the low voltage node.
|
||||
/// </summary>
|
||||
[DataField("lowVoltageNode")]
|
||||
public string? LowVoltageNode;
|
||||
|
||||
@@ -69,7 +99,7 @@ public sealed partial class ElectrifiedComponent : Component
|
||||
public float ShockDamage = 7.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Shock time, in seconds.
|
||||
/// Shock time, in seconds.
|
||||
/// </summary>
|
||||
[DataField("shockTime")]
|
||||
public float ShockTime = 8f;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Spreader;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Database;
|
||||
@@ -64,16 +65,19 @@ public sealed partial class AreaReactionEffect : EntityEffect
|
||||
var transform = reagentArgs.EntityManager.GetComponent<TransformComponent>(reagentArgs.TargetEntity);
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var mapSys = reagentArgs.EntityManager.System<MapSystem>();
|
||||
var sys = reagentArgs.EntityManager.System<TransformSystem>();
|
||||
var spreaderSys = args.EntityManager.System<SpreaderSystem>();
|
||||
var sys = args.EntityManager.System<TransformSystem>();
|
||||
var mapCoords = sys.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform);
|
||||
|
||||
if (!mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
|
||||
!mapSys.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef) ||
|
||||
tileRef.Tile.IsSpace())
|
||||
!mapSys.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (spreaderSys.RequiresFloorToSpread(_prototypeId) && tileRef.Tile.IsSpace())
|
||||
return;
|
||||
|
||||
var coords = mapSys.MapToGrid(gridUid, mapCoords);
|
||||
var ent = reagentArgs.EntityManager.SpawnEntity(_prototypeId, coords.SnapToGrid());
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Spreader;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Maps;
|
||||
@@ -17,6 +18,7 @@ public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
|
||||
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||
[Dependency] private readonly SmokeSystem _smoke = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly SpreaderSystem _spreader = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -31,11 +33,14 @@ public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
|
||||
var mapCoords = _transform.GetMapCoordinates(uid, xform);
|
||||
if (!_mapMan.TryFindGridAt(mapCoords, out _, out var grid) ||
|
||||
!grid.TryGetTileRef(xform.Coordinates, out var tileRef) ||
|
||||
tileRef.Tile.IsSpace())
|
||||
tileRef.Tile.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_spreader.RequiresFloorToSpread(comp.SmokePrototype.ToString()) && tileRef.Tile.IsSpace())
|
||||
return;
|
||||
|
||||
var coords = grid.MapToGrid(mapCoords);
|
||||
var ent = Spawn(comp.SmokePrototype, coords.SnapToGrid());
|
||||
if (!TryComp<SmokeComponent>(ent, out var smoke))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Construction.Conditions;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Eui;
|
||||
@@ -56,7 +57,8 @@ public sealed class AdminFaxEui : BaseEui
|
||||
case AdminFaxEuiMsg.Send sendData:
|
||||
{
|
||||
var printout = new FaxPrintout(sendData.Content, sendData.Title, null, null, sendData.StampState,
|
||||
new() { new StampDisplayInfo { StampedName = sendData.From, StampedColor = sendData.StampColor } });
|
||||
new() { new StampDisplayInfo { StampedName = sendData.From, StampedColor = sendData.StampColor } },
|
||||
locked: sendData.Locked);
|
||||
_faxSystem.Receive(_entityManager.GetEntity(sendData.Target), printout);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -29,4 +29,5 @@ public static class FaxConstants
|
||||
public const string FaxPaperStampStateData = "fax_data_stamp_state";
|
||||
public const string FaxPaperStampedByData = "fax_data_stamped_by";
|
||||
public const string FaxSyndicateData = "fax_data_i_am_syndicate";
|
||||
public const string FaxPaperLockedData = "fax_data_locked";
|
||||
}
|
||||
|
||||
@@ -300,8 +300,9 @@ public sealed class FaxSystem : EntitySystem
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampedByData, out List<StampDisplayInfo>? stampedBy);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperPrototypeData, out string? prototypeId);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperLockedData, out bool? locked);
|
||||
|
||||
var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy);
|
||||
var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy, locked ?? false);
|
||||
Receive(uid, printout, args.SenderAddress);
|
||||
|
||||
break;
|
||||
@@ -473,7 +474,8 @@ public sealed class FaxSystem : EntitySystem
|
||||
labelComponent?.CurrentLabel,
|
||||
metadata.EntityPrototype?.ID ?? DefaultPaperPrototypeId,
|
||||
paper.StampState,
|
||||
paper.StampedBy);
|
||||
paper.StampedBy,
|
||||
paper.EditingDisabled);
|
||||
|
||||
component.PrintingQueue.Enqueue(printout);
|
||||
component.SendTimeoutRemaining += component.SendTimeout;
|
||||
@@ -522,6 +524,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
{ FaxConstants.FaxPaperNameData, nameMod?.BaseName ?? metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
|
||||
{ FaxConstants.FaxPaperContentData, paper.Content },
|
||||
{ FaxConstants.FaxPaperLockedData, paper.EditingDisabled },
|
||||
};
|
||||
|
||||
if (metadata.EntityPrototype != null)
|
||||
@@ -598,6 +601,8 @@ public sealed class FaxSystem : EntitySystem
|
||||
_paperSystem.TryStamp(printed, stamp, printout.StampState);
|
||||
}
|
||||
}
|
||||
|
||||
paper.EditingDisabled = printout.Locked;
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(printed, printout.Name);
|
||||
|
||||
@@ -70,13 +70,18 @@ public sealed partial class PuddleSystem
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
// First update the hit count so anything that is not reactive wont count towards the total!
|
||||
foreach (var hit in args.HitEntities)
|
||||
{
|
||||
if (!HasComp<ReactiveComponent>(hit))
|
||||
hitCount -= 1;
|
||||
}
|
||||
|
||||
foreach (var hit in args.HitEntities)
|
||||
{
|
||||
if (!HasComp<ReactiveComponent>(hit))
|
||||
{
|
||||
hitCount -= 1; // so we don't undershoot solution calculation for actual reactive entities
|
||||
continue;
|
||||
}
|
||||
|
||||
var splitSolution = _solutionContainerSystem.SplitSolution(soln.Value, totalSplit / hitCount);
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace Content.Server.GameTicking
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<GhostComponent>(playerEntity))
|
||||
if (TryComp<GhostComponent>(playerEntity, out var comp) && !comp.CanGhostInteract)
|
||||
return false;
|
||||
|
||||
if (mind.VisitingEntity != default)
|
||||
|
||||
@@ -31,16 +31,18 @@ public sealed class AntagLoadProfileRuleSystem : GameRuleSystem<AntagLoadProfile
|
||||
? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile
|
||||
: HumanoidCharacterProfile.RandomWithSpecies();
|
||||
|
||||
SpeciesPrototype? species;
|
||||
if (ent.Comp.SpeciesOverride != null)
|
||||
{
|
||||
species = _proto.Index(ent.Comp.SpeciesOverride.Value);
|
||||
}
|
||||
else if (profile?.Species is not { } speciesId || !_proto.TryIndex(speciesId, out species))
|
||||
|
||||
if (profile?.Species is not { } speciesId || !_proto.TryIndex(speciesId, out var species))
|
||||
{
|
||||
species = _proto.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies);
|
||||
}
|
||||
|
||||
if (ent.Comp.SpeciesOverride != null
|
||||
&& (ent.Comp.SpeciesOverrideBlacklist?.Contains(new ProtoId<SpeciesPrototype>(species.ID)) ?? false))
|
||||
{
|
||||
species = _proto.Index(ent.Comp.SpeciesOverride.Value);
|
||||
}
|
||||
|
||||
args.Entity = Spawn(species.Prototype);
|
||||
_humanoid.LoadProfile(args.Entity.Value, profile?.WithSpecies(species.ID));
|
||||
}
|
||||
|
||||
@@ -10,8 +10,14 @@ namespace Content.Server.GameTicking.Rules.Components;
|
||||
public sealed partial class AntagLoadProfileRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If specified, the profile loaded will be made into this species.
|
||||
/// If specified, the profile loaded will be made into this species if the chosen species matches the blacklist.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SpeciesPrototype>? SpeciesOverride;
|
||||
|
||||
/// <summary>
|
||||
/// List of species that trigger the override
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<SpeciesPrototype>>? SpeciesOverrideBlacklist;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
using Content.Server.Ninja.Systems;
|
||||
using Content.Shared.Communications;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores some configuration used by the ninja system.
|
||||
/// Objectives and roundend summary are handled by <see cref="GenericAntagRuleComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SpaceNinjaSystem))]
|
||||
public sealed partial class NinjaRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of threats that can be called in. Copied onto <see cref="CommsHackerComponent"/> when gloves are enabled.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<WeightedRandomPrototype> Threats = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when making the player a ninja via antag control or ghost role
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/ninja_greeting.ogg");
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public sealed partial class GerasComponent : Component
|
||||
{
|
||||
[DataField] public ProtoId<PolymorphPrototype> GerasPolymorphId = "SlimeMorphGeras";
|
||||
|
||||
[DataField] public ProtoId<EntityPrototype> GerasAction = "ActionMorphGeras";
|
||||
[DataField] public EntProtoId GerasAction = "ActionMorphGeras";
|
||||
|
||||
[DataField] public EntityUid? GerasActionEntity;
|
||||
}
|
||||
|
||||
@@ -207,13 +207,13 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
var length = direction.Length();
|
||||
var distance = Math.Clamp(length, minDistance, hands.ThrowRange);
|
||||
direction *= distance/length;
|
||||
direction *= distance / length;
|
||||
|
||||
var throwStrength = hands.ThrowForceMultiplier;
|
||||
var throwSpeed = hands.BaseThrowspeed;
|
||||
|
||||
// Let other systems change the thrown entity (useful for virtual items)
|
||||
// or the throw strength.
|
||||
var ev = new BeforeThrowEvent(throwEnt, direction, throwStrength, player);
|
||||
var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player);
|
||||
RaiseLocalEvent(player, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
@@ -223,7 +223,7 @@ namespace Content.Server.Hands.Systems
|
||||
if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands))
|
||||
return false;
|
||||
|
||||
_throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowStrength, ev.PlayerUid);
|
||||
_throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp<LandAtCursorComponent>(ev.ItemUid));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace Content.Server.Item;
|
||||
|
||||
/// <summary>
|
||||
/// Handles whether this item applies a disarm malus when active.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ItemToggleDisarmMalusComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Item has this modifier to the chance to disarm when activated.
|
||||
/// If null, the value will be inferred from the current malus just before the malus is first deactivated.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
public float? ActivatedDisarmMalus = null;
|
||||
|
||||
/// <summary>
|
||||
/// Item has this modifier to the chance to disarm when deactivated. If none is mentioned, it uses the item's default disarm modifier.
|
||||
/// If null, the value will be inferred from the current malus just before the malus is first activated.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
public float? DeactivatedDisarmMalus = null;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server.Item;
|
||||
|
||||
/// <summary>
|
||||
/// Handles whether this item is sharp when toggled on.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ItemToggleSharpComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Server.CombatMode.Disarm;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
|
||||
namespace Content.Server.Item;
|
||||
|
||||
public sealed class ItemToggleSystem : SharedItemToggleSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ItemToggleSharpComponent, ItemToggledEvent>(ToggleSharp);
|
||||
SubscribeLocalEvent<ItemToggleDisarmMalusComponent, ItemToggledEvent>(ToggleMalus);
|
||||
}
|
||||
|
||||
private void ToggleSharp(Entity<ItemToggleSharpComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
// TODO generalize this into a "ToggleComponentComponent", though probably with a better name
|
||||
if (args.Activated)
|
||||
EnsureComp<SharpComponent>(ent);
|
||||
else
|
||||
RemCompDeferred<SharpComponent>(ent);
|
||||
}
|
||||
|
||||
private void ToggleMalus(Entity<ItemToggleDisarmMalusComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
if (!TryComp<DisarmMalusComponent>(ent, out var malus))
|
||||
return;
|
||||
|
||||
if (args.Activated)
|
||||
{
|
||||
ent.Comp.DeactivatedDisarmMalus ??= malus.Malus;
|
||||
if (ent.Comp.ActivatedDisarmMalus is {} activatedMalus)
|
||||
malus.Malus = activatedMalus;
|
||||
return;
|
||||
}
|
||||
|
||||
ent.Comp.ActivatedDisarmMalus ??= malus.Malus;
|
||||
if (ent.Comp.DeactivatedDisarmMalus is {} deactivatedMalus)
|
||||
malus.Malus = deactivatedMalus;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
|
||||
args.PushMarkup(
|
||||
Loc.GetString("emergency-light-component-on-examine-alert",
|
||||
("color", color.ToHex()),
|
||||
("level", name)));
|
||||
("level", Loc.GetString($"alert-level-{name.ToString().ToLower()}"))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,6 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
|
||||
_pointLight.SetColor(entity.Owner, color);
|
||||
_appearance.SetData(entity.Owner, EmergencyLightVisuals.Color, color);
|
||||
_appearance.SetData(entity.Owner, EmergencyLightVisuals.On, true);
|
||||
_ambient.SetAmbience(entity.Owner, true);
|
||||
_ambient.SetAmbience(entity.Owner, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class LubedSystem : EntitySystem
|
||||
var user = args.Container.Owner;
|
||||
_transform.SetCoordinates(uid, Transform(user).Coordinates);
|
||||
_transform.AttachToGridOrMap(uid);
|
||||
_throwing.TryThrow(uid, _random.NextVector2(), strength: component.SlipStrength);
|
||||
_throwing.TryThrow(uid, _random.NextVector2(), baseThrowSpeed: component.SlipStrength);
|
||||
_popup.PopupEntity(Loc.GetString("lube-slip", ("target", Identity.Entity(uid, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using Content.Server.Station.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
|
||||
namespace Content.Server.MassMedia.Systems;
|
||||
|
||||
@@ -35,6 +36,7 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -161,6 +163,12 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
$"{ToPrettyString(msg.Actor):actor} created news article {article.Title} by {article.Author}: {article.Content}"
|
||||
);
|
||||
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("news-publish-admin-announcement",
|
||||
("actor", msg.Actor),
|
||||
("title", article.Title),
|
||||
("author", article.Author ?? Loc.GetString("news-read-ui-no-author"))
|
||||
));
|
||||
|
||||
articles.Add(article);
|
||||
|
||||
var args = new NewsArticlePublishedEvent(article);
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Content.Server.Medical.Components;
|
||||
/// <summary>
|
||||
/// After scanning, retrieves the target Uid to use with its related UI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires <c>ItemToggleComponent</c>.
|
||||
/// </remarks>
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
[Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
|
||||
public sealed partial class HealthAnalyzerComponent : Component
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Medical;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
@@ -37,6 +38,7 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggle = default!;
|
||||
[Dependency] private readonly RottingSystem _rotting = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||
@@ -50,30 +52,10 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DefibrillatorComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<DefibrillatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled || !TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
|
||||
return;
|
||||
|
||||
if (!TryToggle(uid, component, args.User))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
_useDelay.TryResetDelay((uid, useDelay));
|
||||
}
|
||||
|
||||
private void OnPowerCellSlotEmpty(EntityUid uid, DefibrillatorComponent component, ref PowerCellSlotEmptyEvent args)
|
||||
{
|
||||
if (!TerminatingOrDeleted(uid))
|
||||
TryDisable(uid, component);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || args.Target is not { } target)
|
||||
@@ -96,54 +78,12 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
Zap(uid, target, args.User, component);
|
||||
}
|
||||
|
||||
public bool TryToggle(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
return component.Enabled
|
||||
? TryDisable(uid, component)
|
||||
: TryEnable(uid, component, user);
|
||||
}
|
||||
|
||||
public bool TryEnable(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (component.Enabled)
|
||||
return false;
|
||||
|
||||
if (!_powerCell.HasActivatableCharge(uid))
|
||||
return false;
|
||||
|
||||
component.Enabled = true;
|
||||
_appearance.SetData(uid, ToggleVisuals.Toggled, true);
|
||||
_audio.PlayPvs(component.PowerOnSound, uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryDisable(EntityUid uid, DefibrillatorComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (!component.Enabled)
|
||||
return false;
|
||||
|
||||
component.Enabled = false;
|
||||
_appearance.SetData(uid, ToggleVisuals.Toggled, false);
|
||||
|
||||
_audio.PlayPvs(component.PowerOffSound, uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (!component.Enabled)
|
||||
if (!_toggle.IsActivated(uid))
|
||||
{
|
||||
if (user != null)
|
||||
_popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
|
||||
@@ -257,7 +197,7 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
|
||||
// if we don't have enough power left for another shot, turn it off
|
||||
if (!_powerCell.HasActivatableCharge(uid))
|
||||
TryDisable(uid, component);
|
||||
_toggle.TryDeactivate(uid);
|
||||
|
||||
// TODO clean up this clown show above
|
||||
var ev = new TargetDefibrillatedEvent(user, (uid, component));
|
||||
|
||||
@@ -10,12 +10,14 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Medical;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
@@ -188,6 +190,12 @@ public sealed class HealingSystem : EntitySystem
|
||||
|
||||
var isNotSelf = user != target;
|
||||
|
||||
if (isNotSelf)
|
||||
{
|
||||
var msg = Loc.GetString("medical-item-popup-target", ("user", Identity.Entity(user, EntityManager)), ("item", uid));
|
||||
_popupSystem.PopupEntity(msg, target, target, PopupType.Medium);
|
||||
}
|
||||
|
||||
var delay = isNotSelf
|
||||
? component.Delay
|
||||
: component.Delay * GetScaledHealingPenalty(user, component);
|
||||
|
||||
@@ -5,10 +5,14 @@ using Content.Server.PowerCell;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.MedicalScanner;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.PowerCell;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -24,16 +28,18 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
[Dependency] private readonly PowerCellSystem _cell = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggle = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<HealthAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
|
||||
SubscribeLocalEvent<HealthAnalyzerComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<HealthAnalyzerComponent, ItemToggledEvent>(OnToggled);
|
||||
SubscribeLocalEvent<HealthAnalyzerComponent, DroppedEvent>(OnDropped);
|
||||
}
|
||||
|
||||
@@ -85,6 +91,9 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
NeedHand = true,
|
||||
BreakOnMove = true
|
||||
});
|
||||
|
||||
var msg = Loc.GetString("health-analyzer-popup-scan-target", ("user", Identity.Entity(args.User, EntityManager)));
|
||||
_popupSystem.PopupEntity(msg, args.Target.Value, args.Target.Value, PopupType.Medium);
|
||||
}
|
||||
|
||||
private void OnDoAfter(Entity<HealthAnalyzerComponent> uid, ref HealthAnalyzerDoAfterEvent args)
|
||||
@@ -105,16 +114,16 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
private void OnInsertedIntoContainer(Entity<HealthAnalyzerComponent> uid, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (uid.Comp.ScannedEntity is { } patient)
|
||||
StopAnalyzingEntity(uid, patient);
|
||||
_toggle.TryDeactivate(uid.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable continuous updates once battery is dead
|
||||
/// Disable continuous updates once turned off
|
||||
/// </summary>
|
||||
private void OnPowerCellSlotEmpty(Entity<HealthAnalyzerComponent> uid, ref PowerCellSlotEmptyEvent args)
|
||||
private void OnToggled(Entity<HealthAnalyzerComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
if (uid.Comp.ScannedEntity is { } patient)
|
||||
StopAnalyzingEntity(uid, patient);
|
||||
if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
|
||||
StopAnalyzingEntity(ent, patient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,7 +132,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
private void OnDropped(Entity<HealthAnalyzerComponent> uid, ref DroppedEvent args)
|
||||
{
|
||||
if (uid.Comp.ScannedEntity is { } patient)
|
||||
StopAnalyzingEntity(uid, patient);
|
||||
_toggle.TryDeactivate(uid.Owner);
|
||||
}
|
||||
|
||||
private void OpenUserInterface(EntityUid user, EntityUid analyzer)
|
||||
@@ -144,7 +153,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
//Link the health analyzer to the scanned entity
|
||||
healthAnalyzer.Comp.ScannedEntity = target;
|
||||
|
||||
_cell.SetPowerCellDrawEnabled(healthAnalyzer, true);
|
||||
_toggle.TryActivate(healthAnalyzer.Owner);
|
||||
|
||||
UpdateScannedUser(healthAnalyzer, target, true);
|
||||
}
|
||||
@@ -159,7 +168,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
//Unlink the analyzer
|
||||
healthAnalyzer.Comp.ScannedEntity = null;
|
||||
|
||||
_cell.SetPowerCellDrawEnabled(target, false);
|
||||
_toggle.TryDeactivate(healthAnalyzer.Owner);
|
||||
|
||||
UpdateScannedUser(healthAnalyzer, target, false);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Content.Server.Ninja.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the ninja when the suit has its powercell changed.
|
||||
/// Raised on the ninja and suit when the suit has its powercell changed.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct NinjaBatteryChangedEvent(EntityUid Battery, EntityUid BatteryHolder);
|
||||
|
||||
@@ -33,16 +33,17 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
|
||||
/// Start do after for draining a power source.
|
||||
/// Can't predict PNBC existing so only done on server.
|
||||
/// </summary>
|
||||
private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, BeforeInteractHandEvent args)
|
||||
private void OnBeforeInteractHand(Entity<BatteryDrainerComponent> ent, ref BeforeInteractHandEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
var target = args.Target;
|
||||
if (args.Handled || comp.BatteryUid == null || !HasComp<PowerNetworkBatteryComponent>(target))
|
||||
if (args.Handled || comp.BatteryUid is not {} battery || !HasComp<PowerNetworkBatteryComponent>(target))
|
||||
return;
|
||||
|
||||
// handles even if battery is full so you can actually see the poup
|
||||
args.Handled = true;
|
||||
|
||||
if (_battery.IsFull(comp.BatteryUid.Value))
|
||||
if (_battery.IsFull(battery))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("battery-drainer-full"), uid, uid, PopupType.Medium);
|
||||
return;
|
||||
@@ -59,23 +60,24 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
|
||||
_doAfter.TryStartDoAfter(doAfterArgs);
|
||||
}
|
||||
|
||||
private void OnBatteryChanged(EntityUid uid, BatteryDrainerComponent comp, ref NinjaBatteryChangedEvent args)
|
||||
private void OnBatteryChanged(Entity<BatteryDrainerComponent> ent, ref NinjaBatteryChangedEvent args)
|
||||
{
|
||||
SetBattery(uid, args.Battery, comp);
|
||||
SetBattery((ent, ent.Comp), args.Battery);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent<DrainDoAfterEvent> args)
|
||||
protected override void OnDoAfterAttempt(Entity<BatteryDrainerComponent> ent, ref DoAfterAttemptEvent<DrainDoAfterEvent> args)
|
||||
{
|
||||
base.OnDoAfterAttempt(uid, comp, args);
|
||||
base.OnDoAfterAttempt(ent, ref args);
|
||||
|
||||
if (comp.BatteryUid == null || _battery.IsFull(comp.BatteryUid.Value))
|
||||
if (ent.Comp.BatteryUid is not {} battery || _battery.IsFull(battery))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
|
||||
protected override bool TryDrainPower(Entity<BatteryDrainerComponent> ent, EntityUid target)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
if (comp.BatteryUid == null || !TryComp<BatteryComponent>(comp.BatteryUid.Value, out var battery))
|
||||
return false;
|
||||
|
||||
@@ -98,6 +100,7 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
|
||||
|
||||
var output = input * comp.DrainEfficiency;
|
||||
_battery.SetCharge(comp.BatteryUid.Value, battery.CurrentCharge + output, battery);
|
||||
// TODO: create effect message or something
|
||||
Spawn("EffectSparks", Transform(target).Coordinates);
|
||||
_audio.PlayPvs(comp.SparkSound, target);
|
||||
_popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);
|
||||
|
||||
57
Content.Server/Ninja/Systems/ItemCreatorSystem.cs
Normal file
57
Content.Server/Ninja/Systems/ItemCreatorSystem.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Content.Server.Ninja.Events;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server.Ninja.Systems;
|
||||
|
||||
public sealed class ItemCreatorSystem : SharedItemCreatorSystem
|
||||
{
|
||||
[Dependency] private readonly BatterySystem _battery = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ItemCreatorComponent, CreateItemEvent>(OnCreateItem);
|
||||
SubscribeLocalEvent<ItemCreatorComponent, NinjaBatteryChangedEvent>(OnBatteryChanged);
|
||||
}
|
||||
|
||||
private void OnCreateItem(Entity<ItemCreatorComponent> ent, ref CreateItemEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
if (comp.Battery is not {} battery)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var user = args.Performer;
|
||||
if (!_battery.TryUseCharge(battery, comp.Charge))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(comp.NoPowerPopup), user, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new CreateItemAttemptEvent(user);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
// try to put throwing star in hand, otherwise it goes on the ground
|
||||
var star = Spawn(comp.SpawnedPrototype, Transform(user).Coordinates);
|
||||
_hands.TryPickupAnyHand(user, star);
|
||||
}
|
||||
|
||||
private void OnBatteryChanged(Entity<ItemCreatorComponent> ent, ref NinjaBatteryChangedEvent args)
|
||||
{
|
||||
if (ent.Comp.Battery == args.Battery)
|
||||
return;
|
||||
|
||||
ent.Comp.Battery = args.Battery;
|
||||
Dirty(ent, ent.Comp);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,8 @@
|
||||
using Content.Server.Communications;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Ninja.Events;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Shared.Communications;
|
||||
using Content.Shared.CriminalRecords.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Research.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
|
||||
namespace Content.Server.Ninja.Systems;
|
||||
|
||||
@@ -16,89 +11,44 @@ namespace Content.Server.Ninja.Systems;
|
||||
/// </summary>
|
||||
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
|
||||
{
|
||||
[Dependency] private readonly EmagProviderSystem _emagProvider = default!;
|
||||
[Dependency] private readonly CodeConditionSystem _codeCondition = default!;
|
||||
[Dependency] private readonly CommsHackerSystem _commsHacker = default!;
|
||||
[Dependency] private readonly SharedStunProviderSystem _stunProvider = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SpaceNinjaSystem _ninja = default!;
|
||||
|
||||
public override void Initialize()
|
||||
protected override void EnableGloves(Entity<NinjaGlovesComponent> ent, Entity<SpaceNinjaComponent> user)
|
||||
{
|
||||
base.Initialize();
|
||||
base.EnableGloves(ent, user);
|
||||
|
||||
SubscribeLocalEvent<NinjaGlovesComponent, ToggleActionEvent>(OnToggleAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle gloves, if the user is a ninja wearing a ninja suit.
|
||||
/// </summary>
|
||||
private void OnToggleAction(EntityUid uid, NinjaGlovesComponent comp, ToggleActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var user = args.Performer;
|
||||
// need to wear suit to enable gloves
|
||||
if (!TryComp<SpaceNinjaComponent>(user, out var ninja)
|
||||
|| ninja.Suit == null
|
||||
|| !HasComp<NinjaSuitComponent>(ninja.Suit.Value))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-gloves-not-wearing-suit"), user, user);
|
||||
return;
|
||||
}
|
||||
|
||||
// show its state to the user
|
||||
var enabling = comp.User == null;
|
||||
Appearance.SetData(uid, ToggleVisuals.Toggled, enabling);
|
||||
var message = Loc.GetString(enabling ? "ninja-gloves-on" : "ninja-gloves-off");
|
||||
Popup.PopupEntity(message, user, user);
|
||||
|
||||
if (enabling)
|
||||
{
|
||||
EnableGloves(uid, comp, user, ninja);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisableGloves(uid, comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableGloves(EntityUid uid, NinjaGlovesComponent comp, EntityUid user, SpaceNinjaComponent ninja)
|
||||
{
|
||||
// can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
|
||||
if (ninja.Suit == null)
|
||||
if (user.Comp.Suit is not {} suit)
|
||||
return;
|
||||
|
||||
comp.User = user;
|
||||
Dirty(uid, comp);
|
||||
_ninja.AssignGloves(user, uid, ninja);
|
||||
if (!_mind.TryGetMind(user, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
var drainer = EnsureComp<BatteryDrainerComponent>(user);
|
||||
var stun = EnsureComp<StunProviderComponent>(user);
|
||||
_stunProvider.SetNoPowerPopup(user, "ninja-no-power", stun);
|
||||
foreach (var ability in ent.Comp.Abilities)
|
||||
{
|
||||
// non-objective abilities are added in shared already
|
||||
if (ability.Objective is not {} objId)
|
||||
continue;
|
||||
|
||||
// prevent doing an objective multiple times by toggling gloves after doing them
|
||||
// if it's not tied to an objective always add them anyway
|
||||
if (!_mind.TryFindObjective((mindId, mind), objId, out var obj))
|
||||
{
|
||||
Log.Error($"Ninja glove ability of {ent} referenced missing objective {ability.Objective} of {_mind.MindOwnerLoggingString(mind)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_objectives.IsCompleted(obj.Value, (mindId, mind)))
|
||||
EntityManager.AddComponents(user, ability.Components);
|
||||
}
|
||||
|
||||
// let abilities that use battery power work
|
||||
if (_ninja.GetNinjaBattery(user, out var battery, out var _))
|
||||
{
|
||||
var ev = new NinjaBatteryChangedEvent(battery.Value, ninja.Suit.Value);
|
||||
var ev = new NinjaBatteryChangedEvent(battery.Value, suit);
|
||||
RaiseLocalEvent(user, ref ev);
|
||||
}
|
||||
|
||||
var emag = EnsureComp<EmagProviderComponent>(user);
|
||||
_emagProvider.SetWhitelist(user, comp.DoorjackWhitelist, emag);
|
||||
|
||||
EnsureComp<ResearchStealerComponent>(user);
|
||||
// prevent calling in multiple threats by toggling gloves after
|
||||
if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
|
||||
{
|
||||
var hacker = EnsureComp<CommsHackerComponent>(user);
|
||||
var rule = _ninja.NinjaRule(user);
|
||||
if (rule != null)
|
||||
_commsHacker.SetThreats(user, rule.Threats, hacker);
|
||||
}
|
||||
if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
|
||||
{
|
||||
EnsureComp<CriminalRecordsHackerComponent>(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Emp;
|
||||
using Content.Server.Ninja.Events;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
@@ -29,15 +28,13 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
|
||||
SubscribeLocalEvent<NinjaSuitComponent, ContainerIsInsertingAttemptEvent>(OnSuitInsertAttempt);
|
||||
SubscribeLocalEvent<NinjaSuitComponent, EmpAttemptEvent>(OnEmpAttempt);
|
||||
SubscribeLocalEvent<NinjaSuitComponent, AttemptStealthEvent>(OnAttemptStealth);
|
||||
SubscribeLocalEvent<NinjaSuitComponent, CreateThrowingStarEvent>(OnCreateThrowingStar);
|
||||
SubscribeLocalEvent<NinjaSuitComponent, RecallKatanaEvent>(OnRecallKatana);
|
||||
SubscribeLocalEvent<NinjaSuitComponent, NinjaEmpEvent>(OnEmp);
|
||||
}
|
||||
|
||||
protected override void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
|
||||
protected override void NinjaEquipped(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
|
||||
{
|
||||
base.NinjaEquippedSuit(uid, comp, user, ninja);
|
||||
base.NinjaEquipped(ent, user);
|
||||
|
||||
_ninja.SetSuitPowerAlert(user);
|
||||
}
|
||||
@@ -57,16 +54,15 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
|
||||
// can only upgrade power cell, not swap to recharge instantly otherwise ninja could just swap batteries with flashlights in maints for easy power
|
||||
if (!TryComp<BatteryComponent>(args.EntityUid, out var inserting) || inserting.MaxCharge <= battery.MaxCharge)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
// tell ninja abilities that use battery to update it so they don't use charge from the old one
|
||||
var user = Transform(uid).ParentUid;
|
||||
if (!HasComp<SpaceNinjaComponent>(user))
|
||||
if (!_ninja.IsNinja(user))
|
||||
return;
|
||||
|
||||
var ev = new NinjaBatteryChangedEvent(args.EntityUid, uid);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
RaiseLocalEvent(user, ref ev);
|
||||
}
|
||||
|
||||
@@ -77,64 +73,22 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
protected override void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
|
||||
protected override void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
|
||||
{
|
||||
base.UserUnequippedSuit(uid, comp, user);
|
||||
base.UserUnequippedSuit(ent, user);
|
||||
|
||||
// remove power indicator
|
||||
_ninja.SetSuitPowerAlert(user);
|
||||
}
|
||||
|
||||
private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
|
||||
private void OnRecallKatana(Entity<NinjaSuitComponent> ent, ref RecallKatanaEvent args)
|
||||
{
|
||||
var user = args.User;
|
||||
// need 1 second of charge to turn on stealth
|
||||
var chargeNeeded = SuitWattage(uid, comp);
|
||||
// being attacked while cloaked gives no power message since it overloads the power supply or something
|
||||
if (!_ninja.GetNinjaBattery(user, out _, out var battery) || battery.CurrentCharge < chargeNeeded)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (comp.DisableCooldown > GameTiming.CurTime)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
StealthClothing.SetEnabled(uid, user, true);
|
||||
}
|
||||
|
||||
private void OnCreateThrowingStar(EntityUid uid, NinjaSuitComponent comp, CreateThrowingStarEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
var (uid, comp) = ent;
|
||||
var user = args.Performer;
|
||||
if (!_ninja.TryUseCharge(user, comp.ThrowingStarCharge))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
|
||||
if (!_ninja.NinjaQuery.TryComp(user, out var ninja) || ninja.Katana == null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (comp.DisableCooldown > GameTiming.CurTime)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
// try to put throwing star in hand, otherwise it goes on the ground
|
||||
var star = Spawn(comp.ThrowingStarPrototype, Transform(user).Coordinates);
|
||||
_hands.TryPickupAnyHand(user, star);
|
||||
}
|
||||
|
||||
private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatanaEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
var user = args.Performer;
|
||||
if (!TryComp<SpaceNinjaComponent>(user, out var ninja) || ninja.Katana == null)
|
||||
return;
|
||||
|
||||
var katana = ninja.Katana.Value;
|
||||
var coords = _transform.GetWorldPosition(katana);
|
||||
@@ -146,11 +100,8 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (comp.DisableCooldown > GameTiming.CurTime)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
|
||||
if (CheckDisabled(ent, user))
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: teleporting into belt slot
|
||||
var message = _hands.TryPickupAnyHand(user, katana)
|
||||
@@ -159,9 +110,11 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
Popup.PopupEntity(Loc.GetString(message), user, user);
|
||||
}
|
||||
|
||||
private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
|
||||
private void OnEmp(Entity<NinjaSuitComponent> ent, ref NinjaEmpEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
args.Handled = true;
|
||||
|
||||
var user = args.Performer;
|
||||
if (!_ninja.TryUseCharge(user, comp.EmpCharge))
|
||||
{
|
||||
@@ -169,13 +122,9 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (comp.DisableCooldown > GameTiming.CurTime)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
|
||||
if (CheckDisabled(ent, user))
|
||||
return;
|
||||
}
|
||||
|
||||
// I don't think this affects the suit battery, but if it ever does in the future add a blacklist for it
|
||||
var coords = _transform.GetMapCoordinates(user);
|
||||
_emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Communications;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.CriminalRecords.Systems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.GenericAntag;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -11,7 +10,6 @@ using Content.Server.PowerCell;
|
||||
using Content.Server.Research.Systems;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mind;
|
||||
@@ -26,11 +24,6 @@ using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Ninja.Systems;
|
||||
|
||||
// TODO: when syndiborgs are a thing have a borg converter with 6 second doafter
|
||||
// engi -> saboteur
|
||||
// medi -> idk reskin it
|
||||
// other -> assault
|
||||
|
||||
/// <summary>
|
||||
/// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
|
||||
/// </summary>
|
||||
@@ -44,13 +37,11 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly StealthClothingSystem _stealthClothing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SpaceNinjaComponent, GenericAntagCreatedEvent>(OnNinjaCreated);
|
||||
SubscribeLocalEvent<SpaceNinjaComponent, EmaggedSomethingEvent>(OnDoorjack);
|
||||
SubscribeLocalEvent<SpaceNinjaComponent, ResearchStolenEvent>(OnResearchStolen);
|
||||
SubscribeLocalEvent<SpaceNinjaComponent, ThreatCalledInEvent>(OnThreatCalledIn);
|
||||
@@ -62,7 +53,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
var query = EntityQueryEnumerator<SpaceNinjaComponent>();
|
||||
while (query.MoveNext(out var uid, out var ninja))
|
||||
{
|
||||
UpdateNinja(uid, ninja, frameTime);
|
||||
SetSuitPowerAlert((uid, ninja));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,31 +71,13 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
return newCount - oldCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a ninja's gamerule config data.
|
||||
/// If the gamerule was not started then it will be started automatically.
|
||||
/// </summary>
|
||||
public NinjaRuleComponent? NinjaRule(EntityUid uid, GenericAntagComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return null;
|
||||
|
||||
// mind not added yet so no rule
|
||||
if (comp.RuleEntity == null)
|
||||
return null;
|
||||
|
||||
return CompOrNull<NinjaRuleComponent>(comp.RuleEntity);
|
||||
}
|
||||
|
||||
// TODO: can probably copy paste borg code here
|
||||
/// <summary>
|
||||
/// Update the alert for the ninja's suit power indicator.
|
||||
/// </summary>
|
||||
public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
|
||||
public void SetSuitPowerAlert(Entity<SpaceNinjaComponent> ent)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return;
|
||||
|
||||
var (uid, comp) = ent;
|
||||
if (comp.Deleted || comp.Suit == null)
|
||||
{
|
||||
_alerts.ClearAlert(uid, comp.SuitPowerAlert);
|
||||
@@ -145,53 +118,6 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set up everything for ninja to work and send the greeting message/sound.
|
||||
/// Objectives are added by <see cref="GenericAntagSystem"/>.
|
||||
/// </summary>
|
||||
private void OnNinjaCreated(EntityUid uid, SpaceNinjaComponent comp, ref GenericAntagCreatedEvent args)
|
||||
{
|
||||
var mindId = args.MindId;
|
||||
var mind = args.Mind;
|
||||
|
||||
if (mind.Session == null)
|
||||
return;
|
||||
|
||||
var config = NinjaRule(uid);
|
||||
if (config == null)
|
||||
return;
|
||||
|
||||
var role = new NinjaRoleComponent
|
||||
{
|
||||
PrototypeId = "SpaceNinja"
|
||||
};
|
||||
_role.MindAddRole(mindId, role, mind);
|
||||
_role.MindPlaySound(mindId, config.GreetingSound, mind);
|
||||
|
||||
var session = mind.Session;
|
||||
_audio.PlayGlobal(config.GreetingSound, Filter.Empty().AddPlayer(session), false, AudioParams.Default);
|
||||
_chatMan.DispatchServerMessage(session, Loc.GetString("ninja-role-greeting"));
|
||||
}
|
||||
|
||||
// TODO: PowerCellDraw, modify when cloak enabled
|
||||
/// <summary>
|
||||
/// Handle constant power drains from passive usage and cloak.
|
||||
/// </summary>
|
||||
private void UpdateNinja(EntityUid uid, SpaceNinjaComponent ninja, float frameTime)
|
||||
{
|
||||
if (ninja.Suit == null)
|
||||
return;
|
||||
|
||||
float wattage = Suit.SuitWattage(ninja.Suit.Value);
|
||||
|
||||
SetSuitPowerAlert(uid, ninja);
|
||||
if (!TryUseCharge(uid, wattage * frameTime))
|
||||
{
|
||||
// ran out of power, uncloak ninja
|
||||
_stealthClothing.SetEnabled(ninja.Suit.Value, uid, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment greentext when emagging a door.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Roles;
|
||||
using Content.Server.Sticky.Events;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Ninja.Systems;
|
||||
@@ -14,7 +15,7 @@ namespace Content.Server.Ninja.Systems;
|
||||
/// <summary>
|
||||
/// Prevents planting a spider charge outside of its location and handles greentext.
|
||||
/// </summary>
|
||||
public sealed class SpiderChargeSystem : EntitySystem
|
||||
public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
|
||||
{
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
@@ -6,10 +6,11 @@ using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Ninja.Systems;
|
||||
|
||||
@@ -20,12 +21,12 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
|
||||
{
|
||||
[Dependency] private readonly BatterySystem _battery = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -38,16 +39,18 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
|
||||
/// <summary>
|
||||
/// Stun clicked mobs on the whitelist, if there is enough power.
|
||||
/// </summary>
|
||||
private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, BeforeInteractHandEvent args)
|
||||
private void OnBeforeInteractHand(Entity<StunProviderComponent> ent, ref BeforeInteractHandEvent args)
|
||||
{
|
||||
// TODO: generic check
|
||||
var (uid, comp) = ent;
|
||||
if (args.Handled || comp.BatteryUid == null || !_gloves.AbilityCheck(uid, args, out var target))
|
||||
return;
|
||||
|
||||
if (target == uid || _whitelistSystem.IsWhitelistFail(comp.Whitelist, target))
|
||||
if (target == uid || _whitelist.IsWhitelistFail(comp.Whitelist, target))
|
||||
return;
|
||||
|
||||
if (_timing.CurTime < comp.NextStun)
|
||||
var useDelay = EnsureComp<UseDelayComponent>(uid);
|
||||
if (_useDelay.IsDelayed((uid, useDelay), id: comp.DelayId))
|
||||
return;
|
||||
|
||||
// take charge from battery
|
||||
@@ -63,13 +66,14 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
|
||||
_stun.TryParalyze(target, comp.StunTime, refresh: false);
|
||||
|
||||
// short cooldown to prevent instant stunlocking
|
||||
comp.NextStun = _timing.CurTime + comp.Cooldown;
|
||||
_useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);
|
||||
_useDelay.TryResetDelay((uid, useDelay), id: comp.DelayId);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBatteryChanged(EntityUid uid, StunProviderComponent comp, ref NinjaBatteryChangedEvent args)
|
||||
private void OnBatteryChanged(Entity<StunProviderComponent> ent, ref NinjaBatteryChangedEvent args)
|
||||
{
|
||||
SetBattery(uid, args.Battery, comp);
|
||||
SetBattery((ent, ent.Comp), args.Battery);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
@@ -48,6 +49,7 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly StomachSystem _stomach = default!;
|
||||
@@ -156,6 +158,9 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
_appearance.SetData(uid, FoodVisuals.Visual, drainAvailable.Float(), appearance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to feed the drink item to the target entity
|
||||
/// </summary>
|
||||
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
|
||||
{
|
||||
if (!HasComp<BodyComponent>(target))
|
||||
@@ -210,9 +215,9 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
BreakOnDamage = true,
|
||||
MovementThreshold = 0.01f,
|
||||
DistanceThreshold = 1.0f,
|
||||
// Mice and the like can eat without hands.
|
||||
// TODO maybe set this based on some CanEatWithoutHands event or component?
|
||||
NeedHand = forceDrink,
|
||||
// do-after will stop if item is dropped when trying to feed someone else
|
||||
// or if the item started out in the user's own hands
|
||||
NeedHand = forceDrink || _hands.IsHolding(user, item),
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(doAfterEventArgs);
|
||||
|
||||
@@ -99,6 +99,9 @@ public sealed class FoodSystem : EntitySystem
|
||||
args.Handled = result.Handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to feed the food item to the target entity
|
||||
/// </summary>
|
||||
public (bool Success, bool Handled) TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
|
||||
{
|
||||
//Suppresses eating yourself and alive mobs
|
||||
@@ -189,9 +192,9 @@ public sealed class FoodSystem : EntitySystem
|
||||
BreakOnDamage = true,
|
||||
MovementThreshold = 0.01f,
|
||||
DistanceThreshold = MaxFeedDistance,
|
||||
// Mice and the like can eat without hands.
|
||||
// TODO maybe set this based on some CanEatWithoutHands event or component?
|
||||
NeedHand = forceFeed,
|
||||
// do-after will stop if item is dropped when trying to feed someone else
|
||||
// or if the item started out in the user's own hands
|
||||
NeedHand = forceFeed || _hands.IsHolding(user, food),
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(doAfterArgs);
|
||||
|
||||
@@ -35,20 +35,6 @@ public sealed class CodeConditionSystem : EntitySystem
|
||||
return ent.Comp.Completed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a mob's objective with a certain prototype is completed.
|
||||
/// </summary>
|
||||
public bool IsCompleted(Entity<MindContainerComponent?> mob, string prototype)
|
||||
{
|
||||
if (_mind.GetMind(mob, mob.Comp) is not {} mindId)
|
||||
return false;
|
||||
|
||||
if (!_mind.TryFindObjective(mindId, prototype, out var obj))
|
||||
return false;
|
||||
|
||||
return IsCompleted(obj.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an objective's completed field.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Stacks;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
@@ -105,7 +106,7 @@ public sealed class StealConditionSystem : EntitySystem
|
||||
if (pulledEntity != null)
|
||||
{
|
||||
// check if this is the item
|
||||
if (CheckStealTarget(pulledEntity.Value, condition)) count++;
|
||||
count += CheckStealTarget(pulledEntity.Value, condition);
|
||||
|
||||
//we don't check the inventories of sentient entity
|
||||
if (!HasComp<MindContainerComponent>(pulledEntity))
|
||||
@@ -126,7 +127,7 @@ public sealed class StealConditionSystem : EntitySystem
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
// check if this is the item
|
||||
if (CheckStealTarget(entity, condition)) count++; //To Do: add support for stackable items
|
||||
count += CheckStealTarget(entity, condition);
|
||||
|
||||
// if it is a container check its contents
|
||||
if (_containerQuery.TryGetComponent(entity, out var containerManager))
|
||||
@@ -140,14 +141,14 @@ public sealed class StealConditionSystem : EntitySystem
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool CheckStealTarget(EntityUid entity, StealConditionComponent condition)
|
||||
private int CheckStealTarget(EntityUid entity, StealConditionComponent condition)
|
||||
{
|
||||
// check if this is the target
|
||||
if (!TryComp<StealTargetComponent>(entity, out var target))
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
if (target.StealGroup != condition.StealGroup)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
// check if needed target alive
|
||||
if (condition.CheckAlive)
|
||||
@@ -155,9 +156,10 @@ public sealed class StealConditionSystem : EntitySystem
|
||||
if (TryComp<MobStateComponent>(entity, out var state))
|
||||
{
|
||||
if (!_mobState.IsAlive(entity, state))
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return TryComp<StackComponent>(entity, out var stack) ? stack.Count : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Paper;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.Paper;
|
||||
|
||||
@@ -21,4 +20,7 @@ public sealed partial class PaperComponent : SharedPaperComponent
|
||||
/// </summary>
|
||||
[DataField("stampState")]
|
||||
public string? StampState { get; set; }
|
||||
|
||||
[DataField]
|
||||
public bool EditingDisabled = false;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,14 @@ namespace Content.Server.Paper
|
||||
var editable = paperComp.StampedBy.Count == 0 || _tagSystem.HasTag(args.Used, "WriteIgnoreStamps");
|
||||
if (_tagSystem.HasTag(args.Used, "Write") && editable)
|
||||
{
|
||||
if (paperComp.EditingDisabled)
|
||||
{
|
||||
var paperEditingDisabledMessage = Loc.GetString("paper-tamper-proof-modified-message");
|
||||
_popupSystem.PopupEntity(paperEditingDisabledMessage, uid, args.User);
|
||||
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
var writeEvent = new PaperWriteEvent(uid, args.User);
|
||||
RaiseLocalEvent(args.Used, ref writeEvent);
|
||||
|
||||
|
||||
@@ -126,20 +126,18 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
var xform = Transform(uid);
|
||||
var mapId = xform.MapID;
|
||||
|
||||
if (mapId != MapId.Nullspace && TryComp(uid, out MapGridComponent? mapGrid))
|
||||
if (mapId != MapId.Nullspace && HasComp<MapGridComponent>(uid))
|
||||
{
|
||||
var setTiles = new List<(Vector2i Index, Tile tile)>();
|
||||
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
|
||||
foreach (var grid in _mapManager.GetAllGrids(mapId))
|
||||
{
|
||||
var gridUid = grid.Owner;
|
||||
|
||||
if (!_fixturesQuery.TryGetComponent(gridUid, out var fixtures))
|
||||
if (!_fixturesQuery.TryGetComponent(grid.Owner, out var fixtures))
|
||||
continue;
|
||||
|
||||
// Don't want shuttles flying around now do we.
|
||||
_shuttles.Disable(gridUid);
|
||||
var pTransform = _physics.GetPhysicsTransform(gridUid);
|
||||
_shuttles.Disable(grid.Owner);
|
||||
var pTransform = _physics.GetPhysicsTransform(grid.Owner);
|
||||
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
|
||||
@@ -10,22 +11,20 @@ public sealed partial class PowerCellSystem
|
||||
* Handles PowerCellDraw
|
||||
*/
|
||||
|
||||
private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent>();
|
||||
var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent, ItemToggleComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var slot))
|
||||
while (query.MoveNext(out var uid, out var comp, out var slot, out var toggle))
|
||||
{
|
||||
if (!comp.Drawing)
|
||||
if (!comp.Enabled || !toggle.Activated)
|
||||
continue;
|
||||
|
||||
if (Timing.CurTime < comp.NextUpdateTime)
|
||||
continue;
|
||||
|
||||
comp.NextUpdateTime += Delay;
|
||||
comp.NextUpdateTime += comp.Delay;
|
||||
|
||||
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
|
||||
continue;
|
||||
@@ -33,7 +32,8 @@ public sealed partial class PowerCellSystem
|
||||
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
|
||||
continue;
|
||||
|
||||
comp.Drawing = false;
|
||||
Toggle.TryDeactivate((uid, toggle));
|
||||
|
||||
var ev = new PowerCellSlotEmptyEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
@@ -42,26 +42,9 @@ public sealed partial class PowerCellSystem
|
||||
private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
|
||||
{
|
||||
// Update the bools for client prediction.
|
||||
bool canDraw;
|
||||
bool canUse;
|
||||
var canUse = component.UseRate <= 0f || args.Charge > component.UseRate;
|
||||
|
||||
if (component.UseRate > 0f)
|
||||
{
|
||||
canUse = args.Charge > component.UseRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
canUse = true;
|
||||
}
|
||||
|
||||
if (component.DrawRate > 0f)
|
||||
{
|
||||
canDraw = args.Charge > 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
canDraw = true;
|
||||
}
|
||||
var canDraw = component.DrawRate <= 0f || args.Charge > 0f;
|
||||
|
||||
if (canUse != component.CanUse || canDraw != component.CanDraw)
|
||||
{
|
||||
@@ -76,6 +59,9 @@ public sealed partial class PowerCellSystem
|
||||
var canDraw = !args.Ejected && HasCharge(uid, float.MinValue);
|
||||
var canUse = !args.Ejected && HasActivatableCharge(uid, component);
|
||||
|
||||
if (!canDraw)
|
||||
Toggle.TryDeactivate(uid);
|
||||
|
||||
if (canUse != component.CanUse || canDraw != component.CanDraw)
|
||||
{
|
||||
component.CanDraw = canDraw;
|
||||
|
||||
@@ -39,8 +39,8 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
|
||||
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
|
||||
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
|
||||
|
||||
// funny
|
||||
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
|
||||
// funny
|
||||
SubscribeLocalEvent<PowerCellSlotComponent, BeingMicrowavedEvent>(OnSlotMicrowaved);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
@@ -6,13 +7,10 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Speech;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Radio.EntitySystems;
|
||||
@@ -28,7 +26,6 @@ public sealed class RadioDeviceSystem : EntitySystem
|
||||
[Dependency] private readonly RadioSystem _radio = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
// Used to prevent a shitter from using a bunch of radios to spam chat.
|
||||
private HashSet<(string, EntityUid)> _recentlySent = new();
|
||||
@@ -47,7 +44,7 @@ public sealed class RadioDeviceSystem : EntitySystem
|
||||
SubscribeLocalEvent<RadioSpeakerComponent, ActivateInWorldEvent>(OnActivateSpeaker);
|
||||
SubscribeLocalEvent<RadioSpeakerComponent, RadioReceiveEvent>(OnReceiveRadio);
|
||||
|
||||
SubscribeLocalEvent<IntercomComponent, BeforeActivatableUIOpenEvent>(OnBeforeIntercomUiOpen);
|
||||
SubscribeLocalEvent<IntercomComponent, EncryptionChannelsChangedEvent>(OnIntercomEncryptionChannelsChanged);
|
||||
SubscribeLocalEvent<IntercomComponent, ToggleIntercomMicMessage>(OnToggleIntercomMic);
|
||||
SubscribeLocalEvent<IntercomComponent, ToggleIntercomSpeakerMessage>(OnToggleIntercomSpeaker);
|
||||
SubscribeLocalEvent<IntercomComponent, SelectIntercomChannelMessage>(OnSelectIntercomChannel);
|
||||
@@ -150,18 +147,18 @@ public sealed class RadioDeviceSystem : EntitySystem
|
||||
SetSpeakerEnabled(uid, user, !component.Enabled, quiet, component);
|
||||
}
|
||||
|
||||
public void SetSpeakerEnabled(EntityUid uid, EntityUid user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null)
|
||||
public void SetSpeakerEnabled(EntityUid uid, EntityUid? user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.Enabled = enabled;
|
||||
|
||||
if (!quiet)
|
||||
if (!quiet && user != null)
|
||||
{
|
||||
var state = Loc.GetString(component.Enabled ? "handheld-radio-component-on-state" : "handheld-radio-component-off-state");
|
||||
var message = Loc.GetString("handheld-radio-component-on-use", ("radioState", state));
|
||||
_popup.PopupEntity(message, user, user);
|
||||
_popup.PopupEntity(message, user.Value, user.Value);
|
||||
}
|
||||
|
||||
_appearance.SetData(uid, RadioDeviceVisuals.Speaker, component.Enabled);
|
||||
@@ -213,61 +210,74 @@ public sealed class RadioDeviceSystem : EntitySystem
|
||||
var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource));
|
||||
RaiseLocalEvent(args.MessageSource, nameEv);
|
||||
|
||||
var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)),
|
||||
var name = Loc.GetString("speech-name-relay",
|
||||
("speaker", Name(uid)),
|
||||
("originalName", nameEv.Name));
|
||||
|
||||
// log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios
|
||||
_chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false);
|
||||
}
|
||||
|
||||
private void OnBeforeIntercomUiOpen(EntityUid uid, IntercomComponent component, BeforeActivatableUIOpenEvent args)
|
||||
private void OnIntercomEncryptionChannelsChanged(Entity<IntercomComponent> ent, ref EncryptionChannelsChangedEvent args)
|
||||
{
|
||||
UpdateIntercomUi(uid, component);
|
||||
ent.Comp.SupportedChannels = args.Component.Channels.Select(p => new ProtoId<RadioChannelPrototype>(p)).ToList();
|
||||
|
||||
var channel = args.Component.DefaultChannel;
|
||||
if (ent.Comp.CurrentChannel != null && ent.Comp.SupportedChannels.Contains(ent.Comp.CurrentChannel.Value))
|
||||
channel = ent.Comp.CurrentChannel;
|
||||
|
||||
SetIntercomChannel(ent, channel);
|
||||
}
|
||||
|
||||
private void OnToggleIntercomMic(EntityUid uid, IntercomComponent component, ToggleIntercomMicMessage args)
|
||||
private void OnToggleIntercomMic(Entity<IntercomComponent> ent, ref ToggleIntercomMicMessage args)
|
||||
{
|
||||
if (component.RequiresPower && !this.IsPowered(uid, EntityManager))
|
||||
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
|
||||
return;
|
||||
|
||||
SetMicrophoneEnabled(uid, args.Actor, args.Enabled, true);
|
||||
UpdateIntercomUi(uid, component);
|
||||
SetMicrophoneEnabled(ent, args.Actor, args.Enabled, true);
|
||||
ent.Comp.MicrophoneEnabled = args.Enabled;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnToggleIntercomSpeaker(EntityUid uid, IntercomComponent component, ToggleIntercomSpeakerMessage args)
|
||||
private void OnToggleIntercomSpeaker(Entity<IntercomComponent> ent, ref ToggleIntercomSpeakerMessage args)
|
||||
{
|
||||
if (component.RequiresPower && !this.IsPowered(uid, EntityManager))
|
||||
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
|
||||
return;
|
||||
|
||||
SetSpeakerEnabled(uid, args.Actor, args.Enabled, true);
|
||||
UpdateIntercomUi(uid, component);
|
||||
SetSpeakerEnabled(ent, args.Actor, args.Enabled, true);
|
||||
ent.Comp.SpeakerEnabled = args.Enabled;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnSelectIntercomChannel(EntityUid uid, IntercomComponent component, SelectIntercomChannelMessage args)
|
||||
private void OnSelectIntercomChannel(Entity<IntercomComponent> ent, ref SelectIntercomChannelMessage args)
|
||||
{
|
||||
if (component.RequiresPower && !this.IsPowered(uid, EntityManager))
|
||||
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
|
||||
return;
|
||||
|
||||
if (!_protoMan.TryIndex<RadioChannelPrototype>(args.Channel, out _) || !component.SupportedChannels.Contains(args.Channel))
|
||||
if (!_protoMan.HasIndex<RadioChannelPrototype>(args.Channel) || !ent.Comp.SupportedChannels.Contains(args.Channel))
|
||||
return;
|
||||
|
||||
if (TryComp<RadioMicrophoneComponent>(uid, out var mic))
|
||||
mic.BroadcastChannel = args.Channel;
|
||||
if (TryComp<RadioSpeakerComponent>(uid, out var speaker))
|
||||
speaker.Channels = new(){ args.Channel };
|
||||
UpdateIntercomUi(uid, component);
|
||||
SetIntercomChannel(ent, args.Channel);
|
||||
}
|
||||
|
||||
private void UpdateIntercomUi(EntityUid uid, IntercomComponent component)
|
||||
private void SetIntercomChannel(Entity<IntercomComponent> ent, ProtoId<RadioChannelPrototype>? channel)
|
||||
{
|
||||
var micComp = CompOrNull<RadioMicrophoneComponent>(uid);
|
||||
var speakerComp = CompOrNull<RadioSpeakerComponent>(uid);
|
||||
ent.Comp.CurrentChannel = channel;
|
||||
|
||||
var micEnabled = micComp?.Enabled ?? false;
|
||||
var speakerEnabled = speakerComp?.Enabled ?? false;
|
||||
var availableChannels = component.SupportedChannels;
|
||||
var selectedChannel = micComp?.BroadcastChannel ?? SharedChatSystem.CommonChannel;
|
||||
var state = new IntercomBoundUIState(micEnabled, speakerEnabled, availableChannels, selectedChannel);
|
||||
_ui.SetUiState(uid, IntercomUiKey.Key, state);
|
||||
if (channel == null)
|
||||
{
|
||||
SetSpeakerEnabled(ent, null, false);
|
||||
SetMicrophoneEnabled(ent, null, false);
|
||||
ent.Comp.MicrophoneEnabled = false;
|
||||
ent.Comp.SpeakerEnabled = false;
|
||||
Dirty(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<RadioMicrophoneComponent>(ent, out var mic))
|
||||
mic.BroadcastChannel = channel;
|
||||
if (TryComp<RadioSpeakerComponent>(ent, out var speaker))
|
||||
speaker.Channels = new(){ channel };
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,15 @@ public sealed class RadioSystem : EntitySystem
|
||||
// set used to prevent radio feedback loops.
|
||||
private readonly HashSet<string> _messages = new();
|
||||
|
||||
private EntityQuery<TelecomExemptComponent> _exemptQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
|
||||
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
|
||||
|
||||
_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
|
||||
}
|
||||
|
||||
private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args)
|
||||
@@ -121,9 +125,8 @@ public sealed class RadioSystem : EntitySystem
|
||||
|
||||
var sourceMapId = Transform(radioSource).MapID;
|
||||
var hasActiveServer = HasActiveServer(sourceMapId, channel.ID);
|
||||
var hasMicro = HasComp<RadioMicrophoneComponent>(radioSource);
|
||||
var sourceServerExempt = _exemptQuery.HasComp(radioSource);
|
||||
|
||||
var speakerQuery = GetEntityQuery<RadioSpeakerComponent>();
|
||||
var radioQuery = EntityQueryEnumerator<ActiveRadioComponent, TransformComponent>();
|
||||
while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform))
|
||||
{
|
||||
@@ -138,7 +141,7 @@ public sealed class RadioSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
// don't need telecom server for long range channels or handheld radios and intercoms
|
||||
var needServer = !channel.LongRange && (!hasMicro || !speakerQuery.HasComponent(receiver));
|
||||
var needServer = !channel.LongRange && !sourceServerExempt;
|
||||
if (needServer && !hasActiveServer)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -103,8 +104,10 @@ public sealed partial class SalvageSystem
|
||||
|
||||
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", (component.EndTime - _timing.CurTime).Minutes)));
|
||||
|
||||
var directionLocalization = ContentLocalizationManager.FormatDirection(component.DungeonLocation.GetDir()).ToLower();
|
||||
|
||||
if (component.DungeonLocation != Vector2.Zero)
|
||||
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir())));
|
||||
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", directionLocalization)));
|
||||
|
||||
component.Stage = ExpeditionStage.Running;
|
||||
Dirty(args.MapUid, component);
|
||||
|
||||
@@ -9,21 +9,19 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Sandbox.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
public sealed class ColorNetworkCommand : IConsoleCommand
|
||||
public sealed class ColorNetworkCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "colornetwork";
|
||||
public string Description => Loc.GetString("color-network-command-description");
|
||||
public string Help => Loc.GetString("color-network-command-help-text", ("command",Command));
|
||||
public override string Command => "colornetwork";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var sandboxManager = _entManager.System<SandboxSystem>();
|
||||
var adminManager = IoCManager.Resolve<IAdminManager>();
|
||||
if (shell.IsClient && (!sandboxManager.IsSandboxEnabled && !adminManager.HasAdminFlag(shell.Player!, AdminFlags.Mapping)))
|
||||
{
|
||||
shell.WriteError("You are not currently able to use mapping commands.");
|
||||
shell.WriteError(Loc.GetString("cmd-colornetwork-no-access"));
|
||||
}
|
||||
|
||||
if (args.Length != 3)
|
||||
|
||||
@@ -18,6 +18,7 @@ using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Localizations;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
@@ -67,8 +68,9 @@ public sealed class ThrusterSystem : EntitySystem
|
||||
EntityManager.TryGetComponent(uid, out TransformComponent? xform) &&
|
||||
xform.Anchored)
|
||||
{
|
||||
var nozzleLocalization = ContentLocalizationManager.FormatDirection(xform.LocalRotation.Opposite().ToWorldVec().GetDir()).ToLower();
|
||||
var nozzleDir = Loc.GetString("thruster-comp-nozzle-direction",
|
||||
("direction", xform.LocalRotation.Opposite().ToWorldVec().GetDir().ToString().ToLowerInvariant()));
|
||||
("direction", nozzleLocalization));
|
||||
|
||||
args.PushMarkup(nozzleDir);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed partial class BorgSystem
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer ||
|
||||
!chassisComp.Activated)
|
||||
!Toggle.IsActivated(chassis))
|
||||
return;
|
||||
|
||||
if (!_powerCell.HasDrawCharge(uid))
|
||||
@@ -143,6 +143,7 @@ public sealed partial class BorgSystem
|
||||
var ev = new BorgModuleSelectedEvent(chassis);
|
||||
RaiseLocalEvent(moduleUid, ref ev);
|
||||
chassisComp.SelectedModule = moduleUid;
|
||||
Dirty(chassis, chassisComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,6 +163,7 @@ public sealed partial class BorgSystem
|
||||
var ev = new BorgModuleUnselectedEvent(chassis);
|
||||
RaiseLocalEvent(chassisComp.SelectedModule.Value, ref ev);
|
||||
chassisComp.SelectedModule = null;
|
||||
Dirty(chassis, chassisComp);
|
||||
}
|
||||
|
||||
private void OnItemModuleSelected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleSelectedEvent args)
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
@@ -73,6 +74,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ItemToggledEvent>(OnToggled);
|
||||
|
||||
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
|
||||
SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
|
||||
@@ -173,11 +175,11 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
if (args.NewMobState == MobState.Alive)
|
||||
{
|
||||
if (_mind.TryGetMind(uid, out _, out _))
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, true);
|
||||
_powerCell.SetDrawEnabled(uid, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, false);
|
||||
_powerCell.SetDrawEnabled(uid, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,24 +187,10 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
UpdateBatteryAlert((uid, component));
|
||||
|
||||
if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
|
||||
return;
|
||||
|
||||
// if we eject the battery or run out of charge, then disable
|
||||
if (args.Ejected || !_powerCell.HasDrawCharge(uid))
|
||||
{
|
||||
DisableBorgAbilities(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
|
||||
if (_powerCell.HasDrawCharge(uid, draw))
|
||||
if (_powerCell.HasDrawCharge(uid))
|
||||
{
|
||||
// only reenable the powerdraw if a player has the role.
|
||||
if (!draw.Drawing && _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(uid))
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, true);
|
||||
|
||||
EnableBorgAbilities(uid, component);
|
||||
Toggle.TryActivate(uid);
|
||||
}
|
||||
|
||||
UpdateUI(uid, component);
|
||||
@@ -210,7 +198,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
|
||||
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
|
||||
{
|
||||
DisableBorgAbilities(uid, component);
|
||||
Toggle.TryDeactivate(uid);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
@@ -219,6 +207,23 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
args.Dead = true;
|
||||
}
|
||||
|
||||
private void OnToggled(Entity<BorgChassisComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
if (args.Activated)
|
||||
InstallAllModules(uid, comp);
|
||||
else
|
||||
DisableAllModules(uid, comp);
|
||||
|
||||
// only enable the powerdraw if there is a player in the chassis
|
||||
var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
|
||||
_powerCell.SetDrawEnabled(uid, drawing);
|
||||
|
||||
UpdateUI(uid, comp);
|
||||
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
|
||||
@@ -271,44 +276,14 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
_alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the borg, enabling all of its modules.
|
||||
/// </summary>
|
||||
public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component, PowerCellDrawComponent? powerCell = null)
|
||||
{
|
||||
if (component.Activated)
|
||||
return;
|
||||
|
||||
component.Activated = true;
|
||||
InstallAllModules(uid, component);
|
||||
Dirty(uid, component);
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the borg, disabling all of its modules and decreasing its speed.
|
||||
/// </summary>
|
||||
public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
if (!component.Activated)
|
||||
return;
|
||||
|
||||
component.Activated = false;
|
||||
DisableAllModules(uid, component);
|
||||
Dirty(uid, component);
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates a borg when a player occupies it
|
||||
/// </summary>
|
||||
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, true);
|
||||
_access.SetAccessEnabled(uid, true);
|
||||
Toggle.TryActivate(uid);
|
||||
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -317,10 +292,8 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
|
||||
_powerCell.SetPowerCellDrawEnabled(uid, false);
|
||||
_access.SetAccessEnabled(uid, false);
|
||||
Toggle.TryDeactivate(uid);
|
||||
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class ContainmentFieldSystem : EntitySystem
|
||||
var fieldDir = Transform(uid).WorldPosition;
|
||||
var playerDir = Transform(otherBody).WorldPosition;
|
||||
|
||||
_throwing.TryThrow(otherBody, playerDir-fieldDir, strength: component.ThrowForce);
|
||||
_throwing.TryThrow(otherBody, playerDir-fieldDir, baseThrowSpeed: component.ThrowForce);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Content.Server.Speech.EntitySystems
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private static readonly IReadOnlyList<string> Faces = new List<string>{
|
||||
" (・`ω´・)", " ;;w;;", " owo", " UwU", " >w<", " ^w^"
|
||||
" (•`ω´•)", " ;;w;;", " owo", " UwU", " >w<", " ^w^"
|
||||
}.AsReadOnly();
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, string> SpecialWords = new Dictionary<string, string>()
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Spreader;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Collections;
|
||||
@@ -175,11 +176,12 @@ public sealed class SpreaderSystem : EntitySystem
|
||||
/// </summary>
|
||||
public void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId<EdgeSpreaderPrototype> prototype, out ValueList<(MapGridComponent, TileRef)> freeTiles, out ValueList<Vector2i> occupiedTiles, out ValueList<EntityUid> neighbors)
|
||||
{
|
||||
// TODO remove occupiedTiles -- its currently unused and just slows this method down.
|
||||
DebugTools.Assert(_prototype.HasIndex(prototype));
|
||||
freeTiles = [];
|
||||
occupiedTiles = [];
|
||||
neighbors = [];
|
||||
// TODO remove occupiedTiles -- its currently unused and just slows this method down.
|
||||
if (!_prototype.TryIndex(prototype, out var spreaderPrototype))
|
||||
return;
|
||||
|
||||
if (!TryComp<MapGridComponent>(comp.GridUid, out var grid))
|
||||
return;
|
||||
@@ -244,6 +246,9 @@ public sealed class SpreaderSystem : EntitySystem
|
||||
if (!_map.TryGetTileRef(neighborEnt, neighborGrid, neighborPos, out var tileRef) || tileRef.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (spreaderPrototype.PreventSpreadOnSpaced && tileRef.Tile.IsSpace())
|
||||
continue;
|
||||
|
||||
var directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
|
||||
var occupied = false;
|
||||
|
||||
@@ -335,4 +340,12 @@ public sealed class SpreaderSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RequiresFloorToSpread(EntProtoId<EdgeSpreaderComponent> spreader)
|
||||
{
|
||||
if (!_prototype.Index(spreader).TryGetComponent<EdgeSpreaderComponent>(out var spreaderComp, EntityManager.ComponentFactory))
|
||||
return false;
|
||||
|
||||
return _prototype.Index(spreaderComp.Id).PreventSpreadOnSpaced;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration component for the Space Ninja antag.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(NinjaSpawnRule))]
|
||||
public sealed partial class NinjaSpawnRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Distance that the ninja spawns from the station's half AABB radius
|
||||
/// </summary>
|
||||
[DataField("spawnDistance")]
|
||||
public float SpawnDistance = 20f;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for spawning antags in space around a station.
|
||||
/// Requires <c>AntagSelectionComponent</c>.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SpaceSpawnRule))]
|
||||
public sealed partial class SpaceSpawnRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Distance that the entity spawns from the station's half AABB radius
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SpawnDistance = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Location that was picked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public MapCoordinates? Coords;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Ninja.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
@@ -9,18 +9,28 @@ using Robust.Shared.Map.Components;
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event for spawning a Space Ninja mid-game.
|
||||
/// Station event component for spawning this rules antags in space around a station.
|
||||
/// </summary>
|
||||
public sealed class NinjaSpawnRule : StationEventSystem<NinjaSpawnRuleComponent>
|
||||
public sealed class SpaceSpawnRule : StationEventSystem<SpaceSpawnRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, NinjaSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Started(uid, comp, gameRule, args);
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SpaceSpawnRuleComponent, AntagSelectLocationEvent>(OnSelectLocation);
|
||||
}
|
||||
|
||||
protected override void Added(EntityUid uid, SpaceSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, comp, gameRule, args);
|
||||
|
||||
if (!TryGetRandomStation(out var station))
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
var stationData = Comp<StationDataComponent>(station.Value);
|
||||
|
||||
@@ -28,22 +38,28 @@ public sealed class NinjaSpawnRule : StationEventSystem<NinjaSpawnRuleComponent>
|
||||
var gridUid = StationSystem.GetLargestGrid(stationData);
|
||||
if (gridUid == null || !TryComp<MapGridComponent>(gridUid, out var grid))
|
||||
{
|
||||
Sawmill.Warning("Chosen station has no grids, cannot spawn space ninja!");
|
||||
Sawmill.Warning("Chosen station has no grids, cannot pick location for {ToPrettyString(uid):rule}");
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
// figure out its AABB size and use that as a guide to how far ninja should be
|
||||
// figure out its AABB size and use that as a guide to how far the spawner should be
|
||||
var size = grid.LocalAABB.Size.Length() / 2;
|
||||
var distance = size + comp.SpawnDistance;
|
||||
var angle = RobustRandom.NextAngle();
|
||||
// position relative to station center
|
||||
var location = angle.ToVec() * distance;
|
||||
|
||||
// create the spawner, the ninja will appear when a ghost has picked the role
|
||||
// create the spawner!
|
||||
var xform = Transform(gridUid.Value);
|
||||
var position = _transform.GetWorldPosition(xform) + location;
|
||||
var coords = new MapCoordinates(position, xform.MapID);
|
||||
Sawmill.Info($"Creating ninja spawnpoint at {coords}");
|
||||
Spawn("SpawnPointGhostSpaceNinja", coords);
|
||||
comp.Coords = new MapCoordinates(position, xform.MapID);
|
||||
Sawmill.Info($"Picked location {comp.Coords} for {ToPrettyString(uid):rule}");
|
||||
}
|
||||
|
||||
private void OnSelectLocation(Entity<SpaceSpawnRuleComponent> ent, ref AntagSelectLocationEvent args)
|
||||
{
|
||||
if (ent.Comp.Coords is {} coords)
|
||||
args.Coordinates.Add(coords);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.Stunnable.Systems
|
||||
[Dependency] private readonly RiggableSystem _riggableSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly BatterySystem _battery = default!;
|
||||
[Dependency] private readonly SharedItemToggleSystem _itemToggle = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _itemToggle = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Weapons.Misc;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -8,6 +9,7 @@ namespace Content.Server.Weapons.Misc;
|
||||
public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
{
|
||||
[Dependency] private readonly PowerCellSystem _cell = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggle = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -36,12 +38,12 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
|
||||
{
|
||||
base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
|
||||
_cell.SetPowerCellDrawEnabled(gunUid, true);
|
||||
_toggle.TryActivate(gunUid);
|
||||
}
|
||||
|
||||
protected override void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
|
||||
{
|
||||
base.StopTether(gunUid, component, land, transfer);
|
||||
_cell.SetPowerCellDrawEnabled(gunUid, false);
|
||||
_toggle.TryDeactivate(gunUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Weapons.Ranged.Systems;
|
||||
|
||||
@@ -38,6 +39,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StaminaSystem _stamina = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
private const float DamagePitchVariation = 0.05f;
|
||||
public const float GunClumsyChance = 0.5f;
|
||||
@@ -204,17 +206,21 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
var result = rayCastResults[0];
|
||||
|
||||
// Checks if the laser should pass over unless targeted by its user
|
||||
foreach (var collide in rayCastResults)
|
||||
// Check if laser is shot from in a container
|
||||
if (!_container.IsEntityOrParentInContainer(lastUser))
|
||||
{
|
||||
if (collide.HitEntity != gun.Target &&
|
||||
CompOrNull<RequireProjectileTargetComponent>(collide.HitEntity)?.Active == true)
|
||||
// Checks if the laser should pass over unless targeted by its user
|
||||
foreach (var collide in rayCastResults)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (collide.HitEntity != gun.Target &&
|
||||
CompOrNull<RequireProjectileTargetComponent>(collide.HitEntity)?.Active == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = collide;
|
||||
break;
|
||||
result = collide;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var hit = result.HitEntity;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Server.Salvage;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
|
||||
|
||||
@@ -29,11 +30,11 @@ public sealed class ArtifactMagnetTriggerSystem : EntitySystem
|
||||
|
||||
_toActivate.Clear();
|
||||
|
||||
//assume that there's more instruments than artifacts
|
||||
var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent>();
|
||||
while (query.MoveNext(out _, out var magboot, out var magXform))
|
||||
//assume that there's more magboots than artifacts
|
||||
var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent, ItemToggleComponent>();
|
||||
while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
|
||||
{
|
||||
if (!magboot.On)
|
||||
if (!toggle.Activated)
|
||||
continue;
|
||||
|
||||
var artiQuery = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();
|
||||
|
||||
11
Content.Shared/Access/Components/AccessToggleComponent.cs
Normal file
11
Content.Shared/Access/Components/AccessToggleComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Content.Shared.Access.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Access.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles an access provider with <c>ItemToggle</c>.
|
||||
/// Requires <see cref="AccessComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(AccessToggleSystem))]
|
||||
public sealed partial class AccessToggleComponent : Component;
|
||||
21
Content.Shared/Access/Systems/AccessToggleSystem.cs
Normal file
21
Content.Shared/Access/Systems/AccessToggleSystem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
|
||||
namespace Content.Shared.Access.Systems;
|
||||
|
||||
public sealed class AccessToggleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAccessSystem _access = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AccessToggleComponent, ItemToggledEvent>(OnToggled);
|
||||
}
|
||||
|
||||
private void OnToggled(Entity<AccessToggleComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
_access.SetAccessEnabled(ent, args.Activated);
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ public sealed partial class AmeFuelContainerComponent : Component
|
||||
/// The amount of fuel in the container.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public int FuelAmount = 1000;
|
||||
public int FuelAmount = 500;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum fuel capacity of the container.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public int FuelCapacity = 1000;
|
||||
public int FuelCapacity = 500;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user