mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-14 19:29:57 +01:00
Merge remote-tracking branch 'upstream/master' into upstream-sync
# Conflicts: # Content.Server/Communications/CommunicationsConsoleSystem.cs # README.md # Resources/Prototypes/Entities/Mobs/Species/reptilian.yml # Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml # Resources/Prototypes/secret_weights.yml # Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian.rsi/equipped-INNERCLOTHING.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian.rsi/icon.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian.rsi/inhand-left.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian.rsi/inhand-right.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian.rsi/meta.json
This commit is contained in:
32
.github/workflows/update-credits.yml
vendored
Normal file
32
.github/workflows/update-credits.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Update Contrib and Patreons in credits
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * 0
|
||||
|
||||
jobs:
|
||||
get_credits:
|
||||
runs-on: ubuntu-latest
|
||||
# Hey there fork dev! If you like to include your own contributors in this then you can probably just change this to your own repo
|
||||
# Do this in dump_github_contributors.ps1 too into your own repo
|
||||
if: github.repository == 'space-wizards/space-station-14'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Get this week's Contributors
|
||||
shell: pwsh
|
||||
run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt
|
||||
|
||||
# TODO
|
||||
#- name: Get this week's Patreons
|
||||
# run: Tools/script2dumppatreons > Resources/Credits/Patrons.yml
|
||||
|
||||
- name: Commit new credit files
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: Update Credits
|
||||
commit_author: PJBot <pieterjan.briers+bot@gmail.com>
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -168,6 +168,7 @@ PublishScripts/
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
.nuget/
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Content.Client.Actions
|
||||
|
||||
public event Action<EntityUid>? OnActionAdded;
|
||||
public event Action<EntityUid>? OnActionRemoved;
|
||||
public event OnActionReplaced? ActionReplaced;
|
||||
public event Action? ActionsUpdated;
|
||||
public event Action<ActionsComponent>? LinkActions;
|
||||
public event Action? UnlinkActions;
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
using System;
|
||||
using Content.Shared.Chemistry;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Chemistry.Visualizers
|
||||
@@ -13,40 +6,40 @@ namespace Content.Client.Chemistry.Visualizers
|
||||
[RegisterComponent]
|
||||
public sealed partial class SolutionContainerVisualsComponent : Component
|
||||
{
|
||||
[DataField("maxFillLevels")]
|
||||
[DataField]
|
||||
public int MaxFillLevels = 0;
|
||||
[DataField("fillBaseName")]
|
||||
[DataField]
|
||||
public string? FillBaseName = null;
|
||||
[DataField("layer")]
|
||||
public SolutionContainerLayers FillLayer = SolutionContainerLayers.Fill;
|
||||
[DataField("baseLayer")]
|
||||
[DataField]
|
||||
public SolutionContainerLayers Layer = SolutionContainerLayers.Fill;
|
||||
[DataField]
|
||||
public SolutionContainerLayers BaseLayer = SolutionContainerLayers.Base;
|
||||
[DataField("overlayLayer")]
|
||||
[DataField]
|
||||
public SolutionContainerLayers OverlayLayer = SolutionContainerLayers.Overlay;
|
||||
[DataField("changeColor")]
|
||||
[DataField]
|
||||
public bool ChangeColor = true;
|
||||
[DataField("emptySpriteName")]
|
||||
[DataField]
|
||||
public string? EmptySpriteName = null;
|
||||
[DataField("emptySpriteColor")]
|
||||
[DataField]
|
||||
public Color EmptySpriteColor = Color.White;
|
||||
[DataField("metamorphic")]
|
||||
[DataField]
|
||||
public bool Metamorphic = false;
|
||||
[DataField("metamorphicDefaultSprite")]
|
||||
[DataField]
|
||||
public SpriteSpecifier? MetamorphicDefaultSprite;
|
||||
[DataField("metamorphicNameFull")]
|
||||
public string MetamorphicNameFull = "transformable-container-component-glass";
|
||||
[DataField]
|
||||
public LocId MetamorphicNameFull = "transformable-container-component-glass";
|
||||
|
||||
/// <summary>
|
||||
/// Which solution of the SolutionContainerManagerComponent to represent.
|
||||
/// If not set, will work as default.
|
||||
/// </summary>
|
||||
[DataField("solutionName")]
|
||||
[DataField]
|
||||
public string? SolutionName;
|
||||
|
||||
[DataField("initialName")]
|
||||
[DataField]
|
||||
public string InitialName = string.Empty;
|
||||
|
||||
[DataField("initialDescription")]
|
||||
[DataField]
|
||||
public string InitialDescription = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!args.Sprite.LayerMapTryGet(component.FillLayer, out var fillLayer))
|
||||
if (!args.Sprite.LayerMapTryGet(component.Layer, out var fillLayer))
|
||||
return;
|
||||
|
||||
// Currently some solution methods such as overflowing will try to update appearance with a
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Targeting;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
@@ -44,11 +43,6 @@ public sealed class CombatModeSystem : SharedCombatModeSystem
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void OnTargetingZoneChanged(TargetingZone obj)
|
||||
{
|
||||
EntityManager.RaisePredictiveEvent(new CombatModeSystemMessages.SetTargetZoneMessage(obj));
|
||||
}
|
||||
|
||||
public bool IsInCombatMode()
|
||||
{
|
||||
var entity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
@@ -65,12 +59,6 @@ public sealed class CombatModeSystem : SharedCombatModeSystem
|
||||
UpdateHud(entity);
|
||||
}
|
||||
|
||||
public override void SetActiveZone(EntityUid entity, TargetingZone zone, CombatModeComponent? component = null)
|
||||
{
|
||||
base.SetActiveZone(entity, zone, component);
|
||||
UpdateHud(entity);
|
||||
}
|
||||
|
||||
private void UpdateHud(EntityUid entity)
|
||||
{
|
||||
if (entity != _playerManager.LocalPlayer?.ControlledEntity || !Timing.IsFirstTimePredicted)
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Content.Client.Commands
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var containerSys = entityManager.System<SharedContainerSystem>();
|
||||
var organs = entityManager.EntityQuery<OrganComponent>(true);
|
||||
|
||||
foreach (var part in organs)
|
||||
@@ -27,7 +28,7 @@ namespace Content.Client.Commands
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
var tempParent = part.Owner;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
while (containerSys.TryGetContainingContainer(tempParent, out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
|
||||
30
Content.Client/Ghost/GhostToggleSelfVisibility.cs
Normal file
30
Content.Client/Ghost/GhostToggleSelfVisibility.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Shared.Ghost;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Ghost;
|
||||
|
||||
public sealed class GhostToggleSelfVisibility : IConsoleCommand
|
||||
{
|
||||
public string Command => "toggleselfghost";
|
||||
public string Description => "Toggles seeing your own ghost.";
|
||||
public string Help => "toggleselfghost";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var attachedEntity = shell.Player?.AttachedEntity;
|
||||
if (!attachedEntity.HasValue)
|
||||
return;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entityManager.HasComponent<GhostComponent>(attachedEntity))
|
||||
{
|
||||
shell.WriteError("Entity must be a ghost.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entityManager.TryGetComponent(attachedEntity, out SpriteComponent? spriteComponent))
|
||||
return;
|
||||
|
||||
spriteComponent.Visible = !spriteComponent.Visible;
|
||||
}
|
||||
}
|
||||
@@ -174,8 +174,9 @@ namespace Content.Client.Instruments.UI
|
||||
if (localPlayer.ControlledEntity == instrumentEnt)
|
||||
return true;
|
||||
|
||||
var container = _owner.Entities.System<SharedContainerSystem>();
|
||||
// If we're a handheld instrument, we might be in a container. Get it just in case.
|
||||
instrumentEnt.TryGetContainerMan(out var conMan);
|
||||
container.TryGetContainingContainer(instrumentEnt, out var conMan);
|
||||
|
||||
// If the instrument is handheld and we're not holding it, we return.
|
||||
if ((instrument.Handheld && (conMan == null || conMan.Owner != localPlayer.ControlledEntity)))
|
||||
|
||||
@@ -6,15 +6,17 @@ namespace Content.Client.Interactable
|
||||
{
|
||||
public sealed class InteractionSystem : SharedInteractionSystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!EntityManager.EntityExists(target))
|
||||
return false;
|
||||
|
||||
if (!target.TryGetContainer(out var container))
|
||||
if (!_container.TryGetContainingContainer(target, out var container))
|
||||
return false;
|
||||
|
||||
if (!TryComp(container.Owner, out StorageComponent? storage))
|
||||
if (!HasComp<StorageComponent>(container.Owner))
|
||||
return false;
|
||||
|
||||
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.DragDrop;
|
||||
namespace Content.Client.Interaction;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for implementing drag and drop interactions.
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.CombatMode;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Outline;
|
||||
@@ -7,7 +8,6 @@ using Content.Shared.DragDrop;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -20,15 +20,13 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.DragDrop;
|
||||
namespace Content.Client.Interaction;
|
||||
|
||||
/// <summary>
|
||||
/// Handles clientside drag and drop logic
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class DragDropSystem : SharedDragDropSystem
|
||||
{
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
@@ -45,8 +43,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
// how often to recheck possible targets (prevents calling expensive
|
||||
// check logic each update)
|
||||
private const float TargetRecheckInterval = 0.25f;
|
||||
@@ -110,7 +106,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill = Logger.GetSawmill("drag_drop");
|
||||
UpdatesOutsidePrediction = true;
|
||||
UpdatesAfter.Add(typeof(SharedEyeSystem));
|
||||
|
||||
@@ -263,7 +258,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Warning($"Unable to display drag shadow for {ToPrettyString(_draggedEntity.Value)} because it has no sprite component.");
|
||||
Log.Warning($"Unable to display drag shadow for {ToPrettyString(_draggedEntity.Value)} because it has no sprite component.");
|
||||
}
|
||||
|
||||
private bool UpdateDrag(float frameTime)
|
||||
@@ -392,7 +387,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
}
|
||||
|
||||
// tell the server about the drop attempt
|
||||
RaiseNetworkEvent(new DragDropRequestEvent(GetNetEntity(_draggedEntity.Value), GetNetEntity(entity)));
|
||||
RaisePredictiveEvent(new DragDropRequestEvent(GetNetEntity(_draggedEntity.Value), GetNetEntity(entity)));
|
||||
EndDrag();
|
||||
return true;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Client.Interactable;
|
||||
using Content.Shared.Climbing;
|
||||
using Content.Shared.DragDrop;
|
||||
|
||||
namespace Content.Client.Movement.Systems;
|
||||
|
||||
public sealed class ClimbSystem : SharedClimbSystem
|
||||
{
|
||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ClimbableComponent, CanDropTargetEvent>(OnCanDragDropOn);
|
||||
}
|
||||
|
||||
protected override void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
base.OnCanDragDropOn(uid, component, ref args);
|
||||
|
||||
if (!args.CanDrop)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
var target = uid;
|
||||
var dragged = args.Dragged;
|
||||
bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged;
|
||||
|
||||
args.CanDrop = _interactionSystem.InRangeUnobstructed(user, target, component.Range, predicate: Ignored)
|
||||
&& _interactionSystem.InRangeUnobstructed(user, dragged, component.Range, predicate: Ignored);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.PDA.Ringer;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.PDA.Ringer
|
||||
{
|
||||
@@ -29,9 +29,17 @@ namespace Content.Client.PDA.Ringer
|
||||
|
||||
_menu.SetRingerButton.OnPressed += _ =>
|
||||
{
|
||||
if (!TryGetRingtone(out var ringtone)) return;
|
||||
if (!TryGetRingtone(out var ringtone))
|
||||
return;
|
||||
|
||||
SendMessage(new RingerSetRingtoneMessage(ringtone));
|
||||
_menu.SetRingerButton.Disabled = true;
|
||||
|
||||
Timer.Spawn(333, () =>
|
||||
{
|
||||
if (_menu is { Disposed: false, SetRingerButton: { Disposed: false } ringer})
|
||||
ringer.Disabled = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,7 +82,7 @@ namespace Content.Client.PDA.Ringer
|
||||
|
||||
}
|
||||
|
||||
_menu.TestRingerButton.Visible = !msg.IsPlaying;
|
||||
_menu.TestRingerButton.Disabled = msg.IsPlaying;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -79,12 +79,14 @@
|
||||
Access="Public"
|
||||
Text="{Loc 'comp-ringer-ui-test-ringtone-button'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
VerticalAlignment="Center"
|
||||
StyleClasses="OpenRight" />
|
||||
<Button Name = "SetRingerButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'comp-ringer-ui-set-ringtone-button'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
VerticalAlignment="Center"
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -46,6 +46,8 @@ public sealed class ContentReplayPlaybackManager
|
||||
/// </summary>
|
||||
public Type? DefaultState;
|
||||
|
||||
public bool IsScreenshotMode = false;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
public void Initialize()
|
||||
|
||||
35
Content.Client/Replay/ReplayToggleScreenshotModeCommand.cs
Normal file
35
Content.Client/Replay/ReplayToggleScreenshotModeCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Content.Client.UserInterface.Systems.Chat;
|
||||
using Content.Shared.Chat;
|
||||
using Robust.Client.Replays.Commands;
|
||||
using Robust.Client.Replays.UI;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Replay;
|
||||
|
||||
public sealed class ReplayToggleScreenshotModeCommand : BaseReplayCommand
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly ContentReplayPlaybackManager _replayManager = default!;
|
||||
|
||||
public override string Command => "replay_toggle_screenshot_mode";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var screen = _userInterfaceManager.ActiveScreen;
|
||||
if (screen == null)
|
||||
return;
|
||||
|
||||
_replayManager.IsScreenshotMode = !_replayManager.IsScreenshotMode;
|
||||
|
||||
var showReplayWidget = _replayManager.IsScreenshotMode;
|
||||
screen.ShowWidget<ReplayControlWidget>(showReplayWidget);
|
||||
|
||||
foreach (var chatBox in _userInterfaceManager.GetUIController<ChatUIController>().Chats)
|
||||
{
|
||||
chatBox.ChatInput.Visible = !showReplayWidget;
|
||||
if (!showReplayWidget)
|
||||
chatBox.ChatInput.ChannelSelector.Select(ChatSelectChannel.Local);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace Content.Client.Replay.UI;
|
||||
[Virtual]
|
||||
public class ReplaySpectateEntityState : GameplayState
|
||||
{
|
||||
[Dependency] private readonly ContentReplayPlaybackManager _replayManager = default!;
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
@@ -21,11 +23,13 @@ public class ReplaySpectateEntityState : GameplayState
|
||||
return;
|
||||
|
||||
screen.ShowWidget<GameTopMenuBar>(false);
|
||||
SetAnchorAndMarginPreset(screen.GetOrAddWidget<ReplayControlWidget>(), LayoutPreset.TopLeft, margin: 10);
|
||||
var replayWidget = screen.GetOrAddWidget<ReplayControlWidget>();
|
||||
SetAnchorAndMarginPreset(replayWidget, LayoutPreset.TopLeft, margin: 10);
|
||||
replayWidget.Visible = !_replayManager.IsScreenshotMode;
|
||||
|
||||
foreach (var chatbox in UserInterfaceManager.GetUIController<ChatUIController>().Chats)
|
||||
{
|
||||
chatbox.ChatInput.Visible = false;
|
||||
chatbox.ChatInput.Visible = _replayManager.IsScreenshotMode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Client.ContextMenu.UI;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.PDA;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Targeting.UI;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Controls.FancyTree;
|
||||
using Content.Client.Verbs.UI;
|
||||
@@ -1148,29 +1147,6 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty(Label.StylePropertyFont, notoSansDisplayBold14),
|
||||
}),
|
||||
|
||||
// Targeting doll
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {TargetingDoll.StyleClassTargetDollZone}, null,
|
||||
new[] {TextureButton.StylePseudoClassNormal}), new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorDefault),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {TargetingDoll.StyleClassTargetDollZone}, null,
|
||||
new[] {TextureButton.StylePseudoClassHover}), new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorHovered),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {TargetingDoll.StyleClassTargetDollZone}, null,
|
||||
new[] {TextureButton.StylePseudoClassPressed}), new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorPressed),
|
||||
}),
|
||||
|
||||
// NanoHeading
|
||||
|
||||
new StyleRule(
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<targeting:TargetingDoll xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:targeting="clr-namespace:Content.Client.Targeting.UI"
|
||||
Orientation="Vertical">
|
||||
<TextureButton Name = "ButtonHigh" TexturePath="/Textures/Interface/target-doll-high.svg.96dpi.png" HorizontalAlignment="Center" StyleIdentifier="target-doll-zone"/>
|
||||
<TextureButton Name = "ButtonMedium" TexturePath="/Textures/Interface/target-doll-middle.svg.96dpi.png" HorizontalAlignment="Center" StyleIdentifier="target-doll-zone"/>
|
||||
<TextureButton Name = "ButtonLow" TexturePath="/Textures/Interface/target-doll-low.svg.96dpi.png" HorizontalAlignment="Center" StyleIdentifier="target-doll-zone"/>
|
||||
</targeting:TargetingDoll>
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Shared.Targeting;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Targeting.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TargetingDoll : BoxContainer
|
||||
{
|
||||
public static readonly string StyleClassTargetDollZone = "target-doll-zone";
|
||||
|
||||
|
||||
private TargetingZone _activeZone = TargetingZone.Middle;
|
||||
|
||||
public event Action<TargetingZone>? OnZoneChanged;
|
||||
public TargetingDoll()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public TargetingZone ActiveZone
|
||||
{
|
||||
get => _activeZone;
|
||||
set
|
||||
{
|
||||
if (_activeZone == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_activeZone = value;
|
||||
OnZoneChanged?.Invoke(value);
|
||||
|
||||
UpdateButtons();
|
||||
}
|
||||
}
|
||||
private void UpdateButtons()
|
||||
{
|
||||
ButtonHigh.Pressed = _activeZone == TargetingZone.High;
|
||||
ButtonMedium.Pressed = _activeZone == TargetingZone.Middle;
|
||||
ButtonLow.Pressed = _activeZone == TargetingZone.Low;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
@@ -10,8 +8,6 @@ namespace Content.Client.UserInterface.Controls;
|
||||
/// </summary>
|
||||
public sealed class RecordedSplitContainer : SplitContainer
|
||||
{
|
||||
public Action<Vector2, Vector2>? OnSplitResizeFinish;
|
||||
|
||||
public double? DesiredSplitCenter;
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
@@ -30,24 +26,4 @@ public sealed class RecordedSplitContainer : SplitContainer
|
||||
|
||||
return base.ArrangeOverride(finalSize);
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChildCount != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var first = GetChild(0);
|
||||
var second = GetChild(1);
|
||||
|
||||
OnSplitResizeFinish?.Invoke(first.Size, second.Size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed partial class SeparatedChatGameScreen : InGameScreen
|
||||
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
|
||||
SetAnchorAndMarginPreset(Alerts, LayoutPreset.CenterRight, margin: 10);
|
||||
|
||||
ScreenContainer.OnSplitResizeFinish += (first, second) =>
|
||||
ScreenContainer.OnSplitResizeFinished += () =>
|
||||
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Construction;
|
||||
using Content.Client.DragDrop;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Hands;
|
||||
using Content.Client.Interaction;
|
||||
using Content.Client.Outline;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Actions.Controls;
|
||||
@@ -42,6 +42,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
[Dependency] private readonly IOverlayManager _overlays = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
[UISystemDependency] private readonly ActionsSystem? _actionsSystem = default;
|
||||
[UISystemDependency] private readonly InteractionOutlineSystem? _interactionOutline = default;
|
||||
@@ -113,12 +114,11 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
_actionsSystem.OnActionAdded += OnActionAdded;
|
||||
_actionsSystem.OnActionRemoved += OnActionRemoved;
|
||||
_actionsSystem.ActionReplaced += OnActionReplaced;
|
||||
_actionsSystem.ActionsUpdated += OnActionsUpdated;
|
||||
}
|
||||
|
||||
UpdateFilterLabel();
|
||||
SearchAndDisplay();
|
||||
QueueWindowUpdate();
|
||||
|
||||
_dragShadow.Orphan();
|
||||
UIManager.PopupRoot.AddChild(_dragShadow);
|
||||
@@ -307,6 +307,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
if (ActionButton != null)
|
||||
ActionButton.Pressed = true;
|
||||
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnWindowClosed()
|
||||
@@ -321,7 +323,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
_actionsSystem.OnActionAdded -= OnActionAdded;
|
||||
_actionsSystem.OnActionRemoved -= OnActionRemoved;
|
||||
_actionsSystem.ActionReplaced -= OnActionReplaced;
|
||||
_actionsSystem.ActionsUpdated -= OnActionsUpdated;
|
||||
}
|
||||
|
||||
@@ -345,6 +346,9 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void ChangePage(int index)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
var lastPage = _pages.Count - 1;
|
||||
if (index < 0)
|
||||
{
|
||||
@@ -357,7 +361,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
_currentPageIndex = index;
|
||||
var page = _pages[_currentPageIndex];
|
||||
_container?.SetActionData(page);
|
||||
_container?.SetActionData(_actionsSystem, page);
|
||||
|
||||
ActionsBar!.PageButtons.Label.Text = $"{_currentPageIndex + 1}";
|
||||
}
|
||||
@@ -424,7 +428,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
|
||||
AppendAction(actionId);
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnActionRemoved(EntityUid actionId)
|
||||
@@ -454,24 +457,11 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnActionReplaced(EntityUid actionId)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.ActionId == actionId)
|
||||
button.UpdateData(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActionsUpdated()
|
||||
{
|
||||
QueueWindowUpdate();
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
@@ -538,27 +528,56 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void PopulateActions(IEnumerable<(EntityUid Id, BaseActionComponent Comp)> actions)
|
||||
{
|
||||
if (_window == null)
|
||||
if (_window is not { Disposed: false, IsOpen: true })
|
||||
return;
|
||||
|
||||
ClearList();
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
_window.UpdateNeeded = false;
|
||||
|
||||
List<ActionButton> existing = new(_window.ResultsGrid.ChildCount);
|
||||
foreach (var child in _window.ResultsGrid.Children)
|
||||
{
|
||||
if (child is ActionButton button)
|
||||
existing.Add(button);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var button = new ActionButton {Locked = true};
|
||||
if (i < existing.Count)
|
||||
{
|
||||
existing[i++].UpdateData(action.Id, _actionsSystem);
|
||||
continue;
|
||||
}
|
||||
|
||||
button.UpdateData(action.Id);
|
||||
var button = new ActionButton(_entMan, _spriteSystem, this) {Locked = true};
|
||||
button.ActionPressed += OnWindowActionPressed;
|
||||
button.ActionUnpressed += OnWindowActionUnPressed;
|
||||
button.ActionFocusExited += OnWindowActionFocusExisted;
|
||||
|
||||
button.UpdateData(action.Id, _actionsSystem);
|
||||
_window.ResultsGrid.AddChild(button);
|
||||
}
|
||||
|
||||
for (; i < existing.Count; i++)
|
||||
{
|
||||
existing[i].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueWindowUpdate()
|
||||
{
|
||||
if (_window != null)
|
||||
_window.UpdateNeeded = true;
|
||||
}
|
||||
|
||||
private void SearchAndDisplay()
|
||||
{
|
||||
if (_window is not { Disposed: false } || _actionsSystem == null)
|
||||
if (_window is not { Disposed: false, IsOpen: true })
|
||||
return;
|
||||
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } player)
|
||||
@@ -598,6 +617,9 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void SetAction(ActionButton button, EntityUid? actionId)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
int position;
|
||||
|
||||
if (actionId == null)
|
||||
@@ -611,7 +633,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.TryReplaceWith(actionId.Value) &&
|
||||
if (button.TryReplaceWith(actionId.Value, _actionsSystem) &&
|
||||
_container != null &&
|
||||
_container.TryGetButtonIndex(button, out position))
|
||||
{
|
||||
@@ -648,18 +670,18 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
_window.SearchBar.Clear();
|
||||
_window.FilterButton.DeselectAll();
|
||||
UpdateFilterLabel();
|
||||
SearchAndDisplay();
|
||||
QueueWindowUpdate();
|
||||
}
|
||||
|
||||
private void OnSearchChanged(LineEditEventArgs args)
|
||||
{
|
||||
SearchAndDisplay();
|
||||
QueueWindowUpdate();
|
||||
}
|
||||
|
||||
private void OnFilterSelected(ItemPressedEventArgs args)
|
||||
{
|
||||
UpdateFilterLabel();
|
||||
SearchAndDisplay();
|
||||
QueueWindowUpdate();
|
||||
}
|
||||
|
||||
private void OnWindowActionPressed(GUIBoundKeyEventArgs args, ActionButton action)
|
||||
@@ -849,12 +871,15 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void AssignSlots(List<SlotAssignment> assignments)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
foreach (ref var assignment in CollectionsMarshal.AsSpan(assignments))
|
||||
{
|
||||
_pages[assignment.Hotbar][assignment.Slot] = assignment.ActionId;
|
||||
}
|
||||
|
||||
_container?.SetActionData(_pages[_currentPageIndex]);
|
||||
_container?.SetActionData(_actionsSystem, _pages[_currentPageIndex]);
|
||||
}
|
||||
|
||||
public void RemoveActionContainer()
|
||||
@@ -881,19 +906,24 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
_menuDragHelper.Update(args.DeltaSeconds);
|
||||
if (_window is {UpdateNeeded: true})
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnComponentLinked(ActionsComponent component)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
LoadDefaultActions(component);
|
||||
_container?.SetActionData(_pages[DefaultPageIndex]);
|
||||
SearchAndDisplay();
|
||||
_container?.SetActionData(_actionsSystem, _pages[DefaultPageIndex]);
|
||||
QueueWindowUpdate();
|
||||
}
|
||||
|
||||
private void OnComponentUnlinked()
|
||||
{
|
||||
_container?.ClearActionData();
|
||||
SearchAndDisplay();
|
||||
QueueWindowUpdate();
|
||||
StopTargeting();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -21,11 +19,9 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls;
|
||||
|
||||
public sealed class ActionButton : Control, IEntityControl
|
||||
{
|
||||
private IEntityManager? _entities;
|
||||
|
||||
private ActionUIController Controller => UserInterfaceManager.GetUIController<ActionUIController>();
|
||||
private IEntityManager Entities => _entities ??= IoCManager.Resolve<IEntityManager>();
|
||||
private ActionsSystem Actions => Entities.System<ActionsSystem>();
|
||||
private IEntityManager _entities;
|
||||
private SpriteSystem? _spriteSys;
|
||||
private ActionUIController? _controller;
|
||||
private bool _beingHovered;
|
||||
private bool _depressed;
|
||||
private bool _toggled;
|
||||
@@ -54,14 +50,21 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
private readonly SpriteView _bigItemSpriteView;
|
||||
|
||||
public EntityUid? ActionId { get; private set; }
|
||||
private BaseActionComponent? _action;
|
||||
public bool Locked { get; set; }
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
|
||||
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionUnpressed;
|
||||
public event Action<ActionButton>? ActionFocusExited;
|
||||
|
||||
public ActionButton()
|
||||
public ActionButton(IEntityManager entities, SpriteSystem? spriteSys = null, ActionUIController? controller = null)
|
||||
{
|
||||
// TODO why is this constructor so slooooow. The rest of the code is fine
|
||||
|
||||
_entities = entities;
|
||||
_spriteSys = spriteSys;
|
||||
_controller = controller;
|
||||
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
Button = new TextureRect
|
||||
{
|
||||
@@ -180,7 +183,7 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
|
||||
private Control? SupplyTooltip(Control sender)
|
||||
{
|
||||
if (!Entities.TryGetComponent(ActionId, out MetaDataComponent? metadata))
|
||||
if (!_entities.TryGetComponent(ActionId, out MetaDataComponent? metadata))
|
||||
return null;
|
||||
|
||||
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
|
||||
@@ -196,9 +199,8 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
|
||||
private void UpdateItemIcon()
|
||||
{
|
||||
if (!Actions.TryGetActionData(ActionId, out var action) ||
|
||||
action is not {EntityIcon: { } entity} ||
|
||||
!Entities.HasComponent<SpriteComponent>(entity))
|
||||
if (_action is not {EntityIcon: { } entity} ||
|
||||
!_entities.HasComponent<SpriteComponent>(entity))
|
||||
{
|
||||
_bigItemSpriteView.Visible = false;
|
||||
_bigItemSpriteView.SetEntity(null);
|
||||
@@ -207,7 +209,7 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (action.ItemIconStyle)
|
||||
switch (_action.ItemIconStyle)
|
||||
{
|
||||
case ItemActionIconStyle.BigItem:
|
||||
_bigItemSpriteView.Visible = true;
|
||||
@@ -233,17 +235,17 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
|
||||
private void SetActionIcon(Texture? texture)
|
||||
{
|
||||
if (!Actions.TryGetActionData(ActionId, out var action) || texture == null)
|
||||
if (_action == null || texture == null)
|
||||
{
|
||||
_bigActionIcon.Texture = null;
|
||||
_bigActionIcon.Visible = false;
|
||||
_smallActionIcon.Texture = null;
|
||||
_smallActionIcon.Visible = false;
|
||||
}
|
||||
else if (action.EntityIcon != null && action.ItemIconStyle == ItemActionIconStyle.BigItem)
|
||||
else if (_action.EntityIcon != null && _action.ItemIconStyle == ItemActionIconStyle.BigItem)
|
||||
{
|
||||
_smallActionIcon.Texture = texture;
|
||||
_smallActionIcon.Modulate = action.IconColor;
|
||||
_smallActionIcon.Modulate = _action.IconColor;
|
||||
_smallActionIcon.Visible = true;
|
||||
_bigActionIcon.Texture = null;
|
||||
_bigActionIcon.Visible = false;
|
||||
@@ -251,7 +253,7 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
else
|
||||
{
|
||||
_bigActionIcon.Texture = texture;
|
||||
_bigActionIcon.Modulate = action.IconColor;
|
||||
_bigActionIcon.Modulate = _action.IconColor;
|
||||
_bigActionIcon.Visible = true;
|
||||
_smallActionIcon.Texture = null;
|
||||
_smallActionIcon.Visible = false;
|
||||
@@ -262,39 +264,43 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
{
|
||||
UpdateItemIcon();
|
||||
|
||||
if (!Actions.TryGetActionData(ActionId, out var action))
|
||||
if (_action == null)
|
||||
{
|
||||
SetActionIcon(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((Controller.SelectingTargetFor == ActionId || action.Toggled) && action.IconOn != null)
|
||||
SetActionIcon(action.IconOn.Frame0());
|
||||
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
|
||||
_spriteSys ??= _entities.System<SpriteSystem>();
|
||||
if ((_controller.SelectingTargetFor == ActionId || _action.Toggled) && _action.IconOn != null)
|
||||
SetActionIcon(_spriteSys.Frame0(_action.IconOn));
|
||||
else
|
||||
SetActionIcon(action.Icon?.Frame0());
|
||||
SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null);
|
||||
}
|
||||
|
||||
public bool TryReplaceWith(EntityUid actionId)
|
||||
public bool TryReplaceWith(EntityUid actionId, ActionsSystem system)
|
||||
{
|
||||
if (Locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateData(actionId);
|
||||
UpdateData(actionId, system);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateData(EntityUid actionId)
|
||||
public void UpdateData(EntityUid? actionId, ActionsSystem system)
|
||||
{
|
||||
ActionId = actionId;
|
||||
Label.Visible = true;
|
||||
system.TryGetActionData(actionId, out _action);
|
||||
Label.Visible = actionId != null;
|
||||
UpdateIcons();
|
||||
}
|
||||
|
||||
public void ClearData()
|
||||
{
|
||||
ActionId = null;
|
||||
_action = null;
|
||||
Cooldown.Visible = false;
|
||||
Cooldown.Progress = 1;
|
||||
Label.Visible = false;
|
||||
@@ -305,19 +311,17 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!Actions.TryGetActionData(ActionId, out var action))
|
||||
{
|
||||
if (_action == null)
|
||||
return;
|
||||
|
||||
if (_action.Cooldown != null)
|
||||
{
|
||||
Cooldown.FromTime(_action.Cooldown.Value.Start, _action.Cooldown.Value.End);
|
||||
}
|
||||
|
||||
if (action.Cooldown != null)
|
||||
if (ActionId != null && _toggled != _action.Toggled)
|
||||
{
|
||||
Cooldown.FromTime(action.Cooldown.Value.Start, action.Cooldown.Value.End);
|
||||
}
|
||||
|
||||
if (ActionId != null && _toggled != action.Toggled)
|
||||
{
|
||||
_toggled = action.Toggled;
|
||||
_toggled = _action.Toggled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +348,7 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
public void Depress(GUIBoundKeyEventArgs args, bool depress)
|
||||
{
|
||||
// action can still be toggled if it's allowed to stay selected
|
||||
if (!Actions.TryGetActionData(ActionId, out var action) || action is not {Enabled: true})
|
||||
if (_action is not {Enabled: true})
|
||||
return;
|
||||
|
||||
if (_depressed && !depress)
|
||||
@@ -362,14 +366,15 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
HighlightRect.Visible = _beingHovered;
|
||||
|
||||
// always show the normal empty button style if no action in this slot
|
||||
if (!Actions.TryGetActionData(ActionId, out var action))
|
||||
if (_action == null)
|
||||
{
|
||||
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
|
||||
return;
|
||||
}
|
||||
|
||||
// show a hover only if the action is usable or another action is being dragged on top of this
|
||||
if (_beingHovered && (Controller.IsDragging || action.Enabled))
|
||||
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
|
||||
if (_beingHovered && (_controller.IsDragging || _action.Enabled))
|
||||
{
|
||||
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
|
||||
}
|
||||
@@ -384,16 +389,16 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
}
|
||||
|
||||
// if it's toggled on, always show the toggled on style (currently same as depressed style)
|
||||
if (action.Toggled || Controller.SelectingTargetFor == ActionId)
|
||||
if (_action.Toggled || _controller.SelectingTargetFor == ActionId)
|
||||
{
|
||||
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
|
||||
SetOnlyStylePseudoClass(action.IconOn != null
|
||||
SetOnlyStylePseudoClass(_action.IconOn != null
|
||||
? ContainerButton.StylePseudoClassNormal
|
||||
: ContainerButton.StylePseudoClassPressed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!action.Enabled)
|
||||
if (!_action.Enabled)
|
||||
{
|
||||
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Actions;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class ActionButtonContainer : GridContainer
|
||||
get => (ActionButton) GetChild(index);
|
||||
}
|
||||
|
||||
public void SetActionData(params EntityUid?[] actionTypes)
|
||||
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
|
||||
{
|
||||
ClearActionData();
|
||||
|
||||
@@ -31,7 +32,7 @@ public class ActionButtonContainer : GridContainer
|
||||
if (action == null)
|
||||
continue;
|
||||
|
||||
((ActionButton) GetChild(i)).UpdateData(action.Value);
|
||||
((ActionButton) GetChild(i)).UpdateData(action.Value, system);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,18 @@ using Content.Shared.Input;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Actions.Widgets;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ActionsBar : UIWidget
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
public ActionsBar()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var keys = ContentKeyFunctions.GetHotbarBoundKeys();
|
||||
for (var index = 1; index < keys.Length; index++)
|
||||
@@ -24,7 +26,7 @@ public sealed partial class ActionsBar : UIWidget
|
||||
ActionButton MakeButton(int index)
|
||||
{
|
||||
var boundKey = keys[index];
|
||||
var button = new ActionButton();
|
||||
var button = new ActionButton(_entity);
|
||||
button.KeyBind = boundKey;
|
||||
button.Label.Text = index.ToString();
|
||||
return button;
|
||||
|
||||
@@ -10,6 +10,11 @@ public sealed partial class ActionsWindow : DefaultWindow
|
||||
{
|
||||
public MultiselectOptionButton<Filters> FilterButton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the displayed actions or search filter needs updating.
|
||||
/// </summary>
|
||||
public bool UpdateNeeded;
|
||||
|
||||
public ActionsWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
@@ -703,7 +703,7 @@ public sealed class ChatUIController : UIController
|
||||
|
||||
public void UpdateSelectedChannel(ChatBox box)
|
||||
{
|
||||
var (prefixChannel, _, radioChannel) = SplitInputContents(box.ChatInput.Input.Text);
|
||||
var (prefixChannel, _, radioChannel) = SplitInputContents(box.ChatInput.Input.Text.ToLower());
|
||||
|
||||
if (prefixChannel == ChatSelectChannel.None)
|
||||
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(box.SelectedChannel, null);
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
@@ -25,6 +30,9 @@ public sealed partial class TestPair
|
||||
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
||||
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
||||
|
||||
public IPlayerSession? Player => (IPlayerSession?) Server.PlayerMan.Sessions.FirstOrDefault();
|
||||
public PlayerData? PlayerData => Player?.Data.ContentData();
|
||||
|
||||
public PoolTestLogHandler ServerLogHandler { get; private set; } = default!;
|
||||
public PoolTestLogHandler ClientLogHandler { get; private set; } = default!;
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute that indicates that a string contains yaml prototype data that should be loaded by integration tests.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class TestPrototypesAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
components:
|
||||
- type: Buckle
|
||||
- type: Hands
|
||||
- type: InputMover
|
||||
- type: Body
|
||||
prototype: Human
|
||||
- type: StandingState
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable enable
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Server.Climbing;
|
||||
using Content.Shared.Climbing;
|
||||
using Robust.Shared.Maths;
|
||||
using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
|
||||
using ClimbSystem = Content.Shared.Climbing.Systems.ClimbSystem;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Climbing;
|
||||
|
||||
|
||||
71
Content.IntegrationTests/Tests/ConfigPresetTests.cs
Normal file
71
Content.IntegrationTests/Tests/ConfigPresetTests.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Content.Server.Entry;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class ConfigPresetTests
|
||||
{
|
||||
[Test]
|
||||
public async Task TestLoadAll()
|
||||
{
|
||||
var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var resources = server.ResolveDependency<IResourceManager>();
|
||||
var config = server.ResolveDependency<IConfigurationManager>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var originalCVars = new List<(string, object)>();
|
||||
foreach (var cvar in config.GetRegisteredCVars())
|
||||
{
|
||||
var value = config.GetCVar<object>(cvar);
|
||||
originalCVars.Add((cvar, value));
|
||||
}
|
||||
|
||||
var originalCvarsStream = new MemoryStream();
|
||||
config.SaveToTomlStream(originalCvarsStream, config.GetRegisteredCVars());
|
||||
originalCvarsStream.Position = 0;
|
||||
|
||||
var presets = resources.ContentFindFiles(EntryPoint.ConfigPresetsDir);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var preset in presets)
|
||||
{
|
||||
var stream = resources.ContentFileRead(preset);
|
||||
Assert.DoesNotThrow(() => config.LoadDefaultsFromTomlStream(stream));
|
||||
}
|
||||
});
|
||||
|
||||
config.LoadDefaultsFromTomlStream(originalCvarsStream);
|
||||
|
||||
foreach (var originalCVar in originalCVars)
|
||||
{
|
||||
var (name, originalValue) = originalCVar;
|
||||
var newValue = config.GetCVar<object>(name);
|
||||
var originalValueType = originalValue.GetType();
|
||||
var newValueType = newValue.GetType();
|
||||
if (originalValueType.IsEnum || newValueType.IsEnum)
|
||||
{
|
||||
originalValue = Enum.ToObject(originalValueType, originalValue);
|
||||
newValue = Enum.ToObject(originalValueType, newValue);
|
||||
}
|
||||
|
||||
if (originalValueType == typeof(float) || newValueType == typeof(float))
|
||||
{
|
||||
originalValue = Convert.ToSingle(originalValue);
|
||||
newValue = Convert.ToSingle(newValue);
|
||||
}
|
||||
|
||||
if (!Equals(newValue, originalValue))
|
||||
Assert.Fail($"CVar {name} was not reset to its original value.");
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -63,11 +63,11 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>();
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, coords);
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
sEntities.EnsureComponent<ItemComponent>(item);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
@@ -134,11 +134,11 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>();
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, new MapCoordinates(new Vector2(1.9f, 0), mapId));
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
sEntities.EnsureComponent<ItemComponent>(item);
|
||||
wall = sEntities.SpawnEntity("DummyDebugWall", new MapCoordinates(new Vector2(1, 0), sEntities.GetComponent<TransformComponent>(user).MapID));
|
||||
});
|
||||
|
||||
@@ -204,11 +204,11 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>();
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, new MapCoordinates(new Vector2(SharedInteractionSystem.InteractionRange - 0.1f, 0), mapId));
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
sEntities.EnsureComponent<ItemComponent>(item);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
@@ -274,11 +274,11 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>();
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, new MapCoordinates(new Vector2(SharedInteractionSystem.InteractionRange + 0.01f, 0), mapId));
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
sEntities.EnsureComponent<ItemComponent>(item);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
@@ -346,11 +346,11 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>();
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, coords);
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
sEntities.EnsureComponent<ItemComponent>(item);
|
||||
containerEntity = sEntities.SpawnEntity(null, coords);
|
||||
container = conSystem.EnsureContainer<Container>(containerEntity, "InteractionTestContainer");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Minds;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class MindTests
|
||||
{
|
||||
[Test]
|
||||
public async Task DeleteAllThenGhost()
|
||||
{
|
||||
var settings = new PoolSettings
|
||||
{
|
||||
Dirty = true,
|
||||
DummyTicker = false,
|
||||
Connected = true
|
||||
};
|
||||
await using var pair = await PoolManager.GetServerClient(settings);
|
||||
|
||||
// Client is connected with a valid entity & mind
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
|
||||
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
|
||||
|
||||
// Delete **everything**
|
||||
var conHost = pair.Server.ResolveDependency<IConsoleHost>();
|
||||
await pair.Server.WaitPost(() => conHost.ExecuteCommand("entities delete"));
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
Assert.That(pair.Server.EntMan.EntityCount, Is.EqualTo(0));
|
||||
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
|
||||
|
||||
// Create a new map.
|
||||
int mapId = 1;
|
||||
await pair.Server.WaitPost(() => conHost.ExecuteCommand($"addmap {mapId}"));
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
// Client is not attached to anything
|
||||
Assert.Null(pair.Client.Player?.ControlledEntity);
|
||||
Assert.Null(pair.PlayerData?.Mind);
|
||||
|
||||
// Attempt to ghost
|
||||
var cConHost = pair.Client.ResolveDependency<IConsoleHost>();
|
||||
await pair.Client.WaitPost(() => cConHost.ExecuteCommand("ghost"));
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
// Client should be attached to a ghost placed on the new map.
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
|
||||
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
|
||||
var xform = pair.Client.Transform(pair.Client.Player!.ControlledEntity!.Value);
|
||||
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.VendingMachines;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Content.Shared.Wires;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
@@ -96,7 +95,7 @@ namespace Content.IntegrationTests.Tests
|
||||
name: Test Ramen
|
||||
components:
|
||||
- type: Wires
|
||||
LayoutId: Vending
|
||||
layoutId: Vending
|
||||
- type: VendingMachine
|
||||
pack: TestInventory
|
||||
- type: Sprite
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using Content.Server.Anomaly.Effects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component allows the anomaly to inject liquid from the SolutionContainer
|
||||
/// into the surrounding entities with the InjectionSolution component
|
||||
/// </summary>
|
||||
|
||||
[RegisterComponent, Access(typeof(InjectionAnomalySystem))]
|
||||
public sealed partial class InjectionAnomalyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// the maximum amount of injection of a substance into an entity per pulsation
|
||||
/// scales with Severity
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxSolutionInjection = 15;
|
||||
/// <summary>
|
||||
/// the maximum amount of injection of a substance into an entity in the supercritical phase
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SuperCriticalSolutionInjection = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum radius in which the anomaly injects reagents into the surrounding containers.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float InjectRadius = 3;
|
||||
/// <summary>
|
||||
/// The maximum radius in which the anomaly injects reagents into the surrounding containers.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SuperCriticalInjectRadius = 15;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the prototype of the special effect that appears above the entities into which the injection was carried out
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public EntProtoId VisualEffectPrototype = "PuddleSparkle";
|
||||
/// <summary>
|
||||
/// Solution name that can be drained.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution { get; set; } = "default";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Server.Anomaly.Effects;
|
||||
|
||||
namespace Content.Server.Anomaly.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component allows the anomaly to create puddles from the solutionContainer
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(PuddleCreateAnomalySystem))]
|
||||
public sealed partial class PuddleCreateAnomalyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum amount of solution that an anomaly can splash out of the storage on the floor during pulsation.
|
||||
/// Scales with Severity.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxPuddleSize = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of solution that an anomaly can splash out of the storage on the floor during supercritical event
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SuperCriticalPuddleSize = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Solution name that can be drained.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution { get; set; } = "default";
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Content.Server.Anomaly.Effects;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Server.Anomaly.Components;
|
||||
/// <summary>
|
||||
/// This component allows the anomaly to generate a random type of reagent in the specified SolutionContainer.
|
||||
/// With the increasing severity of the anomaly, the type of reagent produced may change.
|
||||
/// The higher the severity of the anomaly, the higher the chance of dangerous or useful reagents.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(ReagentProducerAnomalySystem))]
|
||||
public sealed partial class ReagentProducerAnomalyComponent : Component
|
||||
{
|
||||
//the addition of the reagent will occur instantly when an anomaly appears,
|
||||
//and there will not be the first three seconds of a white empty anomaly.
|
||||
public float AccumulatedFrametime = 3.0f;
|
||||
/// <summary>
|
||||
/// How frequently should this reagent generation update, in seconds?
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float UpdateInterval = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The spread of the random weight of the choice of this category, depending on the severity.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 WeightSpreadDangerous = new(5.0f, 9.0f);
|
||||
/// <summary>
|
||||
/// The spread of the random weight of the choice of this category, depending on the severity.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 WeightSpreadFun = new(3.0f, 0.0f);
|
||||
/// <summary>
|
||||
/// The spread of the random weight of the choice of this category, depending on the severity.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 WeightSpreadUseful = new(1.0f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Category of dangerous reagents for injection. Various toxins and poisons
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<ProtoId<ReagentPrototype>> DangerousChemicals = new();
|
||||
/// <summary>
|
||||
/// Category of useful reagents for injection. Medicine and other things that players WANT to get
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<ProtoId<ReagentPrototype>> UsefulChemicals = new();
|
||||
/// <summary>
|
||||
/// Category of fun reagents for injection. Glue, drugs, beer. Something that will bring fun.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<ProtoId<ReagentPrototype>> FunChemicals = new();
|
||||
|
||||
/// <summary>
|
||||
/// Noise made when anomaly pulse.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier ChangeSound = new SoundPathSpecifier("/Audio/Effects/waterswirl.ogg");
|
||||
/// <summary>
|
||||
/// The component will repaint the sprites of the object to match the current color of the solution,
|
||||
/// if the RandomSprite component is hung correctly.
|
||||
/// Ideally, this should be put into a separate component, but I suffered for 4 hours,
|
||||
/// and nothing worked out for me. So for now it will be like this.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool NeedRecolor = false;
|
||||
|
||||
/// <summary>
|
||||
/// the maximum amount of reagent produced per second
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxReagentProducing = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// how much does the reagent production increase before entering the supercritical state
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SupercriticalReagentProducingModifier = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the reagent that the anomaly produces.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public ProtoId<ReagentPrototype> ProducingReagent = "Water";
|
||||
/// <summary>
|
||||
/// Solution name where the substance is generated
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution = "default";
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public sealed class ElectricityAnomalySystem : EntitySystem
|
||||
if (mobQuery.HasComponent(ent))
|
||||
validEnts.Add(ent);
|
||||
|
||||
if (_random.Prob(0.2f) && poweredQuery.HasComponent(ent))
|
||||
if (_random.Prob(0.01f) && poweredQuery.HasComponent(ent))
|
||||
validEnts.Add(ent);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
@@ -39,11 +38,10 @@ public sealed class EntityAnomalySystem : EntitySystem
|
||||
// A cluster of monsters
|
||||
SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.Spawns);
|
||||
// And so much meat (for the meat anomaly at least)
|
||||
Spawn(component.SupercriticalSpawn, xform.Coordinates);
|
||||
SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, new List<string>(){component.SupercriticalSpawn});
|
||||
SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.SuperCriticalSpawns);
|
||||
}
|
||||
|
||||
private void SpawnMonstersOnOpenTiles(EntitySpawnAnomalyComponent component, TransformComponent xform, int amount, float radius, List<string> spawns)
|
||||
private void SpawnMonstersOnOpenTiles(EntitySpawnAnomalyComponent component, TransformComponent xform, int amount, float radius, List<EntProtoId> spawns)
|
||||
{
|
||||
if (!component.Spawns.Any())
|
||||
return;
|
||||
@@ -68,10 +66,12 @@ public sealed class EntityAnomalySystem : EntitySystem
|
||||
{
|
||||
if (!physQuery.TryGetComponent(ent, out var body))
|
||||
continue;
|
||||
|
||||
if (body.BodyType != BodyType.Static ||
|
||||
!body.Hard ||
|
||||
(body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
|
||||
continue;
|
||||
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
67
Content.Server/Anomaly/Effects/InjectionAnomalySystem.cs
Normal file
67
Content.Server/Anomaly/Effects/InjectionAnomalySystem.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
/// <summary>
|
||||
/// This component allows the anomaly to inject liquid from the SolutionContainer
|
||||
/// into the surrounding entities with the InjectionSolution component
|
||||
/// </summary>
|
||||
///
|
||||
|
||||
/// <see cref="InjectionAnomalyComponent"/>
|
||||
public sealed class InjectionAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
private EntityQuery<InjectableSolutionComponent> _injectableQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<InjectionAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||
SubscribeLocalEvent<InjectionAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical, before: new[] { typeof(SolutionContainerSystem) });
|
||||
|
||||
_injectableQuery = GetEntityQuery<InjectableSolutionComponent>();
|
||||
}
|
||||
|
||||
private void OnPulse(EntityUid uid, InjectionAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||
{
|
||||
PulseScalableEffect(uid, component, component.InjectRadius, component.MaxSolutionInjection * args.Severity);
|
||||
}
|
||||
|
||||
private void OnSupercritical(EntityUid uid, InjectionAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
PulseScalableEffect(uid, component, component.SuperCriticalInjectRadius, component.SuperCriticalSolutionInjection);
|
||||
}
|
||||
|
||||
private void PulseScalableEffect(EntityUid uid, InjectionAnomalyComponent component, float injectRadius, float maxInject)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var sol))
|
||||
return;
|
||||
//We get all the entity in the radius into which the reagent will be injected.
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
var allEnts = _lookup.GetComponentsInRange<InjectableSolutionComponent>(xform.MapPosition, injectRadius)
|
||||
.Select(x => x.Owner).ToList();
|
||||
|
||||
//for each matching entity found
|
||||
foreach (var ent in allEnts)
|
||||
{
|
||||
if (!_solutionContainer.TryGetInjectableSolution(ent, out var injectable))
|
||||
continue;
|
||||
|
||||
if (_injectableQuery.TryGetComponent(ent, out var injEnt))
|
||||
{
|
||||
var buffer = sol;
|
||||
_solutionContainer.TryTransferSolution(ent, injectable, buffer, maxInject);
|
||||
//Spawn Effect
|
||||
var uidXform = Transform(ent);
|
||||
Spawn(component.VisualEffectPrototype, uidXform.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
39
Content.Server/Anomaly/Effects/PuddleCreateAnomalySystem.cs
Normal file
39
Content.Server/Anomaly/Effects/PuddleCreateAnomalySystem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// This component allows the anomaly to create puddles from SolutionContainer.
|
||||
/// </summary>
|
||||
public sealed class PuddleCreateAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PuddleCreateAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||
SubscribeLocalEvent<PuddleCreateAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical, before: new[] { typeof(InjectionAnomalySystem) });
|
||||
}
|
||||
|
||||
private void OnPulse(EntityUid uid, PuddleCreateAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var sol))
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
var puddleSol = _solutionContainer.SplitSolution(uid, sol, component.MaxPuddleSize * args.Severity);
|
||||
_puddle.TrySplashSpillAt(uid, xform.Coordinates, puddleSol, out _);
|
||||
}
|
||||
private void OnSupercritical(EntityUid uid, PuddleCreateAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var sol))
|
||||
return;
|
||||
var buffer = sol;
|
||||
var xform = Transform(uid);
|
||||
_puddle.TrySpillAt(xform.Coordinates, buffer, out _);
|
||||
}
|
||||
}
|
||||
152
Content.Server/Anomaly/Effects/ReagentProducerAnomalySystem.cs
Normal file
152
Content.Server/Anomaly/Effects/ReagentProducerAnomalySystem.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
/// <see cref="ReagentProducerAnomalyComponent"/>
|
||||
|
||||
public sealed class ReagentProducerAnomalySystem : EntitySystem
|
||||
{
|
||||
//The idea is to divide substances into several categories.
|
||||
//The anomaly will choose one of the categories with a given chance based on severity.
|
||||
//Then a random substance will be selected from the selected category.
|
||||
//There are the following categories:
|
||||
|
||||
//Dangerous:
|
||||
//selected most often. A list of substances that are extremely unpleasant for injection.
|
||||
|
||||
//Fun:
|
||||
//Funny things have an increased chance of appearing in an anomaly.
|
||||
|
||||
//Useful:
|
||||
//Those reagents that the players are hunting for. Very low percentage of loss.
|
||||
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PointLightSystem _light = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public const string FallbackReagent = "Water";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ReagentProducerAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||
SubscribeLocalEvent<ReagentProducerAnomalyComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnPulse(EntityUid uid, ReagentProducerAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||
{
|
||||
if (_random.NextFloat(0.0f, 1.0f) > args.Stability)
|
||||
ChangeReagent(uid, component, args.Severity);
|
||||
}
|
||||
|
||||
private void ChangeReagent(EntityUid uid, ReagentProducerAnomalyComponent component, float severity)
|
||||
{
|
||||
var reagent = GetRandomReagentType(uid, component, severity);
|
||||
component.ProducingReagent = reagent;
|
||||
_audio.PlayPvs(component.ChangeSound, uid);
|
||||
}
|
||||
|
||||
//reagent realtime generation
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<ReagentProducerAnomalyComponent, AnomalyComponent>();
|
||||
while (query.MoveNext(out var uid, out var component, out var anomaly))
|
||||
{
|
||||
component.AccumulatedFrametime += frameTime;
|
||||
|
||||
if (component.AccumulatedFrametime < component.UpdateInterval)
|
||||
continue;
|
||||
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var producerSol))
|
||||
continue;
|
||||
|
||||
Solution newSol = new();
|
||||
var reagentProducingAmount = anomaly.Stability * component.MaxReagentProducing * component.AccumulatedFrametime;
|
||||
if (anomaly.Severity >= 0.97) reagentProducingAmount *= component.SupercriticalReagentProducingModifier;
|
||||
|
||||
newSol.AddReagent(component.ProducingReagent, reagentProducingAmount);
|
||||
_solutionContainer.TryAddSolution(uid, producerSol, newSol); //TO DO - the container is not fully filled.
|
||||
|
||||
component.AccumulatedFrametime = 0;
|
||||
|
||||
// The component will repaint the sprites of the object to match the current color of the solution,
|
||||
// if the RandomSprite component is hung correctly.
|
||||
|
||||
// Ideally, this should be put into a separate component, but I suffered for 4 hours,
|
||||
// and nothing worked out for me. So for now it will be like this.
|
||||
if (component.NeedRecolor)
|
||||
{
|
||||
var color = producerSol.GetColor(_prototypeManager);
|
||||
_light.SetColor(uid, color);
|
||||
if (TryComp<RandomSpriteComponent>(uid, out var randomSprite))
|
||||
{
|
||||
foreach (var ent in randomSprite.Selected)
|
||||
{
|
||||
var state = randomSprite.Selected[ent.Key];
|
||||
state.Color = color;
|
||||
randomSprite.Selected[ent.Key] = state;
|
||||
}
|
||||
Dirty(uid, randomSprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, ReagentProducerAnomalyComponent component, MapInitEvent args)
|
||||
{
|
||||
ChangeReagent(uid, component, 0.1f); //MapInit Reagent 100% change
|
||||
}
|
||||
|
||||
// returns a random reagent based on a system of random weights.
|
||||
// First, the category is selected: The category has a minimum and maximum weight,
|
||||
// the current value depends on severity.
|
||||
// Accordingly, with the strengthening of the anomaly,
|
||||
// the chances of falling out of some categories grow, and some fall.
|
||||
//
|
||||
// After that, a random reagent in the selected category is selected.
|
||||
//
|
||||
// Such a system is made to control the danger and interest of the anomaly more.
|
||||
private string GetRandomReagentType(EntityUid uid, ReagentProducerAnomalyComponent component, float severity)
|
||||
{
|
||||
//Category Weight Randomization
|
||||
var currentWeightDangerous = MathHelper.Lerp(component.WeightSpreadDangerous.X, component.WeightSpreadDangerous.Y, severity);
|
||||
var currentWeightFun = MathHelper.Lerp(component.WeightSpreadFun.X, component.WeightSpreadFun.Y, severity);
|
||||
var currentWeightUseful = MathHelper.Lerp(component.WeightSpreadUseful.X, component.WeightSpreadUseful.Y, severity);
|
||||
|
||||
var sumWeight = currentWeightDangerous + currentWeightFun + currentWeightUseful;
|
||||
var rnd = _random.NextFloat(0f, sumWeight);
|
||||
//Dangerous
|
||||
if (rnd <= currentWeightDangerous && component.DangerousChemicals.Count > 0)
|
||||
{
|
||||
var reagent = _random.Pick(component.DangerousChemicals);
|
||||
return reagent;
|
||||
}
|
||||
else rnd -= currentWeightDangerous;
|
||||
//Fun
|
||||
if (rnd <= currentWeightFun && component.FunChemicals.Count > 0)
|
||||
{
|
||||
var reagent = _random.Pick(component.FunChemicals);
|
||||
return reagent;
|
||||
}
|
||||
else rnd -= currentWeightFun;
|
||||
//Useful
|
||||
if (rnd <= currentWeightUseful && component.UsefulChemicals.Count > 0)
|
||||
{
|
||||
var reagent = _random.Pick(component.UsefulChemicals);
|
||||
return reagent;
|
||||
}
|
||||
//We should never end up here.
|
||||
//Maybe Log Error?
|
||||
return FallbackReagent;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
@@ -38,7 +38,7 @@ public sealed class TileAnomalySystem : EntitySystem
|
||||
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius)));
|
||||
foreach (var tileref in tilerefs)
|
||||
{
|
||||
if (!_random.Prob(0.33f))
|
||||
if (!_random.Prob(component.SpawnChance))
|
||||
continue;
|
||||
_tile.ReplaceTile(tileref, fleshTile);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Content.Tests")]
|
||||
[assembly: InternalsVisibleTo("Content.IntegrationTests")]
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Content.Server.Bed
|
||||
AddComp<HealOnBuckleHealingComponent>(uid);
|
||||
component.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(component.HealTime);
|
||||
_actionsSystem.AddAction(args.BuckledEntity, ref component.SleepAction, SleepingSystem.SleepActionId, uid);
|
||||
Dirty(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Sound.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
@@ -36,6 +36,7 @@ namespace Content.Server.Bed.Sleep
|
||||
SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
|
||||
SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
|
||||
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
|
||||
@@ -93,6 +94,11 @@ namespace Content.Server.Bed.Sleep
|
||||
TrySleeping(uid);
|
||||
}
|
||||
|
||||
private void OnBedSleepAction(EntityUid uid, ActionsContainerComponent component, SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(args.Performer);
|
||||
}
|
||||
|
||||
private void OnWakeAction(EntityUid uid, MobStateComponent component, WakeActionEvent args)
|
||||
{
|
||||
if (!TryWakeCooldown(uid))
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Content.Server.Bible
|
||||
|
||||
var damage = _damageableSystem.TryChangeDamage(args.Target.Value, component.Damage, true, origin: uid);
|
||||
|
||||
if (damage == null || damage.Total == 0)
|
||||
if (damage == null || damage.Empty)
|
||||
{
|
||||
var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-others", ("user", Identity.Entity(args.User, EntityManager)),("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid));
|
||||
_popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
|
||||
|
||||
@@ -2,17 +2,13 @@ using Content.Server.Body.Components;
|
||||
using Content.Server.Ghost.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
|
||||
namespace Content.Server.Body.Systems
|
||||
{
|
||||
public sealed class BrainSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -20,11 +16,14 @@ namespace Content.Server.Body.Systems
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BrainComponent, AddedToPartInBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
|
||||
SubscribeLocalEvent<BrainComponent, RemovedFromPartInBodyEvent>((uid, _, args) => HandleMind(args.OldBody, uid));
|
||||
SubscribeLocalEvent<BrainComponent, RemovedFromPartInBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
|
||||
}
|
||||
|
||||
private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
|
||||
{
|
||||
if (TerminatingOrDeleted(newEntity) || TerminatingOrDeleted(oldEntity))
|
||||
return;
|
||||
|
||||
EnsureComp<MindContainerComponent>(newEntity);
|
||||
EnsureComp<MindContainerComponent>(oldEntity);
|
||||
|
||||
@@ -32,16 +31,6 @@ namespace Content.Server.Body.Systems
|
||||
if (HasComp<BodyComponent>(newEntity))
|
||||
ghostOnMove.MustBeDead = true;
|
||||
|
||||
// TODO: This is an awful solution.
|
||||
// Our greatest minds still can't figure out how to allow brains/heads to ghost without giving them the
|
||||
// ability to move first. I hate this with a passion.
|
||||
if (!HasComp<InputMoverComponent>(newEntity))
|
||||
{
|
||||
AddComp<InputMoverComponent>(newEntity);
|
||||
var move = EnsureComp<MovementSpeedModifierComponent>(newEntity);
|
||||
_movementSpeed.ChangeBaseSpeed(newEntity, 0, 0 , 0, move);
|
||||
}
|
||||
|
||||
if (!_mindSystem.TryGetMind(oldEntity, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class MutationSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Add up everything in the bits column and put the number here.
|
||||
const int totalbits = 265;
|
||||
const int totalbits = 270;
|
||||
|
||||
// Tolerances (55)
|
||||
MutateFloat(ref seed.NutrientConsumption , 0.05f, 1.2f, 5, totalbits, severity);
|
||||
@@ -69,8 +69,7 @@ public sealed class MutationSystem : EntitySystem
|
||||
MutateBool(ref seed.Sentient , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.Ligneous , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.Bioluminescent, true , 10, totalbits, severity);
|
||||
// Kudzu disabled until superkudzu bug is fixed
|
||||
// MutateBool(ref seed.TurnIntoKudzu , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.TurnIntoKudzu , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.CanScream , true , 10, totalbits, severity);
|
||||
seed.BioluminescentColor = RandomColor(seed.BioluminescentColor, 10, totalbits, severity);
|
||||
|
||||
@@ -119,7 +118,7 @@ public sealed class MutationSystem : EntitySystem
|
||||
CrossBool(ref result.Sentient, a.Sentient);
|
||||
CrossBool(ref result.Ligneous, a.Ligneous);
|
||||
CrossBool(ref result.Bioluminescent, a.Bioluminescent);
|
||||
// CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu);
|
||||
CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu);
|
||||
CrossBool(ref result.CanScream, a.CanScream);
|
||||
|
||||
CrossGasses(ref result.ExudeGasses, a.ExudeGasses);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Chemistry.Components;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When a <see cref="SmokeComponent"/> despawns this will spawn another entity in its place.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SmokeSystem))]
|
||||
public sealed partial class SmokeDissipateSpawnComponent : Component
|
||||
{
|
||||
[DataField("prototype", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Prototype = string.Empty;
|
||||
}
|
||||
@@ -7,24 +7,24 @@ public sealed partial class SolutionSpikerComponent : Component
|
||||
/// The source solution to take the reagents from in order
|
||||
/// to spike the other solution container.
|
||||
/// </summary>
|
||||
[DataField("sourceSolution")]
|
||||
[DataField]
|
||||
public string SourceSolution { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If spiking with this entity should ignore empty containers or not.
|
||||
/// </summary>
|
||||
[DataField("ignoreEmpty")]
|
||||
[DataField]
|
||||
public bool IgnoreEmpty { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// What should pop up when spiking with this entity.
|
||||
/// </summary>
|
||||
[DataField("popup")]
|
||||
public string Popup { get; private set; } = "spike-solution-generic";
|
||||
[DataField]
|
||||
public LocId Popup { get; private set; } = "spike-solution-generic";
|
||||
|
||||
/// <summary>
|
||||
/// What should pop up when spiking fails because the container was empty.
|
||||
/// </summary>
|
||||
[DataField("popupEmpty")]
|
||||
public string PopupEmpty { get; private set; } = "spike-solution-empty-generic";
|
||||
[DataField]
|
||||
public LocId PopupEmpty { get; private set; } = "spike-solution-empty-generic";
|
||||
}
|
||||
|
||||
@@ -22,16 +22,9 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SolutionInjectOnCollideComponent, ComponentInit>(HandleInit);
|
||||
SubscribeLocalEvent<SolutionInjectOnCollideComponent, StartCollideEvent>(HandleInjection);
|
||||
}
|
||||
|
||||
private void HandleInit(EntityUid uid, SolutionInjectOnCollideComponent component, ComponentInit args)
|
||||
{
|
||||
component.Owner
|
||||
.EnsureComponentWarn<SolutionContainerManagerComponent>($"{nameof(SolutionInjectOnCollideComponent)} requires a SolutionContainerManager on {component.Owner}!");
|
||||
}
|
||||
|
||||
private void HandleInjection(EntityUid uid, SolutionInjectOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
var target = args.OtherEntity;
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Climbing.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Climbing;
|
||||
using Content.Shared.Climbing.Events;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Climbing;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ClimbSystem : SharedClimbSystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
private const string ClimbingFixtureName = "climb";
|
||||
private const int ClimbingCollisionGroup = (int) (CollisionGroup.TableLayer | CollisionGroup.LowImpassable);
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<string, Fixture>> _fixtureRemoveQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
|
||||
SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
|
||||
|
||||
SubscribeLocalEvent<ClimbingComponent, ClimbDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
|
||||
SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
|
||||
SubscribeLocalEvent<GlassTableComponent, ClimbedOnEvent>(OnGlassClimbed);
|
||||
}
|
||||
|
||||
protected override void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
base.OnCanDragDropOn(uid, component, ref args);
|
||||
|
||||
if (!args.CanDrop)
|
||||
return;
|
||||
|
||||
string reason;
|
||||
var canVault = args.User == args.Dragged
|
||||
? CanVault(component, args.User, uid, out reason)
|
||||
: CanVault(component, args.User, args.Dragged, uid, out reason);
|
||||
|
||||
if (!canVault)
|
||||
_popupSystem.PopupEntity(reason, args.User, args.User);
|
||||
|
||||
args.CanDrop = canVault;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void AddClimbableVerb(EntityUid uid, ClimbableComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User))
|
||||
return;
|
||||
|
||||
if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
|
||||
return;
|
||||
|
||||
// TODO VERBS ICON add a climbing icon?
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Act = () => TryClimb(args.User, args.User, args.Target, out _, component),
|
||||
Text = Loc.GetString("comp-climbable-verb-climb")
|
||||
});
|
||||
}
|
||||
|
||||
private void OnClimbableDragDrop(EntityUid uid, ClimbableComponent component, ref DragDropTargetEvent args)
|
||||
{
|
||||
// definitely a better way to check if two entities are equal
|
||||
// but don't have computer access and i have to do this without syntax
|
||||
if (args.Handled || args.User != args.Dragged && !HasComp<HandsComponent>(args.User))
|
||||
return;
|
||||
TryClimb(args.User, args.Dragged, uid, out _, component);
|
||||
}
|
||||
|
||||
public bool TryClimb(EntityUid user,
|
||||
EntityUid entityToMove,
|
||||
EntityUid climbable,
|
||||
out DoAfterId? id,
|
||||
ClimbableComponent? comp = null,
|
||||
ClimbingComponent? climbing = null)
|
||||
{
|
||||
id = null;
|
||||
|
||||
if (!Resolve(climbable, ref comp) || !Resolve(entityToMove, ref climbing))
|
||||
return false;
|
||||
|
||||
// Note, IsClimbing does not mean a DoAfter is active, it means the target has already finished a DoAfter and
|
||||
// is currently on top of something..
|
||||
if (climbing.IsClimbing)
|
||||
return true;
|
||||
|
||||
var args = new DoAfterArgs(EntityManager, user, comp.ClimbDelay, new ClimbDoAfterEvent(), entityToMove, target: climbable, used: entityToMove)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true
|
||||
};
|
||||
|
||||
_audio.PlayPvs(comp.StartClimbSound, climbable);
|
||||
_doAfterSystem.TryStartDoAfter(args, out id);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, ClimbingComponent component, ClimbDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
|
||||
return;
|
||||
|
||||
Climb(uid, args.Args.User, args.Args.Used.Value, args.Args.Target.Value, climbing: component);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void Climb(EntityUid uid, EntityUid user, EntityUid instigator, EntityUid climbable, bool silent = false, ClimbingComponent? climbing = null,
|
||||
PhysicsComponent? physics = null, FixturesComponent? fixtures = null, ClimbableComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref climbing, ref physics, ref fixtures, false))
|
||||
return;
|
||||
|
||||
if (!Resolve(climbable, ref comp))
|
||||
return;
|
||||
|
||||
if (!ReplaceFixtures(climbing, fixtures))
|
||||
return;
|
||||
|
||||
climbing.IsClimbing = true;
|
||||
Dirty(climbing);
|
||||
|
||||
_audio.PlayPvs(comp.FinishClimbSound, climbable);
|
||||
MoveEntityToward(uid, climbable, physics, climbing);
|
||||
// we may potentially need additional logic since we're forcing a player onto a climbable
|
||||
// there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for
|
||||
|
||||
RaiseLocalEvent(uid, new StartClimbEvent(climbable), false);
|
||||
RaiseLocalEvent(climbable, new ClimbedOnEvent(uid, user), false);
|
||||
|
||||
if (silent)
|
||||
return;
|
||||
if (user == uid)
|
||||
{
|
||||
var othersMessage = Loc.GetString("comp-climbable-user-climbs-other", ("user", Identity.Entity(uid, EntityManager)),
|
||||
("climbable", climbable));
|
||||
uid.PopupMessageOtherClients(othersMessage);
|
||||
|
||||
var selfMessage = Loc.GetString("comp-climbable-user-climbs", ("climbable", climbable));
|
||||
uid.PopupMessage(selfMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
var othersMessage = Loc.GetString("comp-climbable-user-climbs-force-other", ("user", Identity.Entity(user, EntityManager)),
|
||||
("moved-user", Identity.Entity(uid, EntityManager)), ("climbable", climbable));
|
||||
user.PopupMessageOtherClients(othersMessage);
|
||||
|
||||
var selfMessage = Loc.GetString("comp-climbable-user-climbs-force", ("moved-user", Identity.Entity(uid, EntityManager)),
|
||||
("climbable", climbable));
|
||||
user.PopupMessage(selfMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the current fixtures with non-climbing collidable versions so that climb end can be detected
|
||||
/// </summary>
|
||||
/// <returns>Returns whether adding the new fixtures was successful</returns>
|
||||
private bool ReplaceFixtures(ClimbingComponent climbingComp, FixturesComponent fixturesComp)
|
||||
{
|
||||
var uid = climbingComp.Owner;
|
||||
|
||||
// Swap fixtures
|
||||
foreach (var (name, fixture) in fixturesComp.Fixtures)
|
||||
{
|
||||
if (climbingComp.DisabledFixtureMasks.ContainsKey(name)
|
||||
|| fixture.Hard == false
|
||||
|| (fixture.CollisionMask & ClimbingCollisionGroup) == 0)
|
||||
continue;
|
||||
|
||||
climbingComp.DisabledFixtureMasks.Add(name, fixture.CollisionMask & ClimbingCollisionGroup);
|
||||
_physics.SetCollisionMask(uid, name, fixture, fixture.CollisionMask & ~ClimbingCollisionGroup, fixturesComp);
|
||||
}
|
||||
|
||||
if (!_fixtureSystem.TryCreateFixture(
|
||||
uid,
|
||||
new PhysShapeCircle(0.35f),
|
||||
ClimbingFixtureName,
|
||||
collisionLayer: (int) CollisionGroup.None,
|
||||
collisionMask: ClimbingCollisionGroup,
|
||||
hard: false,
|
||||
manager: fixturesComp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnClimbEndCollide(EntityUid uid, ClimbingComponent component, ref EndCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ClimbingFixtureName
|
||||
|| !component.IsClimbing
|
||||
|| component.OwnerIsTransitioning)
|
||||
return;
|
||||
|
||||
foreach (var fixture in args.OurFixture.Contacts.Keys)
|
||||
{
|
||||
if (fixture == args.OtherFixture)
|
||||
continue;
|
||||
// If still colliding with a climbable, do not stop climbing
|
||||
if (HasComp<ClimbableComponent>(args.OtherEntity))
|
||||
return;
|
||||
}
|
||||
|
||||
StopClimb(uid, component);
|
||||
}
|
||||
|
||||
private void StopClimb(EntityUid uid, ClimbingComponent? climbing = null, FixturesComponent? fixtures = null)
|
||||
{
|
||||
if (!Resolve(uid, ref climbing, ref fixtures, false))
|
||||
return;
|
||||
|
||||
foreach (var (name, fixtureMask) in climbing.DisabledFixtureMasks)
|
||||
{
|
||||
if (!fixtures.Fixtures.TryGetValue(name, out var fixture))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_physics.SetCollisionMask(uid, name, fixture, fixture.CollisionMask | fixtureMask, fixtures);
|
||||
}
|
||||
climbing.DisabledFixtureMasks.Clear();
|
||||
|
||||
if (!_fixtureRemoveQueue.TryGetValue(uid, out var removeQueue))
|
||||
{
|
||||
removeQueue = new Dictionary<string, Fixture>();
|
||||
_fixtureRemoveQueue.Add(uid, removeQueue);
|
||||
}
|
||||
|
||||
if (fixtures.Fixtures.TryGetValue(ClimbingFixtureName, out var climbingFixture))
|
||||
removeQueue.Add(ClimbingFixtureName, climbingFixture);
|
||||
|
||||
climbing.IsClimbing = false;
|
||||
climbing.OwnerIsTransitioning = false;
|
||||
var ev = new EndClimbEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
Dirty(climbing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user can vault the target
|
||||
/// </summary>
|
||||
/// <param name="component">The component of the entity that is being vaulted</param>
|
||||
/// <param name="user">The entity that wants to vault</param>
|
||||
/// <param name="target">The object that is being vaulted</param>
|
||||
/// <param name="reason">The reason why it cant be dropped</param>
|
||||
/// <returns></returns>
|
||||
public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid target, out string reason)
|
||||
{
|
||||
if (!_actionBlockerSystem.CanInteract(user, target))
|
||||
{
|
||||
reason = Loc.GetString("comp-climbable-cant-interact");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasComp<ClimbingComponent>(user)
|
||||
|| !TryComp(user, out BodyComponent? body)
|
||||
|| !_bodySystem.BodyHasPartType(user, BodyPartType.Leg, body)
|
||||
|| !_bodySystem.BodyHasPartType(user, BodyPartType.Foot, body))
|
||||
{
|
||||
reason = Loc.GetString("comp-climbable-cant-climb");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, target, component.Range))
|
||||
{
|
||||
reason = Loc.GetString("comp-climbable-cant-reach");
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user can vault the dragged entity onto the the target
|
||||
/// </summary>
|
||||
/// <param name="component">The climbable component of the object being vaulted onto</param>
|
||||
/// <param name="user">The user that wants to vault the entity</param>
|
||||
/// <param name="dragged">The entity that is being vaulted</param>
|
||||
/// <param name="target">The object that is being vaulted onto</param>
|
||||
/// <param name="reason">The reason why it cant be dropped</param>
|
||||
/// <returns></returns>
|
||||
public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid dragged, EntityUid target,
|
||||
out string reason)
|
||||
{
|
||||
if (!_actionBlockerSystem.CanInteract(user, dragged) || !_actionBlockerSystem.CanInteract(user, target))
|
||||
{
|
||||
reason = Loc.GetString("comp-climbable-cant-interact");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasComp<ClimbingComponent>(dragged))
|
||||
{
|
||||
reason = Loc.GetString("comp-climbable-cant-climb");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged;
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, target, component.Range, predicate: Ignored)
|
||||
|| !_interactionSystem.InRangeUnobstructed(user, dragged, component.Range, predicate: Ignored))
|
||||
{
|
||||
reason = Loc.GetString("comp-climbable-cant-reach");
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ForciblySetClimbing(EntityUid uid, EntityUid climbable, ClimbingComponent? component = null)
|
||||
{
|
||||
Climb(uid, uid, uid, climbable, true, component);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args)
|
||||
{
|
||||
if (!args.Buckling)
|
||||
return;
|
||||
StopClimb(uid, component);
|
||||
}
|
||||
|
||||
private void OnGlassClimbed(EntityUid uid, GlassTableComponent component, ClimbedOnEvent args)
|
||||
{
|
||||
if (TryComp<PhysicsComponent>(args.Climber, out var physics) && physics.Mass <= component.MassLimit)
|
||||
return;
|
||||
|
||||
_damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber);
|
||||
_damageableSystem.TryChangeDamage(uid, component.TableDamage, origin: args.Climber);
|
||||
_stunSystem.TryParalyze(args.Climber, TimeSpan.FromSeconds(component.StunTime), true);
|
||||
|
||||
// Not shown to the user, since they already get a 'you climb on the glass table' popup
|
||||
_popupSystem.PopupEntity(
|
||||
Loc.GetString("glass-table-shattered-others", ("table", uid), ("climber", Identity.Entity(args.Climber, EntityManager))), args.Climber,
|
||||
Filter.PvsExcept(args.Climber), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the entity toward the target climbed entity
|
||||
/// </summary>
|
||||
public void MoveEntityToward(EntityUid uid, EntityUid target, PhysicsComponent? physics = null, ClimbingComponent? climbing = null)
|
||||
{
|
||||
if (!Resolve(uid, ref physics, ref climbing, false))
|
||||
return;
|
||||
|
||||
var from = Transform(uid).WorldPosition;
|
||||
var to = Transform(target).WorldPosition;
|
||||
var (x, y) = (to - from).Normalized();
|
||||
|
||||
if (MathF.Abs(x) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
|
||||
to = new Vector2(from.X, to.Y);
|
||||
else if (MathF.Abs(y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
|
||||
to = new Vector2(to.X, from.Y);
|
||||
|
||||
var velocity = (to - from).Length();
|
||||
|
||||
if (velocity <= 0.0f)
|
||||
return;
|
||||
|
||||
// Since there are bodies with different masses:
|
||||
// mass * 10 seems enough to move entity
|
||||
// instead of launching cats like rockets against the walls with constant impulse value.
|
||||
_physics.ApplyLinearImpulse(uid, (to - from).Normalized() * velocity * physics.Mass * 10, body: physics);
|
||||
_physics.SetBodyType(uid, BodyType.Dynamic, body: physics);
|
||||
climbing.OwnerIsTransitioning = true;
|
||||
_actionBlockerSystem.UpdateCanMove(uid);
|
||||
|
||||
// Transition back to KinematicController after BufferTime
|
||||
climbing.Owner.SpawnTimer((int) (ClimbingComponent.BufferTime * 1000), () =>
|
||||
{
|
||||
if (climbing.Deleted)
|
||||
return;
|
||||
|
||||
_physics.SetBodyType(uid, BodyType.KinematicController);
|
||||
climbing.OwnerIsTransitioning = false;
|
||||
_actionBlockerSystem.UpdateCanMove(uid);
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var (uid, fixtures) in _fixtureRemoveQueue)
|
||||
{
|
||||
if (!TryComp<PhysicsComponent>(uid, out var physicsComp)
|
||||
|| !TryComp<FixturesComponent>(uid, out var fixturesComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var fixture in fixtures)
|
||||
{
|
||||
_fixtureSystem.DestroyFixture(uid, fixture.Key, fixture.Value, body: physicsComp, manager: fixturesComp);
|
||||
}
|
||||
}
|
||||
|
||||
_fixtureRemoveQueue.Clear();
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_fixtureRemoveQueue.Clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it is climbed on.
|
||||
/// </summary>
|
||||
public sealed class ClimbedOnEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Climber;
|
||||
public EntityUid Instigator;
|
||||
|
||||
public ClimbedOnEvent(EntityUid climber, EntityUid instigator)
|
||||
{
|
||||
Climber = climber;
|
||||
Instigator = instigator;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it successfully climbs on something.
|
||||
/// </summary>
|
||||
public sealed class StartClimbEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Climbable;
|
||||
|
||||
public StartClimbEvent(EntityUid climbable)
|
||||
{
|
||||
Climbable = climbable;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Communications;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Communications
|
||||
@@ -21,42 +20,40 @@ namespace Content.Server.Communications
|
||||
/// If a Fluent ID isn't found, just uses the raw string
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("title", required: true)]
|
||||
public string AnnouncementDisplayName = "comms-console-announcement-title-station";
|
||||
[DataField(required: true)]
|
||||
public LocId Title = "comms-console-announcement-title-station";
|
||||
|
||||
/// <summary>
|
||||
/// Announcement color
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("color")]
|
||||
public Color AnnouncementColor = Color.Gold;
|
||||
[DataField]
|
||||
public Color Color = Color.Gold;
|
||||
|
||||
/// <summary>
|
||||
/// Time in seconds between announcement delays on a per-console basis
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("delay")]
|
||||
public int DelayBetweenAnnouncements = 90;
|
||||
[DataField]
|
||||
public int Delay = 90;
|
||||
|
||||
/// <summary>
|
||||
/// Can call or recall the shuttle
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("canShuttle")]
|
||||
public bool CanCallShuttle = true;
|
||||
[DataField]
|
||||
public bool CanShuttle = true;
|
||||
|
||||
/// <summary>
|
||||
/// Announce on all grids (for nukies)
|
||||
/// </summary>
|
||||
[DataField("global")]
|
||||
public bool AnnounceGlobal = false;
|
||||
[DataField]
|
||||
public bool Global = false;
|
||||
|
||||
/// <summary>
|
||||
/// Announce sound file path
|
||||
/// </summary>
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
|
||||
|
||||
public PlayerBoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key);
|
||||
[DataField]
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ namespace Content.Server.Communications
|
||||
|
||||
comp.UIUpdateAccumulator -= UIUpdateInterval;
|
||||
|
||||
if (comp.UserInterface is { } ui && ui.SubscribedSessions.Count > 0)
|
||||
UpdateCommsConsoleInterface(uid, comp);
|
||||
if (_uiSystem.TryGetUi(uid, CommunicationsConsoleUiKey.Key, out var ui) && ui.SubscribedSessions.Count > 0)
|
||||
UpdateCommsConsoleInterface(uid, comp, ui);
|
||||
}
|
||||
|
||||
base.Update(frameTime);
|
||||
@@ -121,9 +121,11 @@ namespace Content.Server.Communications
|
||||
/// <summary>
|
||||
/// Updates the UI for a particular comms console.
|
||||
/// </summary>
|
||||
/// <param name="comp"></param>
|
||||
public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComponent comp)
|
||||
public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComponent comp, PlayerBoundUserInterface? ui = null)
|
||||
{
|
||||
if (ui == null && !_uiSystem.TryGetUi(uid, CommunicationsConsoleUiKey.Key, out ui))
|
||||
return;
|
||||
|
||||
var stationUid = _stationSystem.GetOwningStation(uid);
|
||||
List<string>? levels = null;
|
||||
string currentLevel = default!;
|
||||
@@ -151,15 +153,14 @@ namespace Content.Server.Communications
|
||||
}
|
||||
}
|
||||
|
||||
if (comp.UserInterface is not null)
|
||||
_uiSystem.SetUiState(comp.UserInterface, new CommunicationsConsoleInterfaceState(
|
||||
CanAnnounce(comp),
|
||||
CanCallOrRecall(comp),
|
||||
levels,
|
||||
currentLevel,
|
||||
currentDelay,
|
||||
_roundEndSystem.ExpectedCountdownEnd
|
||||
));
|
||||
_uiSystem.SetUiState(ui, new CommunicationsConsoleInterfaceState(
|
||||
CanAnnounce(comp),
|
||||
CanCallOrRecall(comp),
|
||||
levels,
|
||||
currentLevel,
|
||||
currentDelay,
|
||||
_roundEndSystem.ExpectedCountdownEnd
|
||||
));
|
||||
}
|
||||
|
||||
private static bool CanAnnounce(CommunicationsConsoleComponent comp)
|
||||
@@ -188,7 +189,7 @@ namespace Content.Server.Communications
|
||||
|
||||
// Calling shuttle checks
|
||||
if (_roundEndSystem.ExpectedCountdownEnd is null)
|
||||
return comp.CanCallShuttle;
|
||||
return comp.CanShuttle;
|
||||
|
||||
// Recalling shuttle checks
|
||||
var recallThreshold = _cfg.GetCVar(CCVars.EmergencyRecallTurningPoint);
|
||||
@@ -203,7 +204,9 @@ namespace Content.Server.Communications
|
||||
|
||||
private void OnSelectAlertLevelMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleSelectAlertLevelMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity is not { Valid: true } mob) return;
|
||||
if (message.Session.AttachedEntity is not { Valid: true } mob)
|
||||
return;
|
||||
|
||||
if (!CanUse(mob, uid))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("comms-console-permission-denied"), message.Session, PopupType.Medium);
|
||||
@@ -256,19 +259,27 @@ namespace Content.Server.Communications
|
||||
}
|
||||
}
|
||||
|
||||
comp.AnnouncementCooldownRemaining = comp.DelayBetweenAnnouncements;
|
||||
comp.AnnouncementCooldownRemaining = comp.Delay;
|
||||
UpdateCommsConsoleInterface(uid, comp);
|
||||
|
||||
var ev = new CommunicationConsoleAnnouncementEvent(uid, comp, msg, message.Session.AttachedEntity);
|
||||
RaiseLocalEvent(ref ev);
|
||||
|
||||
// allow admemes with vv
|
||||
Loc.TryGetString(comp.AnnouncementDisplayName, out var title);
|
||||
title ??= comp.AnnouncementDisplayName;
|
||||
Loc.TryGetString(comp.Title, out var title);
|
||||
title ??= comp.Title;
|
||||
|
||||
msg += "\n" + Loc.GetString("comms-console-announcement-sent-by") + " " + author;
|
||||
// Corvax-Announcements-Start
|
||||
_chatSystem.DispatchGlobalAnnouncement(msg, title, announcementSound: comp.AnnouncementSound, colorOverride: comp.AnnouncementColor);
|
||||
if (comp.Global)
|
||||
{
|
||||
_chatSystem.DispatchGlobalAnnouncement(msg, title, announcementSound: comp.Sound, colorOverride: comp.Color);
|
||||
|
||||
if (message.Session.AttachedEntity != null)
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following global announcement: {msg}");
|
||||
|
||||
return;
|
||||
}
|
||||
_chatSystem.DispatchStationAnnouncement(uid, msg, title, colorOverride: comp.Color);
|
||||
|
||||
if (message.Session.AttachedEntity != null)
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following {(comp.AnnounceGlobal ? "global" : "station")} announcement: {msg}");
|
||||
@@ -277,8 +288,12 @@ namespace Content.Server.Communications
|
||||
|
||||
private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message)
|
||||
{
|
||||
if (!CanCallOrRecall(comp)) return;
|
||||
if (message.Session.AttachedEntity is not { Valid: true } mob) return;
|
||||
if (!CanCallOrRecall(comp))
|
||||
return;
|
||||
|
||||
if (message.Session.AttachedEntity is not { Valid: true } mob)
|
||||
return;
|
||||
|
||||
if (!CanUse(mob, uid))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Session);
|
||||
@@ -299,8 +314,12 @@ namespace Content.Server.Communications
|
||||
|
||||
private void OnRecallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleRecallEmergencyShuttleMessage message)
|
||||
{
|
||||
if (!CanCallOrRecall(comp)) return;
|
||||
if (message.Session.AttachedEntity is not { Valid: true } mob) return;
|
||||
if (!CanCallOrRecall(comp))
|
||||
return;
|
||||
|
||||
if (message.Session.AttachedEntity is not { Valid: true } mob)
|
||||
return;
|
||||
|
||||
if (!CanUse(mob, uid))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Session);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Wires;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Construction.Completions;
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class ChangeWiresPanelSecurityLevel : IGraphAction
|
||||
{
|
||||
[DataField("level")]
|
||||
[ValidatePrototypeId<WiresPanelSecurityLevelPrototype>]
|
||||
public string WiresPanelSecurityLevelID = "Level0";
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
if (WiresPanelSecurityLevelID == null)
|
||||
return;
|
||||
|
||||
if (entityManager.TryGetComponent(uid, out WiresPanelComponent? wiresPanel)
|
||||
&& entityManager.TrySystem(out WiresSystem? wiresSystem))
|
||||
{
|
||||
wiresSystem.SetWiresPanelSecurityData(uid, wiresPanel, WiresPanelSecurityLevelID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Wires;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Construction.Completions;
|
||||
|
||||
/// <summary>
|
||||
/// This graph action is used to set values on entities with the <see cref="WiresPanelSecurityComponent"/>
|
||||
/// </summary>
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class SetWiresPanelSecurity : IGraphAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the Examine field on the entity's <see cref="WiresPanelSecurityComponent"/>
|
||||
/// </summary>
|
||||
[DataField("examine")]
|
||||
public string Examine = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the WiresAccessible field on the entity's <see cref="WiresPanelSecurityComponent"/>
|
||||
/// </summary>
|
||||
[DataField("wiresAccessible")]
|
||||
public bool WiresAccessible = true;
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
if (entityManager.TryGetComponent(uid, out WiresPanelSecurityComponent? _))
|
||||
{
|
||||
var ev = new WiresPanelSecurityEvent(Examine, WiresAccessible);
|
||||
entityManager.EventBus.RaiseLocalEvent(uid, ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Content.Server/Construction/Conditions/HasTag.cs
Normal file
43
Content.Server/Construction/Conditions/HasTag.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Shared.Construction;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Examine;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
using Content.Shared.Tag;
|
||||
|
||||
namespace Content.Server.Construction.Conditions
|
||||
{
|
||||
/// <summary>
|
||||
/// This condition checks whether if an entity with the <see cref="TagComponent"/> possesses a specific tag
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class HasTag : IGraphCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// The tag the entity is being checked for
|
||||
/// </summary>
|
||||
[DataField("tag")]
|
||||
public string Tag { get; private set; }
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
{
|
||||
if (!entityManager.TrySystem<TagSystem>(out var tagSystem))
|
||||
return false;
|
||||
|
||||
return tagSystem.HasTag(uid, Tag);
|
||||
}
|
||||
|
||||
public bool DoExamine(ExaminedEvent args)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
||||
{
|
||||
yield return new ConstructionGuideEntry()
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Database;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Construction
|
||||
{
|
||||
@@ -298,9 +299,23 @@ namespace Content.Server.Construction
|
||||
throw new Exception("Missing construction components");
|
||||
}
|
||||
|
||||
// Exit if the new entity's prototype is the same as the original, or the prototype is invalid
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !_prototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
return null;
|
||||
|
||||
// [Optional] Exit if the new entity's prototype is a parent of the original
|
||||
// E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that
|
||||
// had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would
|
||||
// exit here because 'AirlockCommand' is derived from 'Airlock'
|
||||
if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
|
||||
metaData.EntityPrototype?.ID != null)
|
||||
{
|
||||
var parents = _prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
|
||||
if (parents != null && parents.Any(x => x.ID == newEntity))
|
||||
return null;
|
||||
}
|
||||
|
||||
// Optional resolves.
|
||||
Resolve(uid, ref containerManager, false);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Prying.Systems;
|
||||
using Content.Shared.Radio.EntitySystems;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -38,7 +39,7 @@ namespace Content.Server.Construction
|
||||
|
||||
// Event handling. Add your subscriptions here! Just make sure they're all handled by EnqueueEvent.
|
||||
SubscribeLocalEvent<ConstructionComponent, InteractUsingEvent>(EnqueueEvent,
|
||||
new []{typeof(AnchorableSystem), typeof(PryingSystem) },
|
||||
new []{typeof(AnchorableSystem), typeof(PryingSystem), typeof(WeldableSystem)},
|
||||
new []{typeof(EncryptionKeySystem)});
|
||||
SubscribeLocalEvent<ConstructionComponent, OnTemperatureChangeEvent>(EnqueueEvent);
|
||||
SubscribeLocalEvent<ConstructionComponent, PartAssemblyPartInsertedEvent>(EnqueueEvent);
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Content.Server.Containers
|
||||
[UsedImplicitly]
|
||||
public sealed class EmptyOnMachineDeconstructSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -33,12 +35,12 @@ namespace Content.Server.Containers
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ContainerManagerComponent>(uid, out var mComp))
|
||||
return;
|
||||
var baseCoords = EntityManager.GetComponent<TransformComponent>(component.Owner).Coordinates;
|
||||
var baseCoords = EntityManager.GetComponent<TransformComponent>(uid).Coordinates;
|
||||
foreach (var v in component.Containers)
|
||||
{
|
||||
if (mComp.TryGetContainer(v, out var container))
|
||||
{
|
||||
container.EmptyContainer(true, baseCoords);
|
||||
_container.EmptyContainer(container, true, baseCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,5 @@ namespace Content.Server.Crayon
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("deleteEmpty")]
|
||||
public bool DeleteEmpty = true;
|
||||
|
||||
[ViewVariables] public PlayerBoundUserInterface? UserInterface => Owner.GetUIOrNull(CrayonUiKey.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ public sealed class CrayonSystem : SharedCrayonSystem
|
||||
|
||||
// Decrease "Ammo"
|
||||
component.Charges--;
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
|
||||
_adminLogger.Add(LogType.CrayonDraw, LogImpact.Low, $"{EntityManager.ToPrettyString(args.User):user} drew a {component.Color:color} {component.SelectedState}");
|
||||
args.Handled = true;
|
||||
|
||||
@@ -89,17 +90,16 @@ public sealed class CrayonSystem : SharedCrayonSystem
|
||||
return;
|
||||
|
||||
if (!TryComp<ActorComponent>(args.User, out var actor) ||
|
||||
component.UserInterface == null)
|
||||
!_uiSystem.TryGetUi(uid, SharedCrayonComponent.CrayonUiKey.Key, out var ui))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_uiSystem.ToggleUi(component.UserInterface, actor.PlayerSession);
|
||||
|
||||
if (component.UserInterface?.SubscribedSessions.Contains(actor.PlayerSession) == true)
|
||||
_uiSystem.ToggleUi(ui, actor.PlayerSession);
|
||||
if (ui.SubscribedSessions.Contains(actor.PlayerSession))
|
||||
{
|
||||
// Tell the user interface the selected stuff
|
||||
_uiSystem.SetUiState(component.UserInterface, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
|
||||
_uiSystem.SetUiState(ui, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
@@ -108,22 +108,22 @@ public sealed class CrayonSystem : SharedCrayonSystem
|
||||
private void OnCrayonBoundUI(EntityUid uid, CrayonComponent component, CrayonSelectMessage args)
|
||||
{
|
||||
// Check if the selected state is valid
|
||||
if (!_prototypeManager.TryIndex<DecalPrototype>(args.State, out var prototype) || !prototype.Tags.Contains("crayon")) return;
|
||||
if (!_prototypeManager.TryIndex<DecalPrototype>(args.State, out var prototype) || !prototype.Tags.Contains("crayon"))
|
||||
return;
|
||||
|
||||
component.SelectedState = args.State;
|
||||
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnCrayonBoundUIColor(EntityUid uid, CrayonComponent component, CrayonColorMessage args)
|
||||
{
|
||||
// you still need to ensure that the given color is a valid color
|
||||
if (component.SelectableColor && args.Color != component.Color)
|
||||
{
|
||||
component.Color = args.Color;
|
||||
if (!component.SelectableColor || args.Color == component.Color)
|
||||
return;
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
component.Color = args.Color;
|
||||
Dirty(uid, component);
|
||||
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public sealed class CrayonSystem : SharedCrayonSystem
|
||||
// Get the first one from the catalog and set it as default
|
||||
var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().FirstOrDefault(x => x.Tags.Contains("crayon"));
|
||||
component.SelectedState = decal?.ID ?? string.Empty;
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnCrayonDropped(EntityUid uid, CrayonComponent component, DroppedEvent args)
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Destructible;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -36,6 +37,7 @@ namespace Content.Server.Destructible
|
||||
[Dependency] public readonly TriggerSystem TriggerSystem = default!;
|
||||
[Dependency] public readonly SolutionContainerSystem SolutionContainerSystem = default!;
|
||||
[Dependency] public readonly PuddleSystem PuddleSystem = default!;
|
||||
[Dependency] public readonly SharedContainerSystem ContainerSystem = default!;
|
||||
[Dependency] public readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] public readonly IComponentFactory ComponentFactory = default!;
|
||||
[Dependency] public readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
|
||||
foreach (var container in containerManager.GetAllContainers())
|
||||
{
|
||||
container.EmptyContainer(true, system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates);
|
||||
system.ContainerSystem.EmptyContainer(container, true, system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Causes the drink/food to open when the destruction threshold is reached.
|
||||
/// If it is already open nothing happens.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed partial class OpenBehavior : IThresholdBehavior
|
||||
{
|
||||
public void Execute(EntityUid uid, DestructibleSystem system, EntityUid? cause = null)
|
||||
{
|
||||
var openable = EntitySystem.Get<OpenableSystem>();
|
||||
openable.TryOpen(uid);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ public sealed class AirlockSystem : SharedAirlockSystem
|
||||
[Dependency] private readonly WiresSystem _wiresSystem = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly DoorBoltSystem _bolts = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -154,10 +153,12 @@ public sealed class AirlockSystem : SharedAirlockSystem
|
||||
{
|
||||
if (TryComp<WiresPanelComponent>(uid, out var panel) &&
|
||||
panel.Open &&
|
||||
_prototypeManager.TryIndex<WiresPanelSecurityLevelPrototype>(panel.CurrentSecurityLevelID, out var securityLevelPrototype) &&
|
||||
securityLevelPrototype.WiresAccessible &&
|
||||
TryComp<ActorComponent>(args.User, out var actor))
|
||||
{
|
||||
if (TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
|
||||
!wiresPanelSecurity.WiresAccessible)
|
||||
return;
|
||||
|
||||
_wiresSystem.OpenUserInterface(uid, actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
return;
|
||||
|
||||
@@ -177,7 +177,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
if (!electrified.OnAttacked)
|
||||
return;
|
||||
|
||||
if (_meleeWeapon.GetDamage(args.Used, args.User).Total == 0)
|
||||
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
||||
return;
|
||||
|
||||
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
||||
@@ -192,7 +192,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
private void OnLightAttacked(EntityUid uid, PoweredLightComponent component, AttackedEvent args)
|
||||
{
|
||||
|
||||
if (_meleeWeapon.GetDamage(args.Used, args.User).Total == 0)
|
||||
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
||||
return;
|
||||
|
||||
if (args.Used != args.User)
|
||||
|
||||
@@ -22,20 +22,20 @@ using Content.Server.ServerUpdates;
|
||||
using Content.Server.Voting.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Server.ServerStatus;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Localizations;
|
||||
|
||||
namespace Content.Server.Entry
|
||||
{
|
||||
public sealed class EntryPoint : GameServer
|
||||
{
|
||||
private const string ConfigPresetsDir = "/ConfigPresets/";
|
||||
internal const string ConfigPresetsDir = "/ConfigPresets/";
|
||||
private const string ConfigPresetsDirBuild = $"{ConfigPresetsDir}Build/";
|
||||
|
||||
private EuiManager _euiManager = default!;
|
||||
|
||||
@@ -38,17 +38,6 @@ public sealed class SmokeSystem : EntitySystem
|
||||
SubscribeLocalEvent<SmokeComponent, EntityUnpausedEvent>(OnSmokeUnpaused);
|
||||
SubscribeLocalEvent<SmokeComponent, ReactionAttemptEvent>(OnReactionAttempt);
|
||||
SubscribeLocalEvent<SmokeComponent, SpreadNeighborsEvent>(OnSmokeSpread);
|
||||
SubscribeLocalEvent<SmokeDissipateSpawnComponent, TimedDespawnEvent>(OnSmokeDissipate);
|
||||
}
|
||||
|
||||
private void OnSmokeDissipate(EntityUid uid, SmokeDissipateSpawnComponent component, ref TimedDespawnEvent args)
|
||||
{
|
||||
if (!TryComp<TransformComponent>(uid, out var xform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Spawn(component.Prototype, xform.Coordinates);
|
||||
}
|
||||
|
||||
private void OnSmokeSpread(EntityUid uid, SmokeComponent component, ref SpreadNeighborsEvent args)
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace Content.Server.Forensics
|
||||
[RegisterComponent]
|
||||
public sealed partial class FiberComponent : Component
|
||||
{
|
||||
[DataField("fiberMaterial")]
|
||||
public string FiberMaterial = "fibers-synthetic";
|
||||
[DataField]
|
||||
public LocId FiberMaterial = "fibers-synthetic";
|
||||
|
||||
[DataField("fiberColor")]
|
||||
[DataField]
|
||||
public string? FiberColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Content.Server.Friends.Systems;
|
||||
|
||||
public sealed class PettableFriendSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly FactionExceptionSystem _factionException = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _factionException = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -26,7 +26,7 @@ public sealed class PettableFriendSystem : EntitySystem
|
||||
if (args.Handled || !TryComp<FactionExceptionComponent>(uid, out var factionException))
|
||||
return;
|
||||
|
||||
if (_factionException.IsIgnored(factionException, user))
|
||||
if (_factionException.IsIgnored(uid, user, factionException))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(comp.FailureString, ("target", uid)), user, user);
|
||||
return;
|
||||
@@ -34,7 +34,7 @@ public sealed class PettableFriendSystem : EntitySystem
|
||||
|
||||
// you have made a new friend :)
|
||||
_popup.PopupEntity(Loc.GetString(comp.SuccessString, ("target", uid)), user, user);
|
||||
_factionException.IgnoreEntity(factionException, user);
|
||||
_factionException.IgnoreEntity(uid, user, factionException);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,6 @@ public sealed class PettableFriendSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var targetComp = AddComp<FactionExceptionComponent>(args.Target);
|
||||
_factionException.IgnoreEntities(targetComp, comp.Ignored);
|
||||
_factionException.IgnoreEntities(args.Target, comp.Ignored, targetComp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,11 @@ namespace Content.Server.GameTicking
|
||||
|
||||
if (_mind.TryGetMind(session.UserId, out var mindId, out var mind))
|
||||
{
|
||||
if (args.OldStatus == SessionStatus.Connecting && args.NewStatus == SessionStatus.Connected)
|
||||
if (args.NewStatus != SessionStatus.Disconnected)
|
||||
{
|
||||
mind.Session = session;
|
||||
_pvsOverride.AddSessionOverride(mindId.Value, session);
|
||||
}
|
||||
|
||||
DebugTools.Assert(mind.Session == session);
|
||||
}
|
||||
@@ -113,7 +116,10 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("player-leave-message", ("name", args.Session.Name)));
|
||||
if (mind != null)
|
||||
{
|
||||
_pvsOverride.ClearOverride(mindId!.Value);
|
||||
mind.Session = null;
|
||||
}
|
||||
|
||||
if (_playerGameStatuses.ContainsKey(args.Session.UserId)) // Corvax-Queue: Delete data only if player was in game
|
||||
_userDb.ClientDisconnected(session);
|
||||
|
||||
@@ -94,7 +94,17 @@ public sealed partial class GameTicker
|
||||
_sawmillReplays.Info($"Moving replay into final position: {state.MoveToPath}");
|
||||
_taskManager.BlockWaitOnTask(_replays.WaitWriteTasks());
|
||||
DebugTools.Assert(!_replays.IsWriting());
|
||||
data.Directory.CreateDir(state.MoveToPath.Value.Directory);
|
||||
|
||||
try
|
||||
{
|
||||
if (!data.Directory.Exists(state.MoveToPath.Value.Directory))
|
||||
data.Directory.CreateDir(state.MoveToPath.Value.Directory);
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
_sawmillReplays.Error($"Error creating replay directory {state.MoveToPath.Value.Directory}: {e}");
|
||||
}
|
||||
|
||||
data.Directory.Rename(data.Path, state.MoveToPath.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace Content.Server.Ghost
|
||||
var minds = _entities.System<SharedMindSystem>();
|
||||
if (!minds.TryGetMind(player, out var mindId, out var mind))
|
||||
{
|
||||
shell.WriteLine("You have no Mind, you can't ghost.");
|
||||
return;
|
||||
mindId = minds.CreateMind(player.UserId);
|
||||
mind = _entities.GetComponent<MindComponent>(mindId);
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<GameTicker>().OnGhostAttempt(mindId, true, true, mind))
|
||||
|
||||
@@ -343,7 +343,7 @@ namespace Content.Server.Ghost.Roles
|
||||
if (ghostRole.MakeSentient)
|
||||
MakeSentientCommand.MakeSentient(mob, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech);
|
||||
|
||||
mob.EnsureComponent<MindContainerComponent>();
|
||||
EnsureComp<MindContainerComponent>(mob);
|
||||
|
||||
GhostRoleInternalCreateMindAndTransfer(args.Player, uid, mob, ghostRole);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Content.Server.Guardian
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -88,7 +89,7 @@ namespace Content.Server.Guardian
|
||||
|
||||
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
|
||||
{
|
||||
component.GuardianContainer = uid.EnsureContainer<ContainerSlot>("GuardianContainer");
|
||||
component.GuardianContainer = _container.EnsureContainer<ContainerSlot>(uid, "GuardianContainer");
|
||||
_actionSystem.AddAction(uid, ref component.ActionEntity, component.Action);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,9 +169,9 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
if (playerSession.AttachedEntity is not {Valid: true} player ||
|
||||
!Exists(player) ||
|
||||
player.IsInContainer() ||
|
||||
ContainerSystem.IsEntityInContainer(player) ||
|
||||
!TryComp(player, out HandsComponent? hands) ||
|
||||
hands.ActiveHandEntity is not EntityUid throwEnt ||
|
||||
hands.ActiveHandEntity is not { } throwEnt ||
|
||||
!_actionBlockerSystem.CanThrow(player, throwEnt))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ public sealed partial class InstrumentComponent : SharedInstrumentComponent
|
||||
public IPlayerSession? InstrumentPlayer =>
|
||||
_entMan.GetComponentOrNull<ActivatableUIComponent>(Owner)?.CurrentSingleUser
|
||||
?? _entMan.GetComponentOrNull<ActorComponent>(Owner)?.PlayerSession;
|
||||
|
||||
[ViewVariables] public PlayerBoundUserInterface? UserInterface => Owner.GetUIOrNull(InstrumentUiKey.Key);
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Stunnable;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
@@ -17,7 +16,6 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Instruments;
|
||||
|
||||
@@ -435,9 +433,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
// Just in case
|
||||
Clean(uid);
|
||||
|
||||
if (instrument.UserInterface is not null)
|
||||
_bui.CloseAll(instrument.UserInterface);
|
||||
_bui.TryCloseAll(uid, InstrumentUiKey.Key);
|
||||
}
|
||||
|
||||
instrument.Timer += frameTime;
|
||||
|
||||
8
Content.Server/Interaction/DragDropSystem.cs
Normal file
8
Content.Server/Interaction/DragDropSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.DragDrop;
|
||||
|
||||
namespace Content.Server.Interaction;
|
||||
|
||||
public sealed class DragDropSystem : SharedDragDropSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -32,8 +32,6 @@ namespace Content.Server.Interaction
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropRequestEvent);
|
||||
|
||||
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
|
||||
}
|
||||
|
||||
@@ -58,45 +56,6 @@ namespace Content.Server.Interaction
|
||||
return _uiSystem.SessionHasOpenUi(container.Owner, StorageComponent.StorageUiKey.Key, actor.PlayerSession);
|
||||
}
|
||||
|
||||
#region Drag drop
|
||||
|
||||
private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var dragged = GetEntity(msg.Dragged);
|
||||
var target = GetEntity(msg.Target);
|
||||
|
||||
if (Deleted(dragged) || Deleted(target))
|
||||
return;
|
||||
|
||||
var user = args.SenderSession.AttachedEntity;
|
||||
|
||||
if (user == null || !_actionBlockerSystem.CanInteract(user.Value, target))
|
||||
return;
|
||||
|
||||
// must be in range of both the target and the object they are drag / dropping
|
||||
// Client also does this check but ya know we gotta validate it.
|
||||
if (!InRangeUnobstructed(user.Value, dragged, popup: true)
|
||||
|| !InRangeUnobstructed(user.Value, target, popup: true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dragArgs = new DragDropDraggedEvent(user.Value, target);
|
||||
|
||||
// trigger dragdrops on the dropped entity
|
||||
RaiseLocalEvent(dragged, ref dragArgs);
|
||||
|
||||
if (dragArgs.Handled)
|
||||
return;
|
||||
|
||||
var dropArgs = new DragDropTargetEvent(user.Value, dragged);
|
||||
|
||||
// trigger dragdrops on the target entity (what you are dropping onto)
|
||||
RaiseLocalEvent(GetEntity(msg.Target), ref dropArgs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
|
||||
{
|
||||
if (ev.Player.AttachedEntity is not { } user)
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
@@ -31,6 +32,13 @@ namespace Content.Server.Light.EntitySystems
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, ToggleActionEvent>(OnToggleAction);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GotEmaggedEvent>(OnGotEmagged);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, UnpoweredFlashlightComponent component, MapInitEvent args)
|
||||
{
|
||||
_actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnToggleAction(EntityUid uid, UnpoweredFlashlightComponent component, ToggleActionEvent args)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Climbing;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Materials;
|
||||
@@ -9,6 +8,7 @@ using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Climbing.Events;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
@@ -160,7 +160,7 @@ namespace Content.Server.Medical.BiomassReclaimer
|
||||
});
|
||||
}
|
||||
|
||||
private void OnClimbedOn(EntityUid uid, BiomassReclaimerComponent component, ClimbedOnEvent args)
|
||||
private void OnClimbedOn(EntityUid uid, BiomassReclaimerComponent component, ref ClimbedOnEvent args)
|
||||
{
|
||||
if (!CanGib(uid, args.Climber, component))
|
||||
{
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace Content.Server.Medical.Components
|
||||
[DataField("scanDelay")]
|
||||
public float ScanDelay = 0.8f;
|
||||
|
||||
public PlayerBoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Sound played on scanning begin
|
||||
/// </summary>
|
||||
|
||||
@@ -7,7 +7,6 @@ using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Climbing;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
@@ -32,6 +31,7 @@ using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
|
||||
namespace Content.Server.Medical;
|
||||
|
||||
|
||||
@@ -51,12 +51,12 @@ namespace Content.Server.Medical
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OpenUserInterface(EntityUid user, HealthAnalyzerComponent healthAnalyzer)
|
||||
private void OpenUserInterface(EntityUid user, EntityUid analyzer)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(user, out var actor) || healthAnalyzer.UserInterface == null)
|
||||
if (!TryComp<ActorComponent>(user, out var actor) || !_uiSystem.TryGetUi(analyzer, HealthAnalyzerUiKey.Key, out var ui))
|
||||
return;
|
||||
|
||||
_uiSystem.OpenUi(healthAnalyzer.UserInterface ,actor.PlayerSession);
|
||||
_uiSystem.OpenUi(ui ,actor.PlayerSession);
|
||||
}
|
||||
|
||||
public void UpdateScannedUser(EntityUid uid, EntityUid user, EntityUid? target, HealthAnalyzerComponent? healthAnalyzer)
|
||||
@@ -64,7 +64,7 @@ namespace Content.Server.Medical
|
||||
if (!Resolve(uid, ref healthAnalyzer))
|
||||
return;
|
||||
|
||||
if (target == null || healthAnalyzer.UserInterface == null)
|
||||
if (target == null || !_uiSystem.TryGetUi(uid, HealthAnalyzerUiKey.Key, out var ui))
|
||||
return;
|
||||
|
||||
if (!HasComp<DamageableComponent>(target))
|
||||
@@ -73,9 +73,9 @@ namespace Content.Server.Medical
|
||||
TryComp<TemperatureComponent>(target, out var temp);
|
||||
TryComp<BloodstreamComponent>(target, out var bloodstream);
|
||||
|
||||
OpenUserInterface(user, healthAnalyzer);
|
||||
OpenUserInterface(user, uid);
|
||||
|
||||
_uiSystem.SendUiMessage(healthAnalyzer.UserInterface, new HealthAnalyzerScannedUserMessage(GetNetEntity(target), temp != null ? temp.CurrentTemperature : float.NaN,
|
||||
_uiSystem.SendUiMessage(ui, new HealthAnalyzerScannedUserMessage(GetNetEntity(target), temp != null ? temp.CurrentTemperature : float.NaN,
|
||||
bloodstream != null ? bloodstream.BloodSolution.FillFraction : float.NaN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Climbing;
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Shared.Destructible;
|
||||
@@ -13,6 +12,7 @@ using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Server.Containers;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
@@ -25,12 +26,32 @@ public sealed class MindSystem : SharedMindSystem
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedGhostSystem _ghosts = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MindContainerComponent, EntityTerminatingEvent>(OnMindContainerTerminating);
|
||||
SubscribeLocalEvent<MindComponent, ComponentShutdown>(OnMindShutdown);
|
||||
}
|
||||
|
||||
private void OnMindShutdown(EntityUid uid, MindComponent mind, ComponentShutdown args)
|
||||
{
|
||||
if (mind.UserId is {} user)
|
||||
{
|
||||
UserMinds.Remove(user);
|
||||
if (_players.GetPlayerData(user).ContentData() is { } oldData)
|
||||
oldData.Mind = null;
|
||||
mind.UserId = null;
|
||||
}
|
||||
|
||||
if (!TryComp(mind.OwnedEntity, out MetaDataComponent? meta) || meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(mind.OwnedEntity.Value, new MindRemovedMessage(uid, mind), true);
|
||||
mind.OwnedEntity = null;
|
||||
mind.OwnedComponent = null;
|
||||
}
|
||||
|
||||
private void OnMindContainerTerminating(EntityUid uid, MindContainerComponent component, ref EntityTerminatingEvent args)
|
||||
@@ -195,11 +216,11 @@ public sealed class MindSystem : SharedMindSystem
|
||||
public override void TransferTo(EntityUid mindId, EntityUid? entity, bool ghostCheckOverride = false, bool createGhost = true,
|
||||
MindComponent? mind = null)
|
||||
{
|
||||
base.TransferTo(mindId, entity, ghostCheckOverride, createGhost, mind);
|
||||
|
||||
if (!Resolve(mindId, ref mind))
|
||||
if (mind == null && !Resolve(mindId, ref mind))
|
||||
return;
|
||||
|
||||
base.TransferTo(mindId, entity, ghostCheckOverride, createGhost, mind);
|
||||
|
||||
if (entity == mind.OwnedEntity)
|
||||
return;
|
||||
|
||||
@@ -239,6 +260,8 @@ public sealed class MindSystem : SharedMindSystem
|
||||
var oldEntity = mind.OwnedEntity;
|
||||
if (oldComp != null && oldEntity != null)
|
||||
{
|
||||
if (oldComp.Mind != null)
|
||||
_pvsOverride.ClearOverride(oldComp.Mind.Value);
|
||||
oldComp.Mind = null;
|
||||
RaiseLocalEvent(oldEntity.Value, new MindRemovedMessage(oldEntity.Value, mind), true);
|
||||
}
|
||||
@@ -290,6 +313,7 @@ public sealed class MindSystem : SharedMindSystem
|
||||
if (mind.UserId == userId)
|
||||
return;
|
||||
|
||||
_pvsOverride.ClearOverride(mindId);
|
||||
if (userId != null && !_players.TryGetPlayerData(userId.Value, out _))
|
||||
{
|
||||
Log.Error($"Attempted to set mind user to invalid value {userId}");
|
||||
@@ -331,6 +355,7 @@ public sealed class MindSystem : SharedMindSystem
|
||||
if (_players.TryGetSessionById(userId.Value, out var ret))
|
||||
{
|
||||
mind.Session = ret;
|
||||
_pvsOverride.AddSessionOverride(mindId, ret);
|
||||
_actor.Attach(mind.CurrentEntity, ret);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,18 @@ namespace Content.Server.NPC.Components;
|
||||
/// Prevents an NPC from attacking ignored entities from enemy factions.
|
||||
/// Can be added to if pettable, see PettableFriendComponent.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(FactionExceptionSystem))]
|
||||
[RegisterComponent, Access(typeof(NpcFactionSystem))]
|
||||
public sealed partial class FactionExceptionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of entities that this NPC will refuse to attack
|
||||
/// Collection of entities that this NPC will refuse to attack
|
||||
/// </summary>
|
||||
[DataField("ignored")]
|
||||
public HashSet<EntityUid> Ignored = new();
|
||||
|
||||
/// <summary>
|
||||
/// Collection of entities that this NPC will attack, regardless of faction.
|
||||
/// </summary>
|
||||
[DataField("hostiles")]
|
||||
public HashSet<EntityUid> Hostiles = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Server.NPC.Systems;
|
||||
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for tracking entities stored in <see cref="FactionExceptionComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(NpcFactionSystem))]
|
||||
public sealed partial class FactionExceptionTrackerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// entities with <see cref="FactionExceptionComponent"/> that are tracking this entity.
|
||||
/// </summary>
|
||||
[DataField("entities")]
|
||||
public HashSet<EntityUid> Entities = new();
|
||||
}
|
||||
24
Content.Server/NPC/Components/NPCRetaliationComponent.cs
Normal file
24
Content.Server/NPC/Components/NPCRetaliationComponent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Server.NPC.Systems;
|
||||
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Entities with this component will retaliate against those who physically attack them.
|
||||
/// It has an optional "memory" specification wherein it will only attack those entities for a specified length of time.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(NPCRetaliationSystem))]
|
||||
public sealed partial class NPCRetaliationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long after being attacked will an NPC continue to be aggressive to the attacker for.
|
||||
/// </summary>
|
||||
[DataField("attackMemoryLength"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan? AttackMemoryLength;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary that stores an entity and the time at which they will no longer be considered hostile.
|
||||
/// </summary>
|
||||
/// todo: this needs to support timeoffsetserializer at some point
|
||||
[DataField("attackMemories")]
|
||||
public Dictionary<EntityUid, TimeSpan> AttackMemories = new();
|
||||
}
|
||||
@@ -18,6 +18,7 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using ClimbableComponent = Content.Shared.Climbing.Components.ClimbableComponent;
|
||||
|
||||
namespace Content.Server.NPC.Pathfinding;
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Server.NPC.Components;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents an NPC from attacking some entities from an enemy faction.
|
||||
/// </summary>
|
||||
public sealed class FactionExceptionSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns whether the entity from an enemy faction won't be attacked
|
||||
/// </summary>
|
||||
public bool IsIgnored(FactionExceptionComponent comp, EntityUid target)
|
||||
{
|
||||
return comp.Ignored.Contains(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents an entity from an enemy faction from being attacked
|
||||
/// </summary>
|
||||
public void IgnoreEntity(FactionExceptionComponent comp, EntityUid target)
|
||||
{
|
||||
comp.Ignored.Add(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a list of entities from an enemy faction from being attacked
|
||||
/// </summary>
|
||||
public void IgnoreEntities(FactionExceptionComponent comp, IEnumerable<EntityUid> ignored)
|
||||
{
|
||||
comp.Ignored.UnionWith(ignored);
|
||||
}
|
||||
}
|
||||
90
Content.Server/NPC/Systems/NPCRetaliationSystem.cs
Normal file
90
Content.Server/NPC/Systems/NPCRetaliationSystem.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles NPC which become aggressive after being attacked.
|
||||
/// </summary>
|
||||
public sealed class NPCRetaliationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _deAggroQueue = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<NPCRetaliationComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<NPCRetaliationComponent, DisarmedEvent>(OnDisarmed);
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, NPCRetaliationComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased)
|
||||
return;
|
||||
|
||||
if (args.Origin is not { } origin)
|
||||
return;
|
||||
|
||||
TryRetaliate(uid, origin, component);
|
||||
}
|
||||
|
||||
private void OnDisarmed(EntityUid uid, NPCRetaliationComponent component, DisarmedEvent args)
|
||||
{
|
||||
TryRetaliate(uid, args.Source, component);
|
||||
}
|
||||
|
||||
public bool TryRetaliate(EntityUid uid, EntityUid target, NPCRetaliationComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
// don't retaliate against inanimate objects.
|
||||
if (!HasComp<MobStateComponent>(target))
|
||||
return false;
|
||||
|
||||
if (_npcFaction.IsEntityFriendly(uid, target))
|
||||
return false;
|
||||
|
||||
_npcFaction.AggroEntity(uid, target);
|
||||
if (component.AttackMemoryLength is { } memoryLength)
|
||||
{
|
||||
component.AttackMemories[target] = _timing.CurTime + memoryLength;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<NPCRetaliationComponent, FactionExceptionComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var factionException, out var metaData))
|
||||
{
|
||||
_deAggroQueue.Clear();
|
||||
|
||||
foreach (var ent in new ValueList<EntityUid>(comp.AttackMemories.Keys))
|
||||
{
|
||||
if (_timing.CurTime < comp.AttackMemories[ent])
|
||||
continue;
|
||||
|
||||
if (TerminatingOrDeleted(ent, metaData))
|
||||
_deAggroQueue.Add(ent);
|
||||
|
||||
_deAggroQueue.Add(ent);
|
||||
}
|
||||
|
||||
foreach (var ent in _deAggroQueue)
|
||||
{
|
||||
_npcFaction.DeAggroEntity(uid, ent, factionException);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.NPC;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ using Content.Shared.NPC;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using ClimbableComponent = Content.Shared.Climbing.Components.ClimbableComponent;
|
||||
using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
@@ -132,7 +134,7 @@ public sealed partial class NPCSteeringSystem
|
||||
{
|
||||
return SteeringObstacleStatus.Completed;
|
||||
}
|
||||
else if (climbing.OwnerIsTransitioning)
|
||||
else if (climbing.NextTransition != null)
|
||||
{
|
||||
return SteeringObstacleStatus.Continuing;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user